Skip to content

a generic alternative for the collapsed scales #474

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

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/mark.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {color} from "d3";
import {color, max, min} from "d3";
import {ascendingDefined, nonempty} from "./defined.js";
import {plot} from "./plot.js";

Expand Down Expand Up @@ -317,7 +317,20 @@ export function values(channels = [], scales) {
if (scale !== undefined) {
scale = scales[scale];
if (scale !== undefined) {
value = Array.from(value, scale);
if (scale.isCollapsed) {
switch(name) {
case "x1":
case "y1":
value = Array.from(value).fill(min(scale.range())); break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is slightly different behavior than my PR this it will apply the scale’s inset. That might be desirable, though it’s different than the behavior of one-dimensional marks, and it’s also now dependent on the way the scale range is defined (which can be overridden). I assume it was done because the dimensions aren’t passed into this method, but it’s worth thinking about.

Also this requires materializing another array which is slow although not likely to be an issue. 😅

case "x2":
case "y2":
value = Array.from(value).fill(max(scale.range())); break;
default:
value = Array.from(value, scale); break;
}
} else {
value = Array.from(value, scale);
}
}
}
values[name] = value;
Expand Down
16 changes: 16 additions & 0 deletions src/scales/quantitative.js
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,7 @@ export function ScaleQ(key, scale, channels, {

if (range !== undefined) scale.range(range);
if (clamp) scale.clamp(clamp);
scale.isCollapsed = isCollapsed(scale);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It’d be good to avoid the mutation here.

return {type: "quantitative", reverse, domain, range, scale, inset, percent};
}

Expand Down Expand Up @@ -284,3 +285,18 @@ function inferLogDomain(channels) {
}
return [1, 10];
}

// Certain marks have special behavior if a scale is collapsed, i.e. if the
// domain is degenerate and represents only a single value such as [3, 3]; for
// example, a rect will span the full extent of the chart along a collapsed
// dimension (whereas a dot will simply be drawn in the center).
export function isCollapsed(scale) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You moved this into quantitative.js, but this behavior isn’t limited to quantitative scales (although that is where it most often applies). See the ordinalBar example, which should also be considered collapsed if the domain only contains a single value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clarify the ordinalBar example? I've tried several variations and all seem to give the same chart under both branches. For example Plot.barY("A", {x: (d, i) => i, y: d => d}).plot() gives a large rect (width x height).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The example is with a point scale:

Plot.plot({
  y: {
    type: "point",
    grid: true
  },
  marks: [
    Plot.barY([0, 0, 0], {x: (d, i) => i, y2: d => d}),
    Plot.ruleY([0])
  ]
})

This is admittedly a pretty pointless plot, but it does demonstrate the issue: in this PR, the y-domain is not considered collapsed because it is not a quantitative scale, even though its domain only has a single value [0].

Screen Shot 2021-08-09 at 6 34 35 PM

Whereas in my PR you get:

Screen Shot 2021-08-09 at 6 34 47 PM

const domain = scale.domain();
const value = scale(domain[0]);
for (let i = 1, n = domain.length; i < n; ++i) {
if (scale(domain[i]) - value) {
return false;
}
}
return true;
}
22 changes: 22 additions & 0 deletions test/output/singleValueBar.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
45 changes: 45 additions & 0 deletions test/output/singleValueBin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions test/plots/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ export {default as sfTemperatureBandArea} from "./sf-temperature-band-area.js";
export {default as simpsonsRatings} from "./simpsons-ratings.js";
export {default as simpsonsRatingsDots} from "./simpsons-ratings-dots.js";
export {default as simpsonsViews} from "./simpsons-views.js";
export {default as singleValueBar} from "./single-value-bar.js";
export {default as singleValueBin} from "./single-value-bin.js";
export {default as stackedBar} from "./stacked-bar.js";
export {default as stocksIndex} from "./stocks-index.js";
export {default as travelersYearOverYear} from "./travelers-year-over-year.js";
Expand Down
12 changes: 12 additions & 0 deletions test/plots/single-value-bar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import * as Plot from "@observablehq/plot";

export default async function() {
return Plot.plot({
marks: [
Plot.barY({length: 1}, {x: ["foo"], y1: [0], y2: [0]}),
Plot.ruleX(["foo"], {stroke: "red", y1: [0], y2: [0]}),
Plot.dot({length: 1}, {x: ["foo"], y: [0], r: 100, fill: "white"}),
Plot.text({length: 1}, {x: ["foo"], y: [0], text: ["♜"], fontSize: 180})
]
});
}
5 changes: 5 additions & 0 deletions test/plots/single-value-bin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import * as Plot from "@observablehq/plot";

export default async function() {
return Plot.rectY([3], Plot.binX()).plot();
}