diff --git a/docs/features/marks.md b/docs/features/marks.md index 4d9cb90cd4..7e163907e6 100644 --- a/docs/features/marks.md +++ b/docs/features/marks.md @@ -114,7 +114,7 @@ Plot.plot({ ``` ::: -Marks may also be a function which returns an SVG element, if you wish to insert arbitrary content. (Here we use [Hypertext Literal](https://github.com/observablehq/htl) to generate an SVG gradient.) +Marks may also be a function which returns an [SVG element](https://developer.mozilla.org/en-US/docs/Web/SVG/Element), if you wish to insert arbitrary content. (Here we use [Hypertext Literal](https://github.com/observablehq/htl) to generate an SVG gradient.) :::plot defer https://observablehq.com/@observablehq/plot-gradient-bars ```js diff --git a/src/facet.js b/src/facet.js index 5398b344e7..cc3af59ac4 100644 --- a/src/facet.js +++ b/src/facet.js @@ -63,11 +63,16 @@ export function facetGroups(data, {fx, fy}) { } export function facetTranslator(fx, fy, {marginTop, marginLeft}) { - return fx && fy - ? ({x, y}) => `translate(${fx(x) - marginLeft},${fy(y) - marginTop})` - : fx - ? ({x}) => `translate(${fx(x) - marginLeft},0)` - : ({y}) => `translate(0,${fy(y) - marginTop})`; + const x = fx ? ({x}) => fx(x) - marginLeft : () => 0; + const y = fy ? ({y}) => fy(y) - marginTop : () => 0; + return function (d) { + if (this.tagName === "svg") { + this.setAttribute("x", x(d)); + this.setAttribute("y", y(d)); + } else { + this.setAttribute("transform", `translate(${x(d)},${y(d)})`); + } + }; } // Returns an index that for each facet lists all the elements present in other diff --git a/src/plot.js b/src/plot.js index a7383a66c7..829c4624a4 100644 --- a/src/plot.js +++ b/src/plot.js @@ -317,7 +317,7 @@ export function plot(options = {}) { } } } - g?.selectChildren().attr("transform", facetTranslate); + g?.selectChildren().each(facetTranslate); } } diff --git a/test/output/nestedFacets.html b/test/output/nestedFacets.html new file mode 100644 index 0000000000..78280a3cce --- /dev/null +++ b/test/output/nestedFacets.html @@ -0,0 +1,1008 @@ +
+ + + + + + + + + + IF + + + + SI1 + + + + I1 + + + clarity + + + + + + 52 + 54 + 56 + 58 + 60 + 62 + 64 + 66 + 68 + 70 + + + + depth → + + + + D + + + E + + + F + + + + color + + + + + + + + + + + Fair + + + Good + + + Ideal + + + Premium + + + Very Good + + + + cut + + + + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + + clarity + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fair + + + Good + + + Ideal + + + Premium + + + Very Good + + + + cut + + + + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Fair + + + Good + + + Ideal + + + Premium + + + Very Good + + + + cut + + + + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + IF + SI1 + I1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/test/plots/index.ts b/test/plots/index.ts index b13d1ec45b..3176991d75 100644 --- a/test/plots/index.ts +++ b/test/plots/index.ts @@ -184,6 +184,7 @@ export * from "./movies-profit-by-genre.js"; export * from "./movies-rating-by-genre.js"; export * from "./multiplication-table.js"; export * from "./music-revenue.js"; +export * from "./nested-facets.js"; export * from "./npm-versions.js"; export * from "./opacity.js"; export * from "./ordinal-bar.js"; diff --git a/test/plots/nested-facets.ts b/test/plots/nested-facets.ts new file mode 100644 index 0000000000..fa952a4464 --- /dev/null +++ b/test/plots/nested-facets.ts @@ -0,0 +1,53 @@ +import * as Plot from "@observablehq/plot"; +import * as d3 from "d3"; + +export async function nestedFacets() { + const diamonds = await d3.csv("data/diamonds.csv", d3.autoType); + return Plot.plot({ + width: 960, + height: 480, + fx: {domain: ["D", "E", "F"]}, + color: {legend: "ramp", domain: ["IF", "SI1", "I1"]}, + y: {domain: [51, 71.9], insetTop: 20, labelAnchor: "center"}, + marginLeft: 40, + marginBottom: 40, + marginTop: 35, + marks: [ + Plot.axisFx({anchor: "top"}), + Plot.frame({anchor: "top", strokeOpacity: 1}), + Plot.dot(diamonds, { + fx: "color", // outer x facet + y: "depth", // shared y scale + fill: "clarity", // shared color scale + render(index, {scales}, _values, {facet, ...dimensions}) { + const data = Array.from(index, (i) => this.data[i]); // subplot dataset as a subset of the data + return Plot.plot({ + ...dimensions, + marginTop: 60, + ...scales, // shared color scale, shared y scale + fx: {axis: "bottom", paddingOuter: 0.1, paddingInner: 0.2}, // inner x facet + x: { + domain: scales.color.domain, + axis: "top", + labelAnchor: "left", + labelOffset: 16, + ...(index["fi"] && {label: null}), + grid: true, + tickSize: 0 + }, // new x scale with a common domain and additional axis options + y: {...scales.y, grid: 4, axis: null}, // shared y scale with additional options + marks: [ + Plot.frame({anchor: "bottom"}), + Plot.boxY(data, { + fx: "cut", + x: "clarity", + y: "depth", + fill: "clarity" + }) + ] + }) as SVGElement; + } + }) + ] + }); +}