Skip to content

Commit d689c74

Browse files
committed
filter by category
1 parent 61b5d18 commit d689c74

File tree

1 file changed

+41
-7
lines changed

1 file changed

+41
-7
lines changed

docs/summary-table.md

Lines changed: 41 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function table(data, options = {}) {
7777
const filters = new Map();
7878

7979
requestAnimationFrame(() => {
80-
for (const s of summaries.filter(({type}) => type)) summary(s);
80+
for (const s of summaries.filter(({type}) => type)) summary(s, filters, refresh);
8181
});
8282

8383
// save table headers for the dirty copy below
@@ -112,14 +112,15 @@ function table(data, options = {}) {
112112
let tmp;
113113
filters.set("search", (i) => textFields.some(({values}) => ((tmp = values.get(i)) && re.test(tmp))));
114114
} catch(error) {
115+
// malformed RegExp: surface the error? or ignore and treat as string?
115116
console.warn(error);
116117
}
117118
}
118119
refresh();
119120
}
120121
}
121122

122-
async function summary(div) {
123+
async function summary(div, filters, refresh) {
123124
const {name, type, values} = d3.select(div).datum();
124125
const {width: w, height} = div.getBoundingClientRect();
125126
const width = Math.min(200, (w ?? 80));
@@ -145,16 +146,24 @@ async function summary(div) {
145146
if (type === "Utf8") {
146147
const stackOptions = {order: "sum", reverse: true};
147148
const counts = new Map();
149+
let nulls = 0;
148150
for (const v of values) {
149-
if (v == null) continue;
151+
if (v == null) {nulls++; continue;}
150152
if (counts.has(v)) counts.set(v, 1 + counts.get(v)); else counts.set(v, 1);
151153
}
152154
const topX = d3.sort(counts, ([, c]) => -c).slice(0, 10);
153-
const visible = new Set(topX.filter(([, c]) => c / count > 0.1).map(([key]) => key));
155+
const visible = new Map(topX.filter(([, c]) => c / count > 0.3));
156+
const others = d3.sum(counts, ([key, c]) => visible.has(key) ? 0 : c);
157+
154158
// TODO:
155159
// - if the “others” group has a single value, use it
156160
// - if a category is already named "Others", use "…" instead
157-
// - separate the nulls
161+
162+
const bars = [...visible];
163+
const Other = {toString() {return "Other"}};
164+
const Null = {toString() {return "null"}};
165+
if (others) bars.push([Other, others]);
166+
if (nulls) bars.push([Null, nulls]);
158167

159168
chart = Plot.plot({
160169
width,
@@ -167,10 +176,34 @@ async function summary(div) {
167176
marginTop: 0,
168177
marginBottom: 13,
169178
marks: [
170-
Plot.barX(values, Plot.stackX(stackOptions, Plot.groupZ({x: "count"}, {z: d => visible.has(d) ? d : "Others", insetRight: 1, fill: "var(--theme-foreground-focus)"}))),
171-
Plot.text(values, Plot.stackX(stackOptions, Plot.groupZ({x: "count", text: "first"}, {text: d => visible.has(d) ? d : "Others", z: d => visible.has(d) ? d : "Others", fill: "var(--plot-background)"}))),
179+
Plot.barX(bars, {x: "1", insetRight: 1, fill: ([x]) => typeof x === "string" ? "var(--theme-foreground-focus)" : "gray"}),
180+
Plot.text(bars, Plot.stackX({text: "0", x: "1", fill: "var(--plot-background)", pointerEvents: "none"})),
172181
]
173182
});
183+
184+
let currentMode;
185+
const buttons = d3.select(chart).selectAll("rect").on("click", function(event) {
186+
const mode = bars[this.__data__][0];
187+
if (filters.has(name) && currentMode === mode) {
188+
filters.delete(name);
189+
currentMode = undefined;
190+
d3.select(this).classed("selected", false);
191+
}
192+
else {
193+
if (mode === Null) {
194+
filters.set(name, (i) => values.get(i) == null);
195+
} else if (mode === Other) {
196+
filters.set(name, (i) => {
197+
const v = values.get(i);
198+
return v != null && !visible.has(v);
199+
});
200+
} else filters.set(name, (i) => values.get(i) === mode);
201+
currentMode = mode;
202+
d3.select(chart).selectAll("rect").classed("selected", false);
203+
d3.select(this).classed("selected", true);
204+
}
205+
refresh();
206+
});
174207
}
175208

176209
// ordinal
@@ -242,5 +275,6 @@ async function summary(div) {
242275
.summary-table table .type {font-size: smaller; font-weight: normal; color: var(--theme-foreground-muted); height: 1.35em;}
243276
.summary-table table .summary {font-size: smaller; font-weight: normal; height: 33px;}
244277
.summary-table footer {font-family: var(--sans-serif); font-size: small; color: var(--theme-foreground-faint)}
278+
.summary-table rect.selected { fill: orange; }
245279
246280
</style>

0 commit comments

Comments
 (0)