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

Ordinal intervals? #513

Closed
mbostock opened this issue Aug 20, 2021 · 4 comments · Fixed by #849
Closed

Ordinal intervals? #513

mbostock opened this issue Aug 20, 2021 · 4 comments · Fixed by #849
Labels
enhancement New feature or request question Further information is needed

Comments

@mbostock
Copy link
Member

People are often tempted to apply an ordinal scale (sometimes implicitly via Plot.cell or Plot.bar) to temporal data. This can be misleading because gaps in the data, such as missing days, are not shown. Also, the ordinal scale can’t know the expected regularity of the temporal data, so there’s no way for it to choose an appropriate tick reduction strategy and tick labels are often overlapping.

But what if you told the ordinal scale the expected regularity of the data, say as a D3 time interval? Then the ordinal scale could explicitly compute the complete domain from the extent, e.g. d3.utcDay.range(start, stop), and show any gaps in the data. The ordinal scale could also floor the data using the interval (d3.utcDay.floor(date)) as a scale transform, such that the input dates are forced to align with the expected interval and thus be present in the domain. The ordinal scale could likely even chose an appropriate tick reduction strategy (e.g., every Sunday), at least if it’s a known time interval, or perhaps using interval.every.

Related #74.

@mbostock mbostock added enhancement New feature or request question Further information is needed labels Aug 20, 2021
@tophtucker
Copy link
Contributor

this feels like a great idea! teases out something subtle between "ordinal" and "continuous". like, continuous scales have the nice property that they're ordered, gapless, and linear — and you can have all that, but be discrete!

@mbostock
Copy link
Member Author

People seem to overlook that marks imply specific scale types: specifically that bar (and cell) imply an ordinal scale. This makes sense if you consider that Plot has both a more explicit way of specifying scale type (the type scale option) and a more common implicit way (type inference from data).

So, maybe bar and cell shouldn’t force the ordinal scale type. Instead, if you use a quantitative domain, bars could behave like ticks (say, fixed 1.5px width) and cells could behave like dots (say, fixed 3px radius). If you really want bars with quantitative or temporal data, you have to set type = ordinal (or type = band) in the scale definition. (And maybe a bar and cell mark would automatically promote an ordinal scale into a band scale.)

Or to put it another way, when people use Plot.bar with quantitative or temporal data and see bad ticks, they think they need to fix the ticks; but if the bars are instead too skinny, it’s more suggestive that the bar needs a width. And that width can be specified either by adopting a band scale, or by binning with a rect.

I guess I also wonder if Plot.bar could somehow automatically map to Plot.rect + Plot.bin with quantitative or temporal data. That feels a little magical to me, but so did the implicit Plot.stack transform at first and I love that now.

@mbostock
Copy link
Member Author

Another thought: I think in many cases we want a rect instead of a bar here. Here’s a helper that works well as a replacement for bar:

function timebarY(data, {x, interval = d3.utcDay, ...options} = {}) {
  const X = Plot.valueof(data, x);
  return Plot.rectY(data, {
    x1: X,
    x2: X.map(x => interval.offset(x)),
    insetLeft: 0.5,
    insetRight: 0.5,
    ...options
  });
}

And then:

timebarY(data, {x: "date", interval: d3.utcHour, y: "count"}).plot()

@mbostock
Copy link
Member Author

mbostock commented Sep 20, 2021

Here’s another example that I was at first surprised by, but makes sense if you think about it. Say you have a 1-dimensional tick plot to look at a distribution:

untitled-93

Plot.tickX(cars, {x: "Horsepower"}).plot()

And then you realize you can’t tell how many ticks are overlapping because there’s occlusion, so you think, oh, I’ll stack the ticks.

untitled-94

Plot.tickX(cars, Plot.stackY({x: "Horsepower"})).plot()

Then you get confused: Where are the 0.5’s coming from? Why is it stacking down? And it’s because Plot.tickX implies that y is a band scale, so each tick represents the midpoint of the stacked interval, and band scales are reversed by default so they read from the top down rather than the bottom up.

So probably what you wanted instead was a rule rather than a tick. A ruleX has an x-value and a y-extent, which fits stacking perfectly (assuming each data point has a y-length of one).

untitled-92

Plot.ruleX(cars, Plot.stackY({x: "Horsepower"})).plot()

And in fact if you had used a rule from the beginning, it would have looked identical.

untitled-93

Plot.ruleX(cars, {x: "Horsepower"}).plot()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request question Further information is needed
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants