From 445a4a117372285b96644d8e30640e93707377b3 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sun, 16 Jan 2022 10:56:00 -0800 Subject: [PATCH 1/4] propagate title and href by default --- src/transforms/bin.js | 6 ++- src/transforms/group.js | 20 ++++++-- test/output/penguinMassSpecies.svg | 77 +++++++++++++++++++++++------- test/plots/penguin-mass-species.js | 2 +- 4 files changed, 84 insertions(+), 21 deletions(-) diff --git a/src/transforms/bin.js b/src/transforms/bin.js index 77e7eda1d8..a08059e649 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -2,7 +2,7 @@ import {bin as binner, extent, thresholdFreedmanDiaconis, thresholdScott, thresh import {valueof, range, identity, maybeLazyChannel, maybeTuple, maybeColorChannel, maybeValue, mid, labelof, isTemporal} from "../options.js"; import {coerceDate} from "../scales.js"; import {basic} from "./basic.js"; -import {hasOutput, maybeEvaluator, maybeGroup, maybeOutput, maybeOutputs, maybeReduce, maybeSort, maybeSubgroup, reduceCount, reduceIdentity} from "./group.js"; +import {hasOutput, maybeEvaluator, maybeGroup, maybeOutput, maybeOutputs, maybeReduce, maybeSort, maybeSubgroup, reduceCount, reduceFirst, reduceIdentity, reduceTitle} from "./group.js"; import {maybeInsetX, maybeInsetY} from "./inset.js"; // Group on {z, fill, stroke}, then optionally on y, then bin x. @@ -43,6 +43,10 @@ function binn( bx = maybeBin(bx); by = maybeBin(by); + // Propagate standard mark channels by default. + if (inputs.title != null && outputs.title === undefined) outputs.title = reduceTitle; + if (inputs.href != null && outputs.href === undefined) outputs.href = reduceFirst; + // Compute the outputs. outputs = maybeOutputs(outputs, inputs); reduceData = maybeReduce(reduceData, identity); diff --git a/src/transforms/group.js b/src/transforms/group.js index c2da19acc6..ba7cfdf325 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -1,6 +1,6 @@ -import {group as grouper, sort, sum, deviation, min, max, mean, median, mode, variance, InternSet, minIndex, maxIndex} from "d3"; +import {group as grouper, sort, sum, deviation, min, max, mean, median, mode, variance, InternSet, minIndex, maxIndex, rollup} from "d3"; import {ascendingDefined, firstof} from "../defined.js"; -import {valueof, maybeColorChannel, maybeInput, maybeTuple, maybeLazyChannel, lazyChannel, first, identity, take, labelof, range} from "../options.js"; +import {valueof, maybeColorChannel, maybeInput, maybeTuple, maybeLazyChannel, lazyChannel, first, identity, take, labelof, range, second} from "../options.js"; import {basic} from "./basic.js"; // Group on {z, fill, stroke}. @@ -44,6 +44,10 @@ function groupn( inputs = {} // input channels and options ) { + // Propagate standard mark channels by default. + if (inputs.title != null && outputs.title === undefined) outputs.title = reduceTitle; + if (inputs.href != null && outputs.href === undefined) outputs.href = reduceFirst; + // Compute the outputs. outputs = maybeOutputs(outputs, inputs); reduceData = maybeReduce(reduceData, identity); @@ -260,12 +264,22 @@ export const reduceIdentity = { } }; -const reduceFirst = { +export const reduceFirst = { reduce(I, X) { return X[I[0]]; } }; +export const reduceTitle = { + reduce(I, X) { + return sort(rollup(I, V => V.length, i => X[i]), second) + .reverse() + .slice(0, 5) + .map(([key, value]) => `${key} (${value.toLocaleString("en-US")})`) + .join("\n"); + } +}; + const reduceLast = { reduce(I, X) { return X[I[I.length - 1]]; diff --git a/test/output/penguinMassSpecies.svg b/test/output/penguinMassSpecies.svg index 997f981cf2..5c84da473e 100644 --- a/test/output/penguinMassSpecies.svg +++ b/test/output/penguinMassSpecies.svg @@ -84,22 +84,67 @@ Body mass (g) → - - - - - - - - - - - - - - - - + + Adelie FEMALE (6) + Adelie null (1) + + + Adelie FEMALE (42) + Adelie MALE (3) + Adelie null (2) + + + Adelie MALE (32) + Adelie FEMALE (25) + Adelie null (1) + + + Adelie MALE (30) + Adelie null (1) + + + Adelie MALE (8) + + + Chinstrap FEMALE (2) + + + Chinstrap FEMALE (11) + Chinstrap MALE (4) + + + Chinstrap FEMALE (20) + Chinstrap MALE (15) + + + Chinstrap MALE (12) + Chinstrap FEMALE (1) + + + Chinstrap MALE (3) + + + Gentoo FEMALE (1) + + + Gentoo FEMALE (14) + Gentoo null (1) + + + Gentoo FEMALE (35) + Gentoo null (3) + Gentoo MALE (2) + + + Gentoo MALE (26) + Gentoo FEMALE (8) + + + Gentoo MALE (29) + + + Gentoo MALE (4) + diff --git a/test/plots/penguin-mass-species.js b/test/plots/penguin-mass-species.js index e2089acfa2..375e3037c9 100644 --- a/test/plots/penguin-mass-species.js +++ b/test/plots/penguin-mass-species.js @@ -12,7 +12,7 @@ export default async function() { grid: true }, marks: [ - Plot.rectY(data, Plot.binX({y: "count"}, {x: "body_mass_g", fill: "species"})), + Plot.rectY(data, Plot.binX({y: "count"}, {x: "body_mass_g", fill: "species", title: d => `${d.species} ${d.sex}`})), Plot.ruleY([0]) ] }); From c26584064bc1ff62ae40bf5a2bf3e2d802ee242b Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sun, 16 Jan 2022 11:04:26 -0800 Subject: [PATCH 2/4] DRY --- src/transforms/bin.js | 6 +----- src/transforms/group.js | 14 +++++++------- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/transforms/bin.js b/src/transforms/bin.js index a08059e649..77e7eda1d8 100644 --- a/src/transforms/bin.js +++ b/src/transforms/bin.js @@ -2,7 +2,7 @@ import {bin as binner, extent, thresholdFreedmanDiaconis, thresholdScott, thresh import {valueof, range, identity, maybeLazyChannel, maybeTuple, maybeColorChannel, maybeValue, mid, labelof, isTemporal} from "../options.js"; import {coerceDate} from "../scales.js"; import {basic} from "./basic.js"; -import {hasOutput, maybeEvaluator, maybeGroup, maybeOutput, maybeOutputs, maybeReduce, maybeSort, maybeSubgroup, reduceCount, reduceFirst, reduceIdentity, reduceTitle} from "./group.js"; +import {hasOutput, maybeEvaluator, maybeGroup, maybeOutput, maybeOutputs, maybeReduce, maybeSort, maybeSubgroup, reduceCount, reduceIdentity} from "./group.js"; import {maybeInsetX, maybeInsetY} from "./inset.js"; // Group on {z, fill, stroke}, then optionally on y, then bin x. @@ -43,10 +43,6 @@ function binn( bx = maybeBin(bx); by = maybeBin(by); - // Propagate standard mark channels by default. - if (inputs.title != null && outputs.title === undefined) outputs.title = reduceTitle; - if (inputs.href != null && outputs.href === undefined) outputs.href = reduceFirst; - // Compute the outputs. outputs = maybeOutputs(outputs, inputs); reduceData = maybeReduce(reduceData, identity); diff --git a/src/transforms/group.js b/src/transforms/group.js index ba7cfdf325..1600126813 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -44,10 +44,6 @@ function groupn( inputs = {} // input channels and options ) { - // Propagate standard mark channels by default. - if (inputs.title != null && outputs.title === undefined) outputs.title = reduceTitle; - if (inputs.href != null && outputs.href === undefined) outputs.href = reduceFirst; - // Compute the outputs. outputs = maybeOutputs(outputs, inputs); reduceData = maybeReduce(reduceData, identity); @@ -139,7 +135,11 @@ export function hasOutput(outputs, ...names) { } export function maybeOutputs(outputs, inputs) { - return Object.entries(outputs).map(([name, reduce]) => { + const entries = Object.entries(outputs); + // Propagate standard mark channels by default. + if (inputs.title != null && outputs.title === undefined) entries.push(["title", reduceTitle]); + if (inputs.href != null && outputs.href === undefined) entries.push(["href", reduceFirst]); + return entries.map(([name, reduce]) => { return reduce == null ? {name, initialize() {}, scope() {}, reduce() {}} : maybeOutput(name, reduce, inputs); @@ -264,13 +264,13 @@ export const reduceIdentity = { } }; -export const reduceFirst = { +const reduceFirst = { reduce(I, X) { return X[I[0]]; } }; -export const reduceTitle = { +const reduceTitle = { reduce(I, X) { return sort(rollup(I, V => V.length, i => X[i]), second) .reverse() From 95aacb3fcc5ee6e294faec634f38cc002b9e6d4d Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Sun, 16 Jan 2022 20:46:28 -0800 Subject: [PATCH 3/4] count other --- src/transforms/group.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/transforms/group.js b/src/transforms/group.js index 1600126813..bc8db41b38 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -272,11 +272,10 @@ const reduceFirst = { const reduceTitle = { reduce(I, X) { - return sort(rollup(I, V => V.length, i => X[i]), second) - .reverse() - .slice(0, 5) - .map(([key, value]) => `${key} (${value.toLocaleString("en-US")})`) - .join("\n"); + const groups = sort(rollup(I, V => V.length, i => X[i]), second); + const top = groups.slice(-5).reverse(); + if (top.length < groups.length) top.push(["Other", sum(groups.slice(0, -5), second)]); + return top.map(([key, value]) => `${key} (${value.toLocaleString("en-US")})`).join("\n"); } }; From 4632740790ddc9c0a1db665e25a541f069845892 Mon Sep 17 00:00:00 2001 From: Mike Bostock Date: Tue, 18 Jan 2022 07:48:28 -0800 Subject: [PATCH 4/4] better other summarization --- src/transforms/group.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/transforms/group.js b/src/transforms/group.js index bc8db41b38..32f6cb0049 100644 --- a/src/transforms/group.js +++ b/src/transforms/group.js @@ -272,9 +272,13 @@ const reduceFirst = { const reduceTitle = { reduce(I, X) { + const n = 5; const groups = sort(rollup(I, V => V.length, i => X[i]), second); - const top = groups.slice(-5).reverse(); - if (top.length < groups.length) top.push(["Other", sum(groups.slice(0, -5), second)]); + const top = groups.slice(-n).reverse(); + if (top.length < groups.length) { + const bottom = groups.slice(0, 1 - n); + top[n - 1] = [`… ${bottom.length.toLocaleString("en-US")} more`, sum(bottom, second)]; + } return top.map(([key, value]) => `${key} (${value.toLocaleString("en-US")})`).join("\n"); } };