diff --git a/src/marks/bar.js b/src/marks/bar.js index 57ff15a27c..241de10795 100644 --- a/src/marks/bar.js +++ b/src/marks/bar.js @@ -48,7 +48,11 @@ export class AbstractBar extends Mark { const {rx, ry} = this; const {color} = scales; const {z: Z, title: L, fill: F, stroke: S} = channels; - const index = filter(I, ...this._positions(channels), F, S); + const [X, vx] = maybeCoords(this._x(scales, channels, dimensions), I); + const [Y, vy] = maybeCoords(this._y(scales, channels, dimensions), I); + const [W, vw] = maybeCoords(this._width(scales, channels, dimensions), I); + const [H, vh] = maybeCoords(this._height(scales, channels, dimensions), I); + const index = filter(I, X, Y, W, H, F, S); if (Z) index.sort((i, j) => ascending(Z[i], Z[j])); return create("svg:g") .call(applyIndirectStyles, this) @@ -57,10 +61,10 @@ export class AbstractBar extends Mark { .data(index) .join("rect") .call(applyDirectStyles, this) - .attr("x", this._x(scales, channels, dimensions)) - .attr("width", this._width(scales, channels, dimensions)) - .attr("y", this._y(scales, channels, dimensions)) - .attr("height", this._height(scales, channels, dimensions)) + .attr("x", X ? i => X[i] : vx) + .attr("width", W ? i => W[i] : vw) + .attr("y", Y ? i => Y[i] : vy) + .attr("height", H ? i => H[i] : vh) .attr("fill", F && (i => color(F[i]))) .attr("stroke", S && (i => color(S[i]))) .call(rx != null ? rect => rect.attr("rx", rx) : () => {}) @@ -88,6 +92,17 @@ export class AbstractBar extends Mark { } } +function maybeCoords(x, I) { + if (typeof x === "function") { + const X = []; + for (const i of I) { + X[i] = x(i); + } + return [X, undefined]; + } + return [undefined, x]; +} + export class BarX extends AbstractBar { constructor(data, {x1, x2, y, ...options} = {}) { super( @@ -103,9 +118,6 @@ export class BarX extends AbstractBar { _transform(selection, {x}) { selection.call(applyTransform, x, null); } - _positions({x1: X1, x2: X2, y: Y}) { - return [X1, X2, Y]; - } _x({x}, {x1: X1, x2: X2}) { const {insetLeft} = this; return i => Math.min(x(X1[i]), x(X2[i])) + insetLeft; @@ -131,9 +143,6 @@ export class BarY extends AbstractBar { _transform(selection, {y}) { selection.call(applyTransform, null, y); } - _positions({y1: Y1, y2: Y2, x: X}) { - return [Y1, Y2, X]; - } _y({y}, {y1: Y1, y2: Y2}) { const {insetTop} = this; return i => Math.min(y(Y1[i]), y(Y2[i])) + insetTop; diff --git a/src/plot.js b/src/plot.js index 5ec84d2868..cc6f2bce26 100644 --- a/src/plot.js +++ b/src/plot.js @@ -95,7 +95,7 @@ export function plot(options = {}) { } function Dimensions( - {y, fy}, + {y, fy, fx}, { x: {axis: xAxis} = {}, y: {axis: yAxis} = {}, @@ -104,7 +104,7 @@ function Dimensions( }, { width = 640, - height = y || fy ? 396 : 60, + height = y || fy ? 396 : fx ? 90 : 60, facet: { marginTop: facetMarginTop = fxAxis === "top" ? 30 :0, marginRight: facetMarginRight = fyAxis === "right" ? 40 : 0, diff --git a/src/transforms/group.js b/src/transforms/group.js index 5f89f41ff0..db419d32d0 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -1,5 +1,5 @@ import {group as grouper, sort, sum, InternSet} from "d3"; -import {defined, firstof} from "../defined.js"; +import {firstof} from "../defined.js"; import {valueof, maybeColor, maybeTransform, maybeValue, maybeLazyChannel, lazyChannel, first, identity, take, maybeTuple, labelof} from "../mark.js"; // Group on {z, fill, stroke}. @@ -85,7 +85,7 @@ function group2(xv, yv, {z, fill, stroke, weight, domain, normalize, ...options} for (const facet of facets) { const groupFacet = []; if (normalize === "facet") n = W ? sum(facet, i => W[i]) : facet.length; - for (const [, I] of groups(facet, G, defined1)) { + for (const [, I] of groups(facet, G)) { if (normalize === "z") n = W ? sum(I, i => W[i]) : I.length; for (const [y, fy] of groups(I, Y, ydefined)) { for (const [x, f] of groups(fy, X, xdefined)) { @@ -113,7 +113,7 @@ function group2(xv, yv, {z, fill, stroke, weight, domain, normalize, ...options} } function maybeDomain(domain) { - if (domain === undefined) return defined1; + if (domain === undefined) return () => true; if (domain === null) return () => false; domain = new InternSet(domain); return ([key]) => domain.has(key); @@ -129,10 +129,8 @@ function maybeNormalize(normalize) { throw new Error("invalid normalize"); } -function defined1([key]) { - return defined(key); -} - -export function groups(I, X, defined = defined1) { - return X ? sort(grouper(I, i => X[i]), first).filter(defined) : [[, I]]; +export function groups(I, X, defined) { + if (!X) return [[, I]]; + const G = grouper(I, i => X[i]); + return sort(defined ? Array.from(G).filter(defined) : G, first); } diff --git a/test/output/penguinSpeciesIslandSex.svg b/test/output/penguinSpeciesIslandSex.svg new file mode 100644 index 0000000000..90995efe37 --- /dev/null +++ b/test/output/penguinSpeciesIslandSex.svg @@ -0,0 +1,122 @@ + + + + 0 + + + + 10 + + + + 20 + + + + 30 + + + + 40 + + + + 50 + + + + 60 + + + + 70 + + ↑ Frequency + + + + Adelie + + + Gentoo + + + Chinstrap + species + + + + + + MALE + + + FEMALE + + + N/A + + + + + + + MALE + + + FEMALE + + + N/A + sex + + + + + + MALE + + + FEMALE + + + N/A + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/plots/index.js b/test/plots/index.js index 130cf515bc..e5811620c1 100644 --- a/test/plots/index.js +++ b/test/plots/index.js @@ -53,6 +53,7 @@ export {default as penguinMassSpecies} from "./penguin-mass-species.js"; export {default as penguinSexMassCulmenSpecies} from "./penguin-sex-mass-culmen-species.js"; export {default as penguinSpeciesGroup} from "./penguin-species-group.js"; export {default as penguinSpeciesIsland} from "./penguin-species-island.js"; +export {default as penguinSpeciesIslandSex} from "./penguin-species-island-sex.js"; export {default as policeDeaths} from "./police-deaths.js"; export {default as policeDeathsBar} from "./police-deaths-bar.js"; export {default as randomWalk} from "./random-walk.js"; diff --git a/test/plots/penguin-species-island-sex.js b/test/plots/penguin-species-island-sex.js new file mode 100644 index 0000000000..df409b2cd5 --- /dev/null +++ b/test/plots/penguin-species-island-sex.js @@ -0,0 +1,29 @@ +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({ + facet: { + data, + x: "species" + }, + fx: { + domain: d3.groupSort(data, ({length}) => length, d => d.species).reverse() + }, + x: { + domain: ["MALE", "FEMALE", null], + tickFormat: d => d === null ? "N/A" : d + }, + y: { + grid: true + }, + color: { + scheme: "greys" + }, + marks: [ + Plot.barY(data, Plot.stackY(Plot.groupX({x: "sex", fill: "island", stroke: "black"}))), + Plot.ruleY([0]) + ] + }); +}