Skip to content

mark-level facets #1085

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

Merged
merged 37 commits into from
Dec 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
eb002f0
define facets from each mark
Fil Oct 11, 2022
1c282d7
allow facet: "exclude" in mark faceting
Fil Oct 11, 2022
b4aa8b5
a bit more documentation
Fil Oct 11, 2022
1f597e9
unnecessarily polish multiplication table test
mbostock Oct 12, 2022
109f6ec
stricter/looser facet validation
mbostock Oct 12, 2022
14273d8
add TODO
mbostock Oct 12, 2022
60b0684
remove and simplify the weird parts (j, empty facet determination)
Fil Oct 13, 2022
7cfd8d5
clean up filterFacets
Fil Oct 13, 2022
532e4c3
fix comments
Fil Oct 13, 2022
eb1bb81
regroup all facet handling, simplify
Fil Oct 16, 2022
356f725
inline maybeFacet; improve backwards compatibility
mbostock Oct 22, 2022
611cef0
minimize diff
mbostock Oct 22, 2022
868a6f5
comments
mbostock Oct 22, 2022
98dd563
clean
Fil Oct 23, 2022
aaa44e6
fix tests
mbostock Dec 1, 2022
0e641c8
minimize diff
mbostock Dec 2, 2022
ac90ac6
keep top-level facet state separate
mbostock Dec 2, 2022
c7ad01b
avoid re-initializing fx and fy channels
mbostock Dec 2, 2022
df9098f
TODO re. applyScaleTransforms
mbostock Dec 2, 2022
d271b2c
prettier
mbostock Dec 2, 2022
b677fe7
adopt InternMap; remove sorting
mbostock Dec 2, 2022
63480fb
nullish, not undefined
mbostock Dec 2, 2022
a46f447
style
mbostock Dec 2, 2022
8e32895
remove TODO
mbostock Dec 2, 2022
173af3c
fx and fy aren’t in mark.channels
mbostock Dec 2, 2022
313eb5f
Merge branch 'main' into fil/mark-facets
mbostock Dec 8, 2022
6092cf7
remove todo
Fil Dec 8, 2022
7406b79
fix comment
Fil Dec 8, 2022
7954d0c
use groups
Fil Dec 8, 2022
e90f75f
Merge commit 'f17158a9' into fil/mark-facets
Fil Dec 8, 2022
39bb3d4
tighten state
Fil Dec 8, 2022
01bcd78
sparse facetsIndex
mbostock Dec 8, 2022
316c1fd
Merge branch 'main' into fil/mark-facets
mbostock Dec 9, 2022
d1e902a
simpler facetsIndex initialization
mbostock Dec 9, 2022
eb36868
small consolidation
mbostock Dec 9, 2022
2ec8d08
compute exclude facet index earlier
mbostock Dec 9, 2022
4e9b4a9
simpler facet channels collection
mbostock Dec 9, 2022
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,21 @@ Plot.plot({

When the *include* or *exclude* facet mode is chosen, the mark data must be parallel to the facet data: the mark data must have the same length and order as the facet data. If the data are not parallel, then the wrong data may be shown in each facet. The default *auto* therefore requires strict equality (`===`) for safety, and using the facet data as mark data is recommended when using the *exclude* facet mode. (To construct parallel data safely, consider using [*array*.map](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map) on the facet data.)

Alternatively, facets can be defined for each individual mark by specifying the channel options **fx** or **fy**. In that case, the **facet** option only considers the mark data, and the default *auto* setting is equivalent to *include*. Other values of the *facet* option are unchanged: null or false disable faceting, and *exclude* draws the subset of the mark’s data *not* in the current facet.

```js
Plot.plot({
marks: [
Plot.dot(penguins, {
x: "culmen_length_mm",
y: "culmen_depth_mm",
fx: "sex",
fy: "island"
})
]
})
```

## Legends

Plot can generate legends for *color*, *opacity*, and *symbol* [scales](#scale-options). (An opacity scale is treated as a color scale with varying transparency.) For an inline legend, use the *scale*.**legend** option:
Expand Down
6 changes: 1 addition & 5 deletions src/channel.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ export function Channel(data, {scale, type, value, filter, hint}) {
}

export function Channels(descriptors, data) {
return Object.fromEntries(
Object.entries(descriptors).map(([name, channel]) => {
return [name, Channel(data, channel)];
})
);
return Object.fromEntries(Object.entries(descriptors).map(([name, channel]) => [name, Channel(data, channel)]));
}

// TODO Use Float64Array for scales with numeric ranges, e.g. position?
Expand Down
374 changes: 233 additions & 141 deletions src/plot.js

Large diffs are not rendered by default.

644 changes: 644 additions & 0 deletions test/output/multiplicationTable.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2,134 changes: 1,067 additions & 1,067 deletions test/output/penguinCulmen.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2,905 changes: 2,905 additions & 0 deletions test/output/penguinCulmenMarkFacet.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
112 changes: 112 additions & 0 deletions test/output/penguinFacetAnnotated.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 100 additions & 0 deletions test/output/penguinFacetAnnotatedX.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions test/plots/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,15 @@ export {default as morleyBoxplot} from "./morley-boxplot.js";
export {default as moviesProfitByGenre} from "./movies-profit-by-genre.js";
export {default as moviesRatingByGenre} from "./movies-rating-by-genre.js";
export {default as musicRevenue} from "./music-revenue.js";
export {default as multiplicationTable} from "./multiplication-table.js";
export {default as ordinalBar} from "./ordinal-bar.js";
export {default as penguinAnnotated} from "./penguin-annotated.js";
export {default as penguinCulmen} from "./penguin-culmen.js";
export {default as penguinCulmenArray} from "./penguin-culmen-array.js";
export {default as penguinCulmenDelaunay} from "./penguin-culmen-delaunay.js";
export {default as penguinCulmenDelaunayMesh} from "./penguin-culmen-delaunay-mesh.js";
export {default as penguinCulmenDelaunaySpecies} from "./penguin-culmen-delaunay-species.js";
export {default as penguinCulmenMarkFacet} from "./penguin-culmen-mark-facet.js";
export {default as penguinCulmenVoronoi} from "./penguin-culmen-voronoi.js";
export {default as penguinVoronoi1D} from "./penguin-voronoi-1d.js";
export {default as penguinDensity} from "./penguin-density.js";
Expand All @@ -154,6 +156,8 @@ export {default as penguinDensityZ} from "./penguin-density-z.js";
export {default as penguinDodge} from "./penguin-dodge.js";
export {default as penguinDodgeHexbin} from "./penguin-dodge-hexbin.js";
export {default as penguinDodgeVoronoi} from "./penguin-dodge-voronoi.js";
export {default as penguinFacetAnnotated} from "./penguins-facet-annotated.js";
export {default as penguinFacetAnnotatedX} from "./penguins-facet-annotated-x.js";
export {default as penguinFacetDodge} from "./penguin-facet-dodge.js";
export {default as penguinFacetDodgeIdentity} from "./penguin-facet-dodge-identity.js";
export {default as penguinFacetDodgeIsland} from "./penguin-facet-dodge-island.js";
Expand Down
44 changes: 44 additions & 0 deletions test/plots/multiplication-table.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";

export default async function () {
const numbers = d3.range(2, 10);
return Plot.plot({
height: 450,
width: 450,
padding: 0,
color: {type: "categorical"},
fx: {axis: "top", tickSize: 6},
fy: {tickSize: 6},
marks: [
// This rect is faceted by y and repeated across x, and hence all rects in
// a row have the same fill. With rect, the default definitions of x1, x2,
// y1, and y2 will fill the entire frame, similar to Plot.frame.
Plot.rect(numbers, {
fy: numbers,
fill: numbers,
inset: 1
}),
// This dot is faceted by x and repeated across y, and hence all dots in a
// column have the same fill. With dot, the default definitions of x and y
// would assume that the data is a tuple [x, y], so we set the frameAnchor
// to middle to draw one dot in the center of each frame.
Plot.dot(numbers, {
frameAnchor: "middle",
r: 19,
fx: numbers,
fill: numbers,
stroke: "white"
}),
// This text is faceted by x and y, and hence we need the cross product of
// the numbers. Again there is just one text mark per facet.
Plot.text(d3.cross(numbers, numbers), {
frameAnchor: "middle",
text: ([x, y]) => x * y,
fill: "white",
fx: ([x]) => x,
fy: ([, y]) => y
})
]
});
}
28 changes: 28 additions & 0 deletions test/plots/penguin-culmen-mark-facet.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";

export default async function () {
const data = await d3.csv("data/penguins.csv", d3.autoType);
return Plot.plot({
height: 600,
facet: {marginRight: 80},
marks: [
Plot.frame(),
Plot.dot(data, {
fx: "sex",
fy: "species",
facet: "exclude",
x: "culmen_depth_mm",
y: "culmen_length_mm",
r: 2,
fill: "#ddd"
}),
Plot.dot(data, {
fx: "sex",
fy: "species",
x: "culmen_depth_mm",
y: "culmen_length_mm"
})
]
});
}
21 changes: 21 additions & 0 deletions test/plots/penguins-facet-annotated-x.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as Plot from "@observablehq/plot";
import * as d3 from "d3";

export default async function () {
const penguins = await d3.csv("data/penguins.csv", d3.autoType);
return Plot.plot({
marginLeft: 75,
x: {insetRight: 10},
marks: [
Plot.frame(),
Plot.barX(penguins, Plot.groupY({x: "count"}, {fx: "island", y: "species", fill: "sex"})),
Plot.text(["Torgersen Island only has Adelie penguins!"], {
fx: ["Torgersen"],
frameAnchor: "top-right",
dy: 4,
dx: -4,
lineWidth: 10
})
]
});
}
Loading