Can you vary scales across facets in Plot.plot?

I’m using facets in Plot.plot and wondered if there is an equivalent to setting scales to ‘free’ in R ggplot2? I’d like the scales to vary across rows rather than be shared across all facets. I’ve not been able to find an example.

1 Like

Not really, even though it’s often possible to find ways to go around the limitation (by normalizing on max, for an example see Plot scatter plot matrix / Fil / Observable).

Please feel free to open an issue, or share a notebook with a specific application.

Hi, i’m also trying to adapt your normalizeMaxY function to a “common” bar plot but without success so far. Do you have any advice / hint ?
Here you can find a reproductible example : Facet bar plot / Rémi L | Observable (observablehq.com)
Thanks

try this:

// common bar plot, with n
Plot.plot({
  x: {
    label: "n"
  },
  facet: {
    data: data, 
    x: "facet"
  },
  marks:[
    Plot.barX(data, Plot.normalizeX({
      x: "x",
      y: "y"
    }))
  ]
})
1 Like

I would be interested, how to facet on one category, without showing the levels of the empty sub-categories on the axis in the facets. An example, where the empty sub-categories are shown is here.

EDIT: Or as an alternative do something like this with the categories and their sub-categories.

@urswilke If I understand your question correctly, the answer is currently to create a distinct chart for each category. Currently in Plot all scales are shared between facets (but please vote up Facet with varying scales? · Issue #8 · observablehq/plot · GitHub — concrete examples are also welcome).

I thought I could use @Maya’s approach to free scale in Plot.rectY, but I get an error: “transforms cannot be applied after initializers”.

@nachocab I’ve sent you a suggestion to fix the error. The issue stems from Plot.barY implicitly applying the stack transform when it is called with the y channel. If you call stackY explicitly instead, and before the custom initializer, it will work. The barY mark then receives fully formed y1 and y2 channels, and does not try to add stacking. The initializer needs to be adapted to work with the y1 and y2 channels instead of the y channel (in that case, only y2 is useful).

1 Like

Thanks, Fil! Is there a similar fix to get scalefreeX? That’s actually what I was going for. :sweat_smile:

I see; it’s certainly possible:

// adapted from https://observablehq.com/@mayagans/from-ggplot2-to-plot
scalefreeX = (options) =>
  Plot.initializer(options, (data, facets, channels, scales, dimensions) => {
    const {
      x1: { value: X1 },
      x2: { value: X2 }
    } = channels;
    for (const index of facets) {
      const x = d3
        .scaleUtc(d3.extent(Array.from(index, (i) => [X1[i], X2[i]]).flat()), [
          dimensions.marginLeft,
          dimensions.width - dimensions.marginRight
        ]);
      for (const i of index) (X1[i] = x(X1[i]), X2[i] = x(X2[i]));
    }
    return { data, facets, channels: { x1: { value: X1 }, x2: { value: X2 } } };
  })

However the x axis is now completely useless.

Instead, I’d recommend to do work with a relative date:

      x: d => d3.utcDay.offset(new Date("1970-01-01"), d3.utcDay.count(d3.utcYear(d.date), d.date)),

or, more simply:

      x: d => +d.date - +d3.utcYear(d.date),

And then fix the axis’ tick format:

  x: { tickFormat: "%b", ticks: 12 },

(Expect a slight mis-alignment of days to months if you have a bisextile year—you should then use d3.utcMonth to floor the value before removing the year)

1 Like

Thanks for the help! I’ll study all your suggestions to understand how initializers actually work. :sweat_smile:

I found a hack to plot multi-scale axis for categorical data (see here).

But if there is a solution using facets I would very much prefer this, because in this hack I would also need to adapt tickSize for labels of different width for the Plot.barX plot.