Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Legend options unclear in documentation #825

Closed
stevage opened this issue Mar 24, 2022 · 8 comments
Closed

Legend options unclear in documentation #825

stevage opened this issue Mar 24, 2022 · 8 comments
Labels
documentation Improvements or additions to docs

Comments

@stevage
Copy link

stevage commented Mar 24, 2022

I'm coming to Observable Plot for the first time, without experience of Observable.

It's quite confusing trying to understand how to configure a legend.

plot.legend(name, options)

This would be clearer if the parameter was called "scale" not "name"

mylegend = myplot.legend("color")

This would be clearer if options were being passed in, because the whole rest of the section is about the options, but they're missing from the one actual example.

For an inline legend, use the scale.legend option:

It's very unclear how one "uses the scale.legend option". An example line of code would help a lot.

In the documentation under "Legends" we see:

mylegend = myplot.legend("color")

But this value "color" is not explained anywhere.

There is a list of options (options.tickFormat, options.swatchSize...) but it is incomplete. Other options (eg, options.legend) are referred to only in the paragraph text, which makes this section very difficult to skim.

At the bottom of the section, this example is given:

Plot.legend({color: {type: "linear"}})

But "color" is not documented anywhere.

I think maaaybe in the reference to scale.legend above, "scale" is actually a meta-variable for which color might be the actual value? This is really unclear and takes a lot of effort to work out.

Also, in this notebook, there is reference to a style property that doesn't seem to be documented here.

@mbostock
Copy link
Member

It's quite confusing trying to understand how to configure a legend.

Your main confusion seems to be around the different ways in which legends can be created. There are three.

First, and most commonly, is to include a legend as part of another plot. This is done by setting the legend option to true on the scale for which you wish to show a legend; typically the color scale, but it can also be the opacity or symbol scale. To take the first example from the notebook you linked:

Plot.plot({
  color: {
    legend: true
  },
  marks: [
    Plot.dot(athletes, {x: "weight", y: "height", stroke: "sex"})
  ]
})

I can add this example to the README.

The second way that you can create a legend is as a “standalone” legend; that is, a legend by itself rather than a legend included in another plot. This is done by calling Plot.legend. To pull another example from the legends notebook you linked:

Plot.legend({
  color: {
    interpolate: (t) => wavelengthToColor(400 + t * 350),
    domain: [400, 750]
  },
  width: 350,
  ticks: 10,
  label: "Wavelength (nm) →"
}) 

The third way is that you can create a detached legend derived from an existing plot. This is similar to calling Plot.legend but you don’t have to repeat the specification of the (e.g. color) scale because you pull the scale definition from the existing plot. In this case, you would first declare your plot (typically without a legend):

myplot = Plot.plot({
  marks: [
    Plot.dot(athletes, {x: "weight", y: "height", stroke: "sex"})
  ]
}) 

And then you would create the detached legend by calling myplot.legend:

myplot.legend("color")

(There are again examples of this in the notebook you linked.)

This would be clearer if options were being passed in, because the whole rest of the section is about the options, but they're missing from the one actual example.

The scale options aren’t passed in because this example is showing you how to create a detached legend (the third case above) where the color scale is defined by an existing plot (myplot). You would only specify the scale definition in the first two cases: when defining the color scale for a plot with an included legend, or when defining the color scale for a standalone legend.

There is a list of options (options.tickFormat, options.swatchSize...) but it is incomplete. Other options (eg, options.legend) are referred to only in the paragraph text, which makes this section very difficult to skim.

The tickFormat and swatchSize options are legend options, where as the legend option is a scale option. I’m not aware of any undocumented options.

But "color" is not documented anywhere.

The scales that Plot knows about are listed here:

https://github.com/observablehq/plot/blob/main/README.md#scale-options

The way that you declare a scale for Plot.legend is that same way you declare a scale for Plot.plot.

The fact that legends are supported for only color, opacity, and symbol scales is documented here:

https://github.com/observablehq/plot/blob/main/README.md#legends

there is reference to a style property that doesn't seem to be documented here.

Yes, that seems to be an oversight. The style option for standalone legends is functionally identical to the style option for plots which is documented here:

https://github.com/observablehq/plot/blob/main/README.md#layout-options

@mbostock mbostock added the documentation Improvements or additions to docs label Mar 24, 2022
@stevage
Copy link
Author

stevage commented Mar 24, 2022

First, and most commonly, is to include a legend as part of another plot. This is done by setting the legend option to true on the scale for which you wish to show a legend; typically the color scale

Yes, I was aware of this, but I couldn't see any way to then configure the legend if doing it that way, so I went with the separate method.

I did find it quite unclear trying to understand these three ways of creating a Plot at the top of the documentation. For instance, this single sentence was pretty incomprehensible to me at the start, and requires an awful lot of inference on the part of the reader:

When drawing a single mark, you can call mark.plot(options) as shorthand.

To break down the issues:

  • "When drawing..." - why am I doing this? This immediately assumes the reader has some alternative purpose other than the main one, and we're only a paragraph in.
  • "A single mark" - for a reader not steeped in Observable language, this was extremely confusing. A single mark to me meant a single dot on a chart, not a series of dots... Why would I be trying to draw a single dot?
  • "call mark.plot", so it turns out "mark" is a meta-variable that could actually be "bar"?
  • "call mark.plot(options)" is almost impossible to reconcile against the actual code given, Plot.barY(alphabet, {x: "letter", y: "frequency"}).plot(). Where is "options"? It seems to be a parameter not actually passed here.

To be slightly clearer about the purpose of raising this issue, I'm not really looking for help understanding the documentation, I'm trying to suggest ways in which to improve it to make it more digestible for a first time user. Pretty soon I'm sure this stuff will make sense to me and then it's very hard to see the issues in the doco in order to raise issues about them.

@mbostock
Copy link
Member

Okay, thanks for the feedback. Unfortunately I don’t know how to make any of the documentation clearer, but I appreciate you taking the time to share what you found confusing. Did you have a chance to read any of the documentation on Observable (e.g., Plot: Marks)? You may find that a better introduction to Plot than the README.

mbostock added a commit that referenced this issue Mar 24, 2022
@mbostock
Copy link
Member

Re. mark.plot(options), I made a bunch of tweaks to the README, which I hope clarifies some of the points you raised:

https://github.com/observablehq/plot/blob/main/README.md#markplotoptions

I also moved the documentation of mark.plot down to the Marks section, rather than as part of the Plot.plot documentation. I’m not sure if that helps but the shorthand feels like a more advanced topic and therefore it’s better to cover the longhand of Plot.plot first.

I also added links to the notebooks that introduce Plot’s core concepts to the top of the README, in hopes that people will start there rather than immediately diving into the API reference.

@mbostock
Copy link
Member

mbostock commented Mar 25, 2022

Okay, here are the changes I’ve made inspired by your feedback:

9990f46...95ebd4c

Thank you again for your time. I apologize if my frustration was apparent earlier. It’s extraordinarily difficult to document all this stuff, let alone in a way that is readily understandable. When someone points out all the things that are confusing, it can feel like an insurmountable problem. But I appreciate that you were specific in what you found confusing, pointing out the ambiguities or implicit assumptions, and shining a light on ways we can improve. I’m sure there’s still much more to improve but I hope it’s getting better.

@stevage
Copy link
Author

stevage commented Mar 25, 2022

I totally feel your pain. Documentation is really hard, and (I find) it's very hard to fix something that seems to make sense, because you already know the subject matter so well.

Ideally, you'd have some auto-generated reference material that allows seeing all the options for every method, etc (rather than just a hand-edited README) but obviously it's a lot of work to set up.

And thanks for understanding my goals in providing feedback. Normally, with code, I'd wait until I was more familiar with it before offering suggestions, but with documentation like this, it kind of has to be at the start.

While I'm here, three more bits of feedback:

  • I found it surprising and slightly confusing that the initial example of adding a plot uses document.body.appendChild(). Given that I was expecting to be changing options and data and re-rendering the plot, I was expecting to be able to pass a document element to the function (like Mapbox GL JS does), and have Plot render into that element. It's hard to imagine anyone really ever wanting to use document.body.appendChild() so it sort of sets you off down a dead-end street. A better example might use something like document.getElementById('chart-container').replaceChildren(), which is not really more complicated, and is a useful snippet longer term (I think).
  • I found it very confusing and difficult working out how to actually supply data to the Plot. I skimmed back and forth many times looking for a property called data or similar, before eventually noticing the reference to a variable called alphabet sort of buried at the end of the paragraph under Marks. Without seeing the actual definition of alphabet it required quite a lot of reading between the lines to figure out what data structure it should be. Suggestion: add an extra line that declares alphabet with some data.
  • It would be valuable if the README summarised what features Plot does and doesn't support. I was surprised to discover later on that it doesn't support animated transitions or user interaction with charts, for instance.

@mbostock
Copy link
Member

Ideally, you'd have some auto-generated reference material that allows seeing all the options for every method, etc (rather than just a hand-edited README) but obviously it's a lot of work to set up.

I think eventually we will switch to TypeScript, or at least maintain type declarations #401, which will help.

I found it surprising and slightly confusing that the initial example of adding a plot uses document.body.appendChild.

There are so many ways that people use the DOM, it’s impossible to satisfy everyone, and so I tried to show the most basic (vanilla) usage. Most people use a framework these days, like React:

https://github.com/observablehq/plot-create-react-app-example/blob/main/src/App.js

I guess I don’t really want to take on the responsibility of teaching the DOM in this README; I’m just going to focus on Plot’s API, and leave it to the reader to figure out how to insert stuff into the DOM. (Also, on Observable, which is our primary audience, you don’t have to manage the DOM yourself since a cell can simply return an element.)

I found it very confusing and difficult working out how to actually supply data to the Plot.

Plot is intentionally very flexible on how you can specify data. The only requirement is that data is compatible with Array.from, which means an array, a typed array, an iterable, or even a {length} object will work. That’s mentioned in the Marks section, but it could be called out more explicitly:

Plot also supports columnar data for greater efficiency with bigger datasets; for example, data can be specified as any array of the appropriate length (or any iterable or value compatible with Array.from), and then separate arrays of values can be passed as options.

There’s an example of a “sales” dataset in the README that’s similar to what the “alphabet” dataset is. But there are a bunch of other datasets alluded to the README (“aapl”, “inequality”, etc.). All these are included in the test data, but I don’t want to have to repeat the explanation for every snippet. I dunno. Maybe I can find another place to put it.

It would be valuable if the README summarised what features Plot does and doesn't support. I was surprised to discover later on that it doesn't support animated transitions or user interaction with charts, for instance.

For now, most interaction comes in the form of external controls and re-rendering (e.g., using Observable inputs). But we are also working on interactive selection of data such as brushing; we mention this in the introduction. See #721. We also have some (admittedly terse) issues for animation #166 and incremental rendering #328. Folks have also written plugins for doing some of this already, such as Mike Freeman’s tooltips.

@kentr
Copy link
Contributor

kentr commented May 16, 2022

@mbostock

I realize this issue is closed. I'm just adding a thought in case it's useful.

Ideally, you'd have some auto-generated reference material that allows seeing all the options for every method, etc (rather than just a hand-edited README) but obviously it's a lot of work to set up.

I think eventually we will switch to TypeScript, or at least maintain type declarations #401, which will help.

I agree that TypeScript will help. I don't think that it replaces auto-generated documentation, though.

I find JSDoc-style comments helpful in the IDE and useful for auto-generated docs.

There are GitHub actions for auto-generation with JSDoc. I haven't used them, but they might eliminate the need to set up tooling & workflow within the project itself.

There's also an auto-generation tool that parses JSDoc-style doc blocks in TypeScript projects.

Obviously, I don't know the project's priorities, resources, and traditions; but i'm wondering if it might be worthwhile to begin auto-documentation with JSDoc even before using TypeScript.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to docs
Projects
None yet
Development

No branches or pull requests

3 participants