diff --git a/src/transforms/group.js b/src/transforms/group.js
index c2da19acc6..32f6cb0049 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}.
@@ -135,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);
@@ -266,6 +270,19 @@ 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(-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");
+ }
+};
+
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])
]
});