Skip to content

Commit 217acd7

Browse files
committed
more examples, better presentation
1 parent be8440b commit 217acd7

File tree

1 file changed

+71
-28
lines changed

1 file changed

+71
-28
lines changed

docs/summary-table.md

Lines changed: 71 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,26 @@ SELECT * FROM gaia;
1212
SELECT * FROM penguins;
1313
```
1414

15+
```sql echo
16+
SELECT a::int
17+
, b::int
18+
, (a * b)::int as p
19+
FROM generate_series(1, 9) as s(a)
20+
, generate_series(1, 9) as t(b);
21+
```
22+
23+
```sql echo
24+
FROM range(10, 22, 2);
25+
```
26+
27+
```sql echo
28+
SELECT range * pi() as pi FROM range(10);
29+
```
30+
31+
```sql echo
32+
SELECT cos(range * pi() / 10) as x, sin(range * pi() / 10) as y FROM range(0, 20, 1);
33+
```
34+
1535
```js echo
1636
const sql = DuckDBClient.sql({aapl, penguins, gaia: FileAttachment("/lib/gaia-sample.parquet")});
1737
```
@@ -36,7 +56,7 @@ function table(data, options = {}) {
3656
if (!Array.isArray(data?.schema?.fields)) return container;
3757

3858
// Get the fields as described by Arrow, in the order given (potentially) by the options.
39-
const fields = (options.columns?.map(k => data.schema.find(({name}) => name === k)) ?? data.schema.fields).map(({name, type}) => ({name: String(name), type: String(type)}));
59+
const fields = (options.columns?.map(k => data.schema.find(({name}) => name === k)) ?? data.schema.fields).map(({name, type}) => ({name: String(name), type: String(type), values: data.getChild(name)}));
4060

4161
const th = d3.select(container).select("thead").selectAll("th").data([{}, ...fields]);
4262
th.append("div").classed("type", true).html(({type}) => type);
@@ -47,61 +67,84 @@ function table(data, options = {}) {
4767
</footer>`;
4868
container.appendChild(footer);
4969

50-
requestAnimationFrame(() => summaries
51-
.filter(({type}) => type)
52-
.append(function({name, type}) {
53-
return summary(data.getChild(name), type, this.getBoundingClientRect());
54-
})
55-
);
70+
requestAnimationFrame(() => summaries.filter(({type}) => type).append(summary));
5671
return container;
5772
}
5873

59-
function summary(values, type, {width = 80, height = 33}) {
74+
function summary({name, type, values}) {
75+
const {width: w, height} = this.getBoundingClientRect();
76+
const width = Math.min(200, (w ?? 80) - 10);
6077
let chart;
61-
if (type.startsWith("Float") || type.startsWith("Date")) {
78+
79+
// Count values, NaN, nulls, distinct
80+
// TODO use DuckdB?
81+
let max = -Infinity;
82+
let min = Infinity;
83+
let nulls = 0;
84+
const distinct = new Set();
85+
const capped = 100; // max number of distinct values to count
86+
for (const v of values) {
87+
if (v == null) {nulls++; continue;}
88+
if (min > v) min = v; // note this works for strings too
89+
if (max < v) max = v;
90+
if (distinct.size <= capped && !distinct.has(v)) distinct.add(v);
91+
}
92+
93+
if (distinct.size <= 10 || type === "Utf8") {
94+
const stackOptions = (type === "Utf8") ? {order: "sum", reverse: true} : {order: "value"};
6295
chart = Plot.plot({
6396
width,
6497
height,
6598
style: "overflow: visible;",
66-
x: {round: true},
99+
x: {axis: null},
67100
y: {axis: null},
68101
marginLeft: 2,
69102
marginRight: 2,
70103
marginTop: 0,
71104
marginBottom: 13,
72105
marks: [
73-
Plot.rectY(values, Plot.binX(undefined, {fill: "var(--theme-foreground-focus)", inset: -.3})),
74-
Plot.axisX({tickSpacing: 41, tickSize: 3, tickPadding: 2, fontSize: 8}),
106+
Plot.barX(values, Plot.stackX(stackOptions, Plot.groupZ({x: "count"}, {z: Plot.identity, insetRight: 1, fill: "var(--theme-foreground-focus)"}))),
107+
Plot.text(values, Plot.stackX(stackOptions, Plot.groupZ({x: "count", text: "first"}, {z: Plot.identity, fill: "var(--plot-background)"}))),
75108
]
76109
});
77-
78-
// restore insets where possible
79-
const rects = chart.querySelectorAll("rect");
80-
if (rects.length < 100) {
81-
for (const rect of rects) {
82-
rect.setAttribute("x", Math.floor(rect.getAttribute("x")));
83-
rect.setAttribute("width", Math.max(1, Math.floor(rect.getAttribute("width")) - 1));
84-
}
85-
}
86110
}
87-
else if (type === "Utf8") {
111+
else {
112+
const thresholds = Math.max(10, Math.min(50, d3.thresholdScott(values, min, max))); // TODO optimize thresholdScott
88113
chart = Plot.plot({
89114
width,
90115
height,
91116
style: "overflow: visible;",
92-
x: {axis: null},
117+
x: {
118+
round: true,
119+
nice: true
120+
},
93121
y: {axis: null},
94-
marginLeft: 2,
95-
marginRight: 2,
122+
marginLeft: 9,
123+
marginRight: 9,
96124
marginTop: 0,
97125
marginBottom: 13,
98126
marks: [
99-
Plot.barX(values, Plot.groupZ({x: "count"}, {z: Plot.identity, insetRight: 1, fill: "var(--theme-foreground-focus)"})),
100-
Plot.text(values, Plot.stackX(Plot.groupZ({x: "count", text: "first"}, {z: Plot.identity, fill: "var(--plot-background)"}))),
127+
thresholds > 20 ?
128+
Plot.areaY(values, Plot.binX(undefined, {
129+
fill: "var(--theme-foreground-focus)",
130+
thresholds
131+
})) :
132+
Plot.rectY(values, Plot.binX(undefined, {
133+
fill: "var(--theme-foreground-focus)",
134+
thresholds,
135+
inset: 0,
136+
insetRight: 1,
137+
})),
138+
min * max <= 0 ? Plot.ruleX([0]) : [],
139+
Plot.ruleY([0]),
140+
Plot.axisX({tickSpacing: 41, tickSize: 3, tickPadding: 2, fontSize: 8, ...(!type.startsWith("Date") && Math.max(Math.abs(min), Math.abs(max)) >= 1e5 && {tickFormat: "s"})}),
101141
]
102142
});
103143
}
104-
return chart ?? html`<span>Unknown type ${type}`;
144+
return chart ? html`<div style=${type === "Utf8" ? "" : {
145+
position: "absolute",
146+
right: 0
147+
}}>${chart}` : html`<span>Unknown type ${type}`;
105148
}
106149
```
107150

0 commit comments

Comments
 (0)