@@ -77,7 +77,7 @@ function table(data, options = {}) {
77
77
const filters = new Map ();
78
78
79
79
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 );
81
81
});
82
82
83
83
// save table headers for the dirty copy below
@@ -112,14 +112,15 @@ function table(data, options = {}) {
112
112
let tmp;
113
113
filters .set (" search" , (i ) => textFields .some (({values}) => ((tmp = values .get (i)) && re .test (tmp))));
114
114
} catch (error) {
115
+ // malformed RegExp: surface the error? or ignore and treat as string?
115
116
console .warn (error);
116
117
}
117
118
}
118
119
refresh ();
119
120
}
120
121
}
121
122
122
- async function summary (div ) {
123
+ async function summary (div , filters , refresh ) {
123
124
const {name , type , values } = d3 .select (div).datum ();
124
125
const {width: w , height } = div .getBoundingClientRect ();
125
126
const width = Math .min (200 , (w ?? 80 ));
@@ -145,16 +146,24 @@ async function summary(div) {
145
146
if (type === " Utf8" ) {
146
147
const stackOptions = {order: " sum" , reverse: true };
147
148
const counts = new Map ();
149
+ let nulls = 0 ;
148
150
for (const v of values) {
149
- if (v == null ) continue ;
151
+ if (v == null ) {nulls ++ ; continue ;}
150
152
if (counts .has (v)) counts .set (v, 1 + counts .get (v)); else counts .set (v, 1 );
151
153
}
152
154
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
+
154
158
// TODO:
155
159
// - if the “others” group has a single value, use it
156
160
// - 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]);
158
167
159
168
chart = Plot .plot ({
160
169
width,
@@ -167,10 +176,34 @@ async function summary(div) {
167
176
marginTop: 0 ,
168
177
marginBottom: 13 ,
169
178
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 " } )),
172
181
]
173
182
});
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
+ });
174
207
}
175
208
176
209
// ordinal
@@ -242,5 +275,6 @@ async function summary(div) {
242
275
.summary-table table .type {font-size: smaller; font-weight: normal; color: var(--theme-foreground-muted); height: 1.35em;}
243
276
.summary-table table .summary {font-size: smaller; font-weight: normal; height: 33px;}
244
277
.summary-table footer {font-family: var(--sans-serif); font-size: small; color: var(--theme-foreground-faint)}
278
+ .summary-table rect.selected { fill: orange; }
245
279
246
280
</style>
0 commit comments