@@ -12,6 +12,26 @@ SELECT * FROM gaia;
12
12
SELECT * FROM penguins;
13
13
```
14
14
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
+
15
35
``` js echo
16
36
const sql = DuckDBClient .sql ({aapl, penguins, gaia: FileAttachment (" /lib/gaia-sample.parquet" )});
17
37
```
@@ -36,7 +56,7 @@ function table(data, options = {}) {
36
56
if (! Array .isArray (data? .schema ? .fields )) return container;
37
57
38
58
// 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) }));
40
60
41
61
const th = d3 .select (container).select (" thead" ).selectAll (" th" ).data ([{}, ... fields]);
42
62
th .append (" div" ).classed (" type" , true ).html (({type}) => type);
@@ -47,61 +67,84 @@ function table(data, options = {}) {
47
67
</footer >` ;
48
68
container .appendChild (footer);
49
69
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));
56
71
return container;
57
72
}
58
73
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 );
60
77
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" };
62
95
chart = Plot .plot ({
63
96
width,
64
97
height,
65
98
style: " overflow: visible;" ,
66
- x: {round : true },
99
+ x: {axis : null },
67
100
y: {axis: null },
68
101
marginLeft: 2 ,
69
102
marginRight: 2 ,
70
103
marginTop: 0 ,
71
104
marginBottom: 13 ,
72
105
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) " })) ),
75
108
]
76
109
});
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
- }
86
110
}
87
- else if (type === " Utf8" ) {
111
+ else {
112
+ const thresholds = Math .max (10 , Math .min (50 , d3 .thresholdScott (values, min, max))); // TODO optimize thresholdScott
88
113
chart = Plot .plot ({
89
114
width,
90
115
height,
91
116
style: " overflow: visible;" ,
92
- x: {axis: null },
117
+ x: {
118
+ round: true ,
119
+ nice: true
120
+ },
93
121
y: {axis: null },
94
- marginLeft: 2 ,
95
- marginRight: 2 ,
122
+ marginLeft: 9 ,
123
+ marginRight: 9 ,
96
124
marginTop: 0 ,
97
125
marginBottom: 13 ,
98
126
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" })}),
101
141
]
102
142
});
103
143
}
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} ` ;
105
148
}
106
149
` ` `
107
150
0 commit comments