Assigning plots to variables

This likely betrays my Javascript inexperience but I am wondering what the best approach is to conditionally return certain plots? My made up example is to say "produce one type of plot when one value is selected in Inputs.select and produce another if more than one are selected. I don’t quite understand how one can assign a plot to a variable and use it later. Right now I am doing something like this:

{
if (islands.length === 1) {
     return  Plot.plot({
        grid: true,
        inset: 10,
        color: {legend: true},
        marks: [
          Plot.frame(),
          Plot.dot(pen_sub, {x: "bill_length_mm", y: "bill_depth_mm", stroke: "green"})
        ]
      });
} else if (islands.length > 1) {
    return  Plot.plot({
        grid: true,
        inset: 10,
        color: {legend: true},
        marks: [
          Plot.frame(),
          Plot.dot(pen_sub, {x: "bill_length_mm", y: "bill_depth_mm", stroke: "species"})
        ]
      });
}

However I am wondering if I can make that a little nicer by assigning the two plots to variables and then just reference those variables? I have a notebook here: penguins data / Sam Albers | Observable

I’ve just tried assigning them to a variable but I get an “undefined” output. For example I am not certain quite why this doesn’t work:

length_one_plot = Plot.plot({
        grid: true,
        inset: 10,
        color: {legend: true},
        marks: [
          Plot.frame(),
          Plot.dot(pen_sub, {x: "bill_length_mm", y: "bill_depth_mm", stroke: "green"})
        ]
      })

length_gt_one_plot = Plot.plot({
        grid: true,
        inset: 10,
        color: {legend: true},
        marks: [
          Plot.frame(),
          Plot.dot(pen_sub, {x: "bill_length_mm", y: "bill_depth_mm", stroke: "species"})
        ]
      });

{
  if (islands.length === 1) {
    return  length_one_plot;
  } else if (islands.length > 1) {
    return  length_gt_one_plot;
  }
}

TIA.

Because the difference in your two versions of the plot are minor, in this case I’d be inclined to use the ternary operator to specify the two different colour encodings. This keeps the specification nice and compact:

Plot.plot({
  grid: true,
  inset: 10,
  color: { legend: true },
  marks: [
    Plot.frame(),
    Plot.dot(pen_sub, {
      x: "bill_length_mm",
      y: "bill_depth_mm",
      stroke: islands.length === 1 ? "green" : "species"
    })
  ]
})
1 Like

Thanks for this! Yeah I can see how in my example that would work great. But for my actual use case, I’d really like to understand how to properly assign these because the plots are very different.

Can you share an example of what you tried already where the notebook isn’t working how you want?

If you need to store plot specifications without actually displaying them at the point you specifying them there are a number of options. But one simple one is to recognise that Plot.plot() is just a function that takes a JavaScript object with the plot specification as a parameter. So you could store that parameter object and then Plot it when needed.

For example:

mySpec1 = ({
  grid: true,
  inset: 10,
  color: { legend: true },
  marks: [
    Plot.frame(),
    Plot.dot(pen_sub, {
      x: "bill_length_mm",
      y: "bill_depth_mm",
      stroke: "green"
    })
  ]
})

And then when needed:

Plot.plot(mySpec1)

You can store these specs just as you would any other values, for example in arrays, objects, Maps etc.

1 Like

I’ve updated the notebook and the question to reflect that. Thanks for the prod.

Oh wow. This really helped click something into place for me. This certainly works but I am curious where I might read more about the other options.

I think the approach to use depends on what you are intending to achieve.

Assigning the full specification objects to variables gives you lots of flexibility in that there need be no relation between each specification. But it can result in a ‘bad code smell’ in that there may be lots of repetition in the specs, especially as they get longer.

My instinct is to try to minimise code repetition and isolate the conditional elements of a spec. That is what led my suggestion for the ternary operator.

If the conditional dependencies are more complex than binary alternatives, you could create the specification as a function where you parameterise the bits you need to. For example:

function myCustomSpec(clr) {
  return {
    grid: true,
    inset: 10,
    color: { legend: true },
    marks: [
      Plot.frame(),
      Plot.dot(pen_sub, {
        x: "bill_length_mm",
        y: "bill_depth_mm",
        stroke: clr // "species" or "green" or any other colour provided by parameter.
      })
    ]
  };
}

But if you have a function, you many not need to store the spec separately from the call to Plot.plot() because it is only invoked at the point you call the function:

function myCustomPlot(clr) {
  return Plot.plot({
    grid: true,
    inset: 10,
    color: { legend: true },
    marks: [
      Plot.frame(),
      Plot.dot(pen_sub, {
        x: "bill_length_mm",
        y: "bill_depth_mm",
        stroke: clr // "species" or "green" or any other colour provided here.
      })
    ]
  });
}
myCustomPlot("green")

It’s also worth remembering that many of the values in a Plot specification can be functions themselves (using the compact arrow function notation) which allows you to customise different parts of a specification. The Plot documentation has plenty of examples that use arrow functions (look for (d) =>...) to help you build an understanding of how and when they can be useful.

Finally, something I find useful for more complex plots is not to store entire specifications as variables, but just each of the marks. This allows you to attach meaningful names to them, making reading the code easier, and helps if you need to selectively add or remove marks. This is the approach I used in this wheat and wages exercise:

Plot.plot({
  width: w,
  height: h,
  margin: margin,
  style: `background:${bgClr}; font-family: ${fnt.script};`,
  x: xAxis,
  marks: [
    barsAndArea,
    yAxes,
    grid,
    monarchs,
    century,
    titlePanel,
    annotation
  ].flat()
})
1 Like