@quantumwake/terminal-ux-dashboard-components 0.1.18 → 0.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -11
- package/dist/index.cjs +524 -27
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -8
- package/dist/index.d.ts +106 -8
- package/dist/index.js +516 -30
- package/dist/index.js.map +1 -1
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { createContext, useContext, useMemo, useState, useEffect, useRef, useCallback, Suspense } from 'react';
|
|
1
|
+
import { createContext, useContext, useMemo, useId, useState, useEffect, useRef, useCallback, Suspense } from 'react';
|
|
2
2
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
3
|
+
import { interpolateRdYlGn } from 'd3-scale-chromatic';
|
|
3
4
|
import { ResponsiveBar } from '@nivo/bar';
|
|
4
5
|
import { ResponsivePie } from '@nivo/pie';
|
|
5
6
|
import { ResponsiveLine } from '@nivo/line';
|
|
@@ -8,7 +9,7 @@ import { ResponsiveHeatMap } from '@nivo/heatmap';
|
|
|
8
9
|
import PivotTableUI from 'react-pivottable/PivotTableUI';
|
|
9
10
|
import 'react-pivottable/pivottable.css';
|
|
10
11
|
import { TerminalSlider, TerminalInput, TerminalToggle, TerminalSelect } from '@quantumwake/terminal-ux-components';
|
|
11
|
-
import { Loader2, Play, ChevronDown, AlertCircle, Rows3, BarChart3, PieChart, TrendingUp, ScatterChart, LayoutGrid, Code, Sparkles, Terminal, RefreshCw, Minimize2, Maximize2, Plus, Save, FolderOpen, Trash2, X, Filter, Database, Send, GripVertical } from 'lucide-react';
|
|
12
|
+
import { Loader2, Play, ChevronDown, AlertCircle, Rows3, BarChart3, PieChart, TrendingUp, ScatterChart, LayoutGrid, Grid3x3, Code, Sparkles, Terminal, RefreshCw, Minimize2, Maximize2, Plus, Save, FolderOpen, Trash2, X, Filter, Database, Send, GripVertical } from 'lucide-react';
|
|
12
13
|
import CodeMirror, { EditorView, Prec, keymap } from '@uiw/react-codemirror';
|
|
13
14
|
import { sql, PostgreSQL, schemaCompletionSource, keywordCompletionSource } from '@codemirror/lang-sql';
|
|
14
15
|
import { snippetCompletion, completeFromList, autocompletion, acceptCompletion } from '@codemirror/autocomplete';
|
|
@@ -75,6 +76,8 @@ var aggExpr = (agg, col) => {
|
|
|
75
76
|
return `MIN(${qNum(col)})`;
|
|
76
77
|
case "max":
|
|
77
78
|
return `MAX(${qNum(col)})`;
|
|
79
|
+
case "median":
|
|
80
|
+
return `MEDIAN(${qNum(col)})`;
|
|
78
81
|
default:
|
|
79
82
|
return "COUNT(*)";
|
|
80
83
|
}
|
|
@@ -112,7 +115,10 @@ var buildChartSQL = ({
|
|
|
112
115
|
xFields = [],
|
|
113
116
|
yFields = [],
|
|
114
117
|
valueField = null,
|
|
115
|
-
filters = []
|
|
118
|
+
filters = [],
|
|
119
|
+
labelStat,
|
|
120
|
+
colorStat,
|
|
121
|
+
blockField
|
|
116
122
|
}) => {
|
|
117
123
|
const where = compileWhere(filters);
|
|
118
124
|
const x = xFields[0]?.name;
|
|
@@ -149,6 +155,14 @@ var buildChartSQL = ({
|
|
|
149
155
|
const metric = valueField ? aggExpr(valueField.agg || "count", valueField.name) : "COUNT(*)";
|
|
150
156
|
return `SELECT ${qIdent(x)} AS r, ${qIdent(y)} AS c, ${metric} AS v FROM ${TABLE}${where} GROUP BY 1, 2`;
|
|
151
157
|
}
|
|
158
|
+
case "heatmap-plus": {
|
|
159
|
+
if (!x || !y) return null;
|
|
160
|
+
const col = valueField?.name;
|
|
161
|
+
const lv = col ? aggExpr(labelStat || valueField?.agg || "count", col) : "COUNT(*)";
|
|
162
|
+
const cv = col ? aggExpr(colorStat || labelStat || valueField?.agg || "count", col) : "COUNT(*)";
|
|
163
|
+
const blockSel = blockField ? `, ANY_VALUE(${qIdent(blockField)}) AS block` : "";
|
|
164
|
+
return `SELECT ${qIdent(x)} AS r, ${qIdent(y)} AS c, ${lv} AS lv, ${cv} AS cv${blockSel}, GROUPING(${qIdent(x)}) AS gr, GROUPING(${qIdent(y)}) AS gc FROM ${TABLE}${where} GROUP BY GROUPING SETS ((${qIdent(x)}, ${qIdent(y)}), (${qIdent(x)}), (${qIdent(y)}), ())`;
|
|
165
|
+
}
|
|
152
166
|
default:
|
|
153
167
|
return null;
|
|
154
168
|
}
|
|
@@ -197,6 +211,17 @@ var shapeChartData = (chartType, rows, { yFields = [] } = {}) => {
|
|
|
197
211
|
data: colKeys.map((ck) => ({ x: ck, y: cell[rk][ck] || 0 }))
|
|
198
212
|
}));
|
|
199
213
|
}
|
|
214
|
+
case "heatmap-plus": {
|
|
215
|
+
return rows.map((r) => ({
|
|
216
|
+
r: r.r == null ? null : String(r.r),
|
|
217
|
+
c: r.c == null ? null : String(r.c),
|
|
218
|
+
lv: Number(r.lv) || 0,
|
|
219
|
+
cv: Number(r.cv) || 0,
|
|
220
|
+
gr: Number(r.gr) || 0,
|
|
221
|
+
gc: Number(r.gc) || 0,
|
|
222
|
+
...r.block != null ? { block: String(r.block) } : {}
|
|
223
|
+
}));
|
|
224
|
+
}
|
|
200
225
|
default:
|
|
201
226
|
return [];
|
|
202
227
|
}
|
|
@@ -243,8 +268,54 @@ var DEFAULT_CHART_STYLE = {
|
|
|
243
268
|
titleAlign: "left",
|
|
244
269
|
titleBold: false,
|
|
245
270
|
titleBackground: "",
|
|
246
|
-
titleColor: ""
|
|
271
|
+
titleColor: "",
|
|
272
|
+
// Chart frame + series color model.
|
|
273
|
+
height: 0,
|
|
274
|
+
margin: null,
|
|
275
|
+
maxXTicks: 0,
|
|
276
|
+
seriesColors: []
|
|
277
|
+
};
|
|
278
|
+
var DEFAULT_SERIES_COLORS = [
|
|
279
|
+
"#3987e5",
|
|
280
|
+
// blue
|
|
281
|
+
"#d95926",
|
|
282
|
+
// orange
|
|
283
|
+
"#199e70",
|
|
284
|
+
// aqua
|
|
285
|
+
"#c98500",
|
|
286
|
+
// yellow
|
|
287
|
+
"#d55181",
|
|
288
|
+
// magenta
|
|
289
|
+
"#008300",
|
|
290
|
+
// green
|
|
291
|
+
"#9085e9",
|
|
292
|
+
// violet
|
|
293
|
+
"#e66767"
|
|
294
|
+
// red
|
|
295
|
+
];
|
|
296
|
+
var SERIES_OVERFLOW_COLOR = "#707078";
|
|
297
|
+
var seriesColor = (i, style) => {
|
|
298
|
+
const s = withStyleDefaults(style);
|
|
299
|
+
const palette = s.seriesColors.length ? s.seriesColors : DEFAULT_SERIES_COLORS;
|
|
300
|
+
return i < palette.length ? palette[i] : SERIES_OVERFLOW_COLOR;
|
|
301
|
+
};
|
|
302
|
+
var chartSizing = (style, defaultMargin) => {
|
|
303
|
+
const s = withStyleDefaults(style);
|
|
304
|
+
return {
|
|
305
|
+
frameClass: s.height > 0 ? "w-full" : "h-full w-full min-h-[160px]",
|
|
306
|
+
frameStyle: s.height > 0 ? { height: s.height } : {},
|
|
307
|
+
margin: { ...defaultMargin, ...s.margin || {} }
|
|
308
|
+
};
|
|
247
309
|
};
|
|
310
|
+
function thinTicks(values, max) {
|
|
311
|
+
if (max <= 0 || values.length <= max) return void 0;
|
|
312
|
+
if (max === 1) return [values[values.length - 1]];
|
|
313
|
+
const picked = [];
|
|
314
|
+
for (let i = 0; i < max; i++) {
|
|
315
|
+
picked.push(values[Math.round(i * (values.length - 1) / (max - 1))]);
|
|
316
|
+
}
|
|
317
|
+
return [...new Set(picked)];
|
|
318
|
+
}
|
|
248
319
|
var LEGEND_ANCHORS = ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"];
|
|
249
320
|
var withStyleDefaults = (style) => ({
|
|
250
321
|
...DEFAULT_CHART_STYLE,
|
|
@@ -342,10 +413,73 @@ var aggregate = (records, column, fn) => {
|
|
|
342
413
|
return Math.min(...values.map(Number));
|
|
343
414
|
case "max":
|
|
344
415
|
return Math.max(...values.map(Number));
|
|
416
|
+
case "median": {
|
|
417
|
+
const nums = values.map(Number).filter((v) => !Number.isNaN(v)).sort((a, b) => a - b);
|
|
418
|
+
if (!nums.length) return 0;
|
|
419
|
+
const mid = Math.floor(nums.length / 2);
|
|
420
|
+
return nums.length % 2 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
|
|
421
|
+
}
|
|
345
422
|
default:
|
|
346
423
|
return records.length;
|
|
347
424
|
}
|
|
348
425
|
};
|
|
426
|
+
var HEAT_STATS = ["count", "sum", "avg", "min", "max", "median"];
|
|
427
|
+
var cellKey = (row, col) => `${row}\0${col}`;
|
|
428
|
+
var normalizePartition = (cells, method, vmin, vmax) => {
|
|
429
|
+
const out = /* @__PURE__ */ new Map();
|
|
430
|
+
const values = cells.map((c) => c.colorValue).filter((v) => Number.isFinite(v));
|
|
431
|
+
if (!values.length) return out;
|
|
432
|
+
if (method === "rank") {
|
|
433
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
434
|
+
for (const c of cells) {
|
|
435
|
+
const below = sorted.findIndex((v) => v >= c.colorValue);
|
|
436
|
+
const ties = sorted.filter((v) => v === c.colorValue).length;
|
|
437
|
+
out.set(cellKey(c.row, c.col), (below + 0.5 * ties) / sorted.length);
|
|
438
|
+
}
|
|
439
|
+
return out;
|
|
440
|
+
}
|
|
441
|
+
const lo = vmin ?? Math.min(...values);
|
|
442
|
+
const hi = vmax ?? Math.max(...values);
|
|
443
|
+
for (const c of cells) {
|
|
444
|
+
const t = hi === lo ? 0.5 : (c.colorValue - lo) / (hi - lo);
|
|
445
|
+
out.set(cellKey(c.row, c.col), Math.max(0, Math.min(1, t)));
|
|
446
|
+
}
|
|
447
|
+
return out;
|
|
448
|
+
};
|
|
449
|
+
var normalizeCells = (cells, { scope = "global", method = "linear", vmin, vmax } = {}) => {
|
|
450
|
+
const partitions = /* @__PURE__ */ new Map();
|
|
451
|
+
const partOf = (c) => {
|
|
452
|
+
switch (scope) {
|
|
453
|
+
case "row":
|
|
454
|
+
return `r:${c.row}`;
|
|
455
|
+
case "column":
|
|
456
|
+
return `c:${c.col}`;
|
|
457
|
+
case "block":
|
|
458
|
+
return c.block != null ? `b:${c.block}` : "global";
|
|
459
|
+
default:
|
|
460
|
+
return "global";
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
for (const c of cells) {
|
|
464
|
+
const k = partOf(c);
|
|
465
|
+
const part = partitions.get(k);
|
|
466
|
+
if (part) part.push(c);
|
|
467
|
+
else partitions.set(k, [c]);
|
|
468
|
+
}
|
|
469
|
+
const out = /* @__PURE__ */ new Map();
|
|
470
|
+
for (const part of partitions.values()) {
|
|
471
|
+
for (const [k, t] of normalizePartition(part, method, vmin, vmax)) out.set(k, t);
|
|
472
|
+
}
|
|
473
|
+
return out;
|
|
474
|
+
};
|
|
475
|
+
var heatColor = (t) => interpolateRdYlGn(Math.max(0, Math.min(1, t)));
|
|
476
|
+
var heatLabelColor = (t) => {
|
|
477
|
+
const m = heatColor(t).match(/\d+/g);
|
|
478
|
+
if (!m) return "#0f172a";
|
|
479
|
+
const [r, g, b] = m.map(Number);
|
|
480
|
+
const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
481
|
+
return lum > 140 ? "#0f172a" : "#f8fafc";
|
|
482
|
+
};
|
|
349
483
|
function MetricView({ records, config, value: presetValue }) {
|
|
350
484
|
const value = useMemo(() => {
|
|
351
485
|
if (presetValue != null) return presetValue;
|
|
@@ -440,15 +574,17 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
|
|
|
440
574
|
return Object.entries(groups).map(([key, recs]) => ({ group: key, value: aggregate(recs, valueColumn, aggFn) })).sort((a, b) => b.value - a.value).slice(0, 50);
|
|
441
575
|
}, [records, groupColumn, valueColumn, aggFn, presetData]);
|
|
442
576
|
const data = presetData || computed;
|
|
443
|
-
|
|
577
|
+
const s = withStyleDefaults(style);
|
|
578
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 20, bottom: 60, left: 60 });
|
|
579
|
+
return /* @__PURE__ */ jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsx(
|
|
444
580
|
ResponsiveBar,
|
|
445
581
|
{
|
|
446
582
|
data,
|
|
447
583
|
keys: ["value"],
|
|
448
584
|
indexBy: "group",
|
|
449
|
-
margin
|
|
585
|
+
margin,
|
|
450
586
|
padding: 0.3,
|
|
451
|
-
colors: ["rgba(74, 222, 128, 0.8)"],
|
|
587
|
+
colors: [s.seriesColors[0] || "rgba(74, 222, 128, 0.8)"],
|
|
452
588
|
borderColor: { from: "color", modifiers: [["darker", 1.6]] },
|
|
453
589
|
axisBottom: makeAxis(style, "x", groupColumn),
|
|
454
590
|
axisLeft: makeAxis(style, "y", valueColumn, { numeric: true }),
|
|
@@ -472,16 +608,19 @@ function PieView({ records, groupColumn, data: presetData, style }) {
|
|
|
472
608
|
}, [records, groupColumn, presetData]);
|
|
473
609
|
const data = presetData || computed;
|
|
474
610
|
const legend = legendConfig(style);
|
|
475
|
-
|
|
611
|
+
const s = withStyleDefaults(style);
|
|
612
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 120, bottom: 20, left: 20 });
|
|
613
|
+
const colorById = new Map(data.map((d, i) => [d.id, seriesColor(i, s)]));
|
|
614
|
+
return /* @__PURE__ */ jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsx(
|
|
476
615
|
ResponsivePie,
|
|
477
616
|
{
|
|
478
617
|
data,
|
|
479
|
-
margin
|
|
618
|
+
margin,
|
|
480
619
|
innerRadius: 0.4,
|
|
481
620
|
padAngle: 1,
|
|
482
621
|
cornerRadius: 3,
|
|
483
622
|
activeOuterRadiusOffset: 8,
|
|
484
|
-
colors:
|
|
623
|
+
colors: ((d) => colorById.get(d.id)),
|
|
485
624
|
borderWidth: 1,
|
|
486
625
|
borderColor: { from: "color", modifiers: [["darker", 0.2]] },
|
|
487
626
|
arcLinkLabelsSkipAngle: 10,
|
|
@@ -508,29 +647,54 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
508
647
|
}];
|
|
509
648
|
}, [records, xColumn, yColumn, presetData]);
|
|
510
649
|
const data = presetData || computed;
|
|
511
|
-
|
|
650
|
+
const s = withStyleDefaults(style);
|
|
651
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 20, bottom: 60, left: 60 });
|
|
652
|
+
const colorById = new Map(data.map((serie, i) => [
|
|
653
|
+
serie.id,
|
|
654
|
+
s.seriesColors.length || data.length > 1 ? seriesColor(i, s) : "rgba(96, 165, 250, 0.9)"
|
|
655
|
+
]));
|
|
656
|
+
const tickValues = thinTicks(data[0]?.data.map((d) => d.x) ?? [], s.maxXTicks);
|
|
657
|
+
return /* @__PURE__ */ jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsx(
|
|
512
658
|
ResponsiveLine,
|
|
513
659
|
{
|
|
514
660
|
data,
|
|
515
|
-
margin
|
|
661
|
+
margin,
|
|
516
662
|
xScale: { type: "point" },
|
|
517
663
|
yScale: { type: "linear", min: "auto", max: "auto" },
|
|
518
664
|
curve: "monotoneX",
|
|
519
665
|
enableArea: true,
|
|
520
666
|
areaOpacity: 0.15,
|
|
521
|
-
colors:
|
|
667
|
+
colors: ((serie) => colorById.get(serie.id)),
|
|
522
668
|
pointSize: (data[0]?.data.length ?? 0) > 50 ? 0 : 6,
|
|
523
669
|
pointColor: { theme: "background" },
|
|
524
670
|
pointBorderWidth: 2,
|
|
525
671
|
pointBorderColor: { from: "serieColor" },
|
|
526
672
|
enableGridX: false,
|
|
527
|
-
axisBottom: makeAxis(style, "x", xColumn),
|
|
673
|
+
axisBottom: { ...makeAxis(style, "x", xColumn), ...tickValues ? { tickValues } : {} },
|
|
528
674
|
axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
|
|
529
675
|
useMesh: true,
|
|
530
676
|
theme: buildNivoTheme(style)
|
|
531
677
|
}
|
|
532
678
|
) });
|
|
533
679
|
}
|
|
680
|
+
function SparklineView({ data, height = 56, color = "#3987e5" }) {
|
|
681
|
+
const gradientId = useId();
|
|
682
|
+
const width = 260;
|
|
683
|
+
const pts = data && data.length ? data : [0];
|
|
684
|
+
const max = Math.max(...pts, 1);
|
|
685
|
+
const dx = pts.length > 1 ? width / (pts.length - 1) : width;
|
|
686
|
+
const xy = (v, i) => [i * dx, height - v / max * (height - 4) - 2];
|
|
687
|
+
const line = pts.map((v, i) => `${i === 0 ? "M" : "L"} ${xy(v, i)[0].toFixed(1)} ${xy(v, i)[1].toFixed(1)}`).join(" ");
|
|
688
|
+
const area = `${line} L ${width} ${height} L 0 ${height} Z`;
|
|
689
|
+
return /* @__PURE__ */ jsxs("svg", { viewBox: `0 0 ${width} ${height}`, height, className: "w-full", preserveAspectRatio: "none", children: [
|
|
690
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
691
|
+
/* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: color, stopOpacity: "0.25" }),
|
|
692
|
+
/* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
|
|
693
|
+
] }) }),
|
|
694
|
+
/* @__PURE__ */ jsx("path", { d: area, fill: `url(#${gradientId})` }),
|
|
695
|
+
/* @__PURE__ */ jsx("path", { d: line, fill: "none", stroke: color, strokeWidth: "2", vectorEffect: "non-scaling-stroke" })
|
|
696
|
+
] });
|
|
697
|
+
}
|
|
534
698
|
function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
535
699
|
const computed = useMemo(() => {
|
|
536
700
|
if (presetData || !records) return [];
|
|
@@ -538,14 +702,20 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
538
702
|
return [{ id: `${xColumn} vs ${yColumn}`, data: points }];
|
|
539
703
|
}, [records, xColumn, yColumn, presetData]);
|
|
540
704
|
const data = presetData || computed;
|
|
541
|
-
|
|
705
|
+
const s = withStyleDefaults(style);
|
|
706
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 20, bottom: 60, left: 60 });
|
|
707
|
+
const colorById = new Map(data.map((serie, i) => [
|
|
708
|
+
serie.id,
|
|
709
|
+
s.seriesColors.length || data.length > 1 ? seriesColor(i, s) : "rgba(167, 139, 250, 0.7)"
|
|
710
|
+
]));
|
|
711
|
+
return /* @__PURE__ */ jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsx(
|
|
542
712
|
ResponsiveScatterPlot,
|
|
543
713
|
{
|
|
544
714
|
data,
|
|
545
|
-
margin
|
|
715
|
+
margin,
|
|
546
716
|
xScale: { type: "linear", min: "auto", max: "auto" },
|
|
547
717
|
yScale: { type: "linear", min: "auto", max: "auto" },
|
|
548
|
-
colors:
|
|
718
|
+
colors: ((serie) => colorById.get(serie.serieId ?? serie.id ?? "")),
|
|
549
719
|
nodeSize: 6,
|
|
550
720
|
axisBottom: makeAxis(style, "x", xColumn, { numeric: true }),
|
|
551
721
|
axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
|
|
@@ -641,8 +811,8 @@ function HeatmapView({
|
|
|
641
811
|
if (!data.length || !data[0].data.length) {
|
|
642
812
|
return /* @__PURE__ */ jsx("div", { className: "p-8 text-center text-midnight-text-muted", children: "Not enough distinct values for a heatmap" });
|
|
643
813
|
}
|
|
644
|
-
const margin = showTotals ? { top: 60, right: 70, bottom: 60, left: 100 } : { top: 60, right: 20, bottom: 20, left: 100 };
|
|
645
|
-
return /* @__PURE__ */ jsx("div", { className:
|
|
814
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, showTotals ? { top: 60, right: 70, bottom: 60, left: 100 } : { top: 60, right: 20, bottom: 20, left: 100 });
|
|
815
|
+
return /* @__PURE__ */ jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsx(
|
|
646
816
|
ResponsiveHeatMap,
|
|
647
817
|
{
|
|
648
818
|
data,
|
|
@@ -661,6 +831,160 @@ function HeatmapView({
|
|
|
661
831
|
}
|
|
662
832
|
) });
|
|
663
833
|
}
|
|
834
|
+
var MAX_AXIS2 = 30;
|
|
835
|
+
var TOTAL_ID = "\u03A3";
|
|
836
|
+
var NULL = "(null)";
|
|
837
|
+
var fmtNum2 = (n) => {
|
|
838
|
+
if (typeof n !== "number" || Number.isNaN(n)) return "";
|
|
839
|
+
return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
840
|
+
};
|
|
841
|
+
var orderIds2 = (ids, margin, order) => {
|
|
842
|
+
const sorted = [...ids];
|
|
843
|
+
if (order.endsWith("-desc")) sorted.sort((a, b) => (margin[b]?.lv ?? 0) - (margin[a]?.lv ?? 0));
|
|
844
|
+
else if (order.endsWith("-asc")) sorted.sort((a, b) => (margin[a]?.lv ?? 0) - (margin[b]?.lv ?? 0));
|
|
845
|
+
else sorted.sort();
|
|
846
|
+
return sorted;
|
|
847
|
+
};
|
|
848
|
+
function fromRecords(records, rowCol, colCol, valueCol, labelStat, colorStat, blockField) {
|
|
849
|
+
const col = valueCol || rowCol;
|
|
850
|
+
const statOf = (rs, s) => valueCol ? aggregate(rs, col, s) : rs.length;
|
|
851
|
+
const both = (rs) => ({ lv: statOf(rs, labelStat), cv: statOf(rs, colorStat) });
|
|
852
|
+
const byRow = groupBy(records, rowCol);
|
|
853
|
+
const rowIds = Object.keys(byRow);
|
|
854
|
+
const colIds = [...new Set(records.map((r) => String(r[colCol] ?? NULL)))];
|
|
855
|
+
const cells = {};
|
|
856
|
+
for (const rid of rowIds) {
|
|
857
|
+
cells[rid] = {};
|
|
858
|
+
const byCol2 = groupBy(byRow[rid], colCol);
|
|
859
|
+
for (const cid of colIds) {
|
|
860
|
+
const cellRows = byCol2[cid] || [];
|
|
861
|
+
const stat = both(cellRows);
|
|
862
|
+
if (blockField && cellRows.length) stat.block = String(cellRows[0][blockField] ?? NULL);
|
|
863
|
+
cells[rid][cid] = stat;
|
|
864
|
+
}
|
|
865
|
+
}
|
|
866
|
+
const rowMargin = {};
|
|
867
|
+
for (const rid of rowIds) rowMargin[rid] = both(byRow[rid]);
|
|
868
|
+
const colMargin = {};
|
|
869
|
+
const byCol = groupBy(records, colCol);
|
|
870
|
+
for (const cid of colIds) colMargin[cid] = both(byCol[cid] || []);
|
|
871
|
+
return { rowIds, colIds, cells, rowMargin, colMargin, grand: both(records) };
|
|
872
|
+
}
|
|
873
|
+
function fromData(data) {
|
|
874
|
+
const cells = {};
|
|
875
|
+
const rowMargin = {};
|
|
876
|
+
const colMargin = {};
|
|
877
|
+
let grand = { lv: 0, cv: 0 };
|
|
878
|
+
const rowIds = [];
|
|
879
|
+
const colIds = [];
|
|
880
|
+
for (const d of data) {
|
|
881
|
+
const stat = { lv: d.lv, cv: d.cv, ...d.block != null ? { block: d.block } : {} };
|
|
882
|
+
if (d.gr && d.gc) {
|
|
883
|
+
grand = stat;
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
886
|
+
if (d.gr) {
|
|
887
|
+
if (d.c != null) colMargin[d.c] = stat;
|
|
888
|
+
continue;
|
|
889
|
+
}
|
|
890
|
+
if (d.gc) {
|
|
891
|
+
if (d.r != null) rowMargin[d.r] = stat;
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
const r = d.r ?? NULL, c = d.c ?? NULL;
|
|
895
|
+
if (!cells[r]) {
|
|
896
|
+
cells[r] = {};
|
|
897
|
+
rowIds.push(r);
|
|
898
|
+
}
|
|
899
|
+
if (!colIds.includes(c)) colIds.push(c);
|
|
900
|
+
cells[r][c] = stat;
|
|
901
|
+
}
|
|
902
|
+
return { rowIds, colIds, cells, rowMargin, colMargin, grand };
|
|
903
|
+
}
|
|
904
|
+
function HeatmapPlusView({
|
|
905
|
+
records,
|
|
906
|
+
rowColumn,
|
|
907
|
+
colColumn,
|
|
908
|
+
valueColumn,
|
|
909
|
+
labelStat = "count",
|
|
910
|
+
colorStat = "count",
|
|
911
|
+
blockField,
|
|
912
|
+
data: presetData,
|
|
913
|
+
scope = "global",
|
|
914
|
+
method = "linear",
|
|
915
|
+
vmin,
|
|
916
|
+
vmax,
|
|
917
|
+
rowOrder = "alpha",
|
|
918
|
+
colOrder = "alpha",
|
|
919
|
+
showMargins = false,
|
|
920
|
+
style
|
|
921
|
+
}) {
|
|
922
|
+
const s = withStyleDefaults(style);
|
|
923
|
+
const model = useMemo(() => {
|
|
924
|
+
if (presetData) return fromData(presetData);
|
|
925
|
+
if (records) return fromRecords(records, rowColumn, colColumn, valueColumn, labelStat, colorStat, blockField);
|
|
926
|
+
return null;
|
|
927
|
+
}, [presetData, records, rowColumn, colColumn, valueColumn, labelStat, colorStat, blockField]);
|
|
928
|
+
const built = useMemo(() => {
|
|
929
|
+
if (!model || !model.rowIds.length || !model.colIds.length) return null;
|
|
930
|
+
const { cells, rowMargin, colMargin, grand } = model;
|
|
931
|
+
const rows = orderIds2(model.rowIds, rowMargin, rowOrder).slice(0, MAX_AXIS2);
|
|
932
|
+
const cols = orderIds2(model.colIds, colMargin, colOrder).slice(0, MAX_AXIS2);
|
|
933
|
+
const bodyCells = [];
|
|
934
|
+
for (const rid of rows) for (const cid of cols) {
|
|
935
|
+
bodyCells.push({ row: rid, col: cid, colorValue: cells[rid]?.[cid]?.cv ?? NaN, block: cells[rid]?.[cid]?.block });
|
|
936
|
+
}
|
|
937
|
+
const t2 = new Map(normalizeCells(bodyCells, { scope, method, vmin, vmax }));
|
|
938
|
+
if (showMargins) {
|
|
939
|
+
const rowMc = rows.map((rid) => ({ row: rid, col: TOTAL_ID, colorValue: rowMargin[rid]?.cv ?? NaN }));
|
|
940
|
+
const colMc = cols.map((cid) => ({ row: TOTAL_ID, col: cid, colorValue: colMargin[cid]?.cv ?? NaN }));
|
|
941
|
+
for (const [k, v] of normalizeCells(rowMc, { scope: "global", method })) t2.set(k, v);
|
|
942
|
+
for (const [k, v] of normalizeCells(colMc, { scope: "global", method })) t2.set(k, v);
|
|
943
|
+
t2.set(cellKey(TOTAL_ID, TOTAL_ID), 0.5);
|
|
944
|
+
}
|
|
945
|
+
const colKeys = showMargins ? [...cols, TOTAL_ID] : cols;
|
|
946
|
+
const nivoData2 = rows.map((rid) => ({
|
|
947
|
+
id: rid,
|
|
948
|
+
data: colKeys.map((cid) => ({
|
|
949
|
+
x: cid,
|
|
950
|
+
y: cid === TOTAL_ID ? rowMargin[rid]?.lv ?? 0 : cells[rid]?.[cid]?.lv ?? 0
|
|
951
|
+
}))
|
|
952
|
+
}));
|
|
953
|
+
if (showMargins) {
|
|
954
|
+
nivoData2.push({
|
|
955
|
+
id: TOTAL_ID,
|
|
956
|
+
data: colKeys.map((cid) => ({
|
|
957
|
+
x: cid,
|
|
958
|
+
y: cid === TOTAL_ID ? grand.lv : colMargin[cid]?.lv ?? 0
|
|
959
|
+
}))
|
|
960
|
+
});
|
|
961
|
+
}
|
|
962
|
+
return { nivoData: nivoData2, t: t2 };
|
|
963
|
+
}, [model, rowOrder, colOrder, scope, method, vmin, vmax, showMargins]);
|
|
964
|
+
if (!built) {
|
|
965
|
+
return /* @__PURE__ */ jsx("div", { className: "p-8 text-center text-midnight-text-muted", children: "Not enough distinct values for a heatmap" });
|
|
966
|
+
}
|
|
967
|
+
const { nivoData, t } = built;
|
|
968
|
+
const tAt = (serieId, x) => t.get(cellKey(serieId, x)) ?? 0;
|
|
969
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, { top: 60, right: 20, bottom: showMargins ? 60 : 20, left: 100 });
|
|
970
|
+
return /* @__PURE__ */ jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsx(
|
|
971
|
+
ResponsiveHeatMap,
|
|
972
|
+
{
|
|
973
|
+
data: nivoData,
|
|
974
|
+
margin,
|
|
975
|
+
valueFormat: ((v) => fmtNum2(v)),
|
|
976
|
+
axisTop: { tickSize: 5, tickPadding: 5, tickRotation: s.xTickRotation, legend: s.showXLegend ? s.xAxisLabel || colColumn : "", legendPosition: s.xLegendPosition, legendOffset: -50 },
|
|
977
|
+
axisLeft: { tickSize: 5, tickPadding: 5, legend: s.showYLegend ? s.yAxisLabel || rowColumn : "", legendPosition: s.yLegendPosition, legendOffset: -80 },
|
|
978
|
+
colors: ((cell) => heatColor(tAt(cell.serieId, cell.data.x))),
|
|
979
|
+
emptyColor: "#1e293b",
|
|
980
|
+
borderWidth: 1,
|
|
981
|
+
borderColor: "#334155",
|
|
982
|
+
labelTextColor: ((cell) => heatLabelColor(tAt(cell.serieId, cell.data.x))),
|
|
983
|
+
hoverTarget: "cell",
|
|
984
|
+
theme: buildNivoTheme(style)
|
|
985
|
+
}
|
|
986
|
+
) });
|
|
987
|
+
}
|
|
664
988
|
function PivotView({ records }) {
|
|
665
989
|
const [pivotState, setPivotState] = useState({});
|
|
666
990
|
if (!records?.length) {
|
|
@@ -1197,6 +1521,7 @@ var CHART_TYPES = [
|
|
|
1197
1521
|
{ id: "line", icon: TrendingUp, label: "Line" },
|
|
1198
1522
|
{ id: "scatter", icon: ScatterChart, label: "Scatter" },
|
|
1199
1523
|
{ id: "heatmap", icon: LayoutGrid, label: "Heatmap" },
|
|
1524
|
+
{ id: "heatmap-plus", icon: Grid3x3, label: "Heatmap+" },
|
|
1200
1525
|
{ id: "grouped-bar", icon: BarChart3, label: "Grouped Bar" }
|
|
1201
1526
|
];
|
|
1202
1527
|
var AGG_OPTIONS = ["count", "distinct", "sum", "avg", "min", "max"];
|
|
@@ -1217,6 +1542,17 @@ var ORDER_OPTIONS = [
|
|
|
1217
1542
|
{ id: "total-asc", label: "Total \u2191" }
|
|
1218
1543
|
];
|
|
1219
1544
|
var MARGIN_AGGS = ["sum", "avg", "min", "max", "count"].map((a) => ({ id: a, label: a }));
|
|
1545
|
+
var HEAT_STAT_OPTIONS = HEAT_STATS.map((a) => ({ id: a, label: a }));
|
|
1546
|
+
var SCOPE_OPTIONS = [
|
|
1547
|
+
{ id: "global", label: "Global" },
|
|
1548
|
+
{ id: "row", label: "Per row" },
|
|
1549
|
+
{ id: "column", label: "Per column" },
|
|
1550
|
+
{ id: "block", label: "Per block" }
|
|
1551
|
+
];
|
|
1552
|
+
var METHOD_OPTIONS = [
|
|
1553
|
+
{ id: "linear", label: "Linear" },
|
|
1554
|
+
{ id: "rank", label: "Rank" }
|
|
1555
|
+
];
|
|
1220
1556
|
var FILTER_OP_OPTIONS = FILTER_OPS.map((o) => ({ id: o, label: o }));
|
|
1221
1557
|
var FieldPill = ({ name, type, onRemove, onChangeAgg, agg, showAgg }) => {
|
|
1222
1558
|
const typeColor = {
|
|
@@ -1405,6 +1741,7 @@ var ChartPreview = ({
|
|
|
1405
1741
|
sqlLoading,
|
|
1406
1742
|
sqlError,
|
|
1407
1743
|
heatmapOpts = {},
|
|
1744
|
+
heatPlusOpts = {},
|
|
1408
1745
|
style
|
|
1409
1746
|
}) => {
|
|
1410
1747
|
const xCol = xFields[0]?.name;
|
|
@@ -1443,6 +1780,8 @@ var ChartPreview = ({
|
|
|
1443
1780
|
return /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(ScatterView, { data: shaped, xColumn: xCol, yColumn: yCol, style }) });
|
|
1444
1781
|
case "heatmap":
|
|
1445
1782
|
return shaped.length ? /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(HeatmapView, { data: shaped, rowColumn: xCol, colColumn: yCol, ...heatmapOpts, style }) }) : /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-sm text-midnight-text-muted", children: "No data" });
|
|
1783
|
+
case "heatmap-plus":
|
|
1784
|
+
return shaped.length ? /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(HeatmapPlusView, { data: shaped, rowColumn: xCol, colColumn: yCol, ...heatPlusOpts, style }) }) : /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-sm text-midnight-text-muted", children: "No data" });
|
|
1446
1785
|
default:
|
|
1447
1786
|
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-sm text-midnight-text-muted", children: "Select a chart type" });
|
|
1448
1787
|
}
|
|
@@ -1464,6 +1803,8 @@ var ChartPreview = ({
|
|
|
1464
1803
|
return xCol && yCol ? /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(ScatterView, { records, xColumn: xCol, yColumn: yCol, style }) }) : null;
|
|
1465
1804
|
case "heatmap":
|
|
1466
1805
|
return xCol && yCol ? /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(HeatmapView, { records, rowColumn: xCol, colColumn: yCol, valueColumn: valueField?.name, aggFn: valueField?.agg || "count", ...heatmapOpts, style }) }) : null;
|
|
1806
|
+
case "heatmap-plus":
|
|
1807
|
+
return xCol && yCol ? /* @__PURE__ */ jsx(Suspense, { fallback: null, children: /* @__PURE__ */ jsx(HeatmapPlusView, { records, rowColumn: xCol, colColumn: yCol, valueColumn: valueField?.name, ...heatPlusOpts, style }) }) : null;
|
|
1467
1808
|
default:
|
|
1468
1809
|
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-sm text-midnight-text-muted", children: "Select a chart type" });
|
|
1469
1810
|
}
|
|
@@ -1487,6 +1828,13 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1487
1828
|
const [colOrder, setColOrder] = useState("alpha");
|
|
1488
1829
|
const [marginAgg, setMarginAgg] = useState("sum");
|
|
1489
1830
|
const [showTotals, setShowTotals] = useState(false);
|
|
1831
|
+
const [labelStat, setLabelStat] = useState("count");
|
|
1832
|
+
const [colorStat, setColorStat] = useState("count");
|
|
1833
|
+
const [scope, setScope] = useState("global");
|
|
1834
|
+
const [method, setMethod] = useState("linear");
|
|
1835
|
+
const [vmin, setVmin] = useState("");
|
|
1836
|
+
const [vmax, setVmax] = useState("");
|
|
1837
|
+
const [showMargins, setShowMargins] = useState(false);
|
|
1490
1838
|
const [style, setStyle] = useState(DEFAULT_CHART_STYLE);
|
|
1491
1839
|
const [showStyle, setShowStyle] = useState(false);
|
|
1492
1840
|
const [showFields, setShowFields] = useState(false);
|
|
@@ -1495,8 +1843,17 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1495
1843
|
const [sqlLoading, setSqlLoading] = useState(false);
|
|
1496
1844
|
const [sqlError, setSqlError] = useState(null);
|
|
1497
1845
|
const generatedSql = useMemo(
|
|
1498
|
-
() => buildChartSQL({
|
|
1499
|
-
|
|
1846
|
+
() => buildChartSQL({
|
|
1847
|
+
chartType,
|
|
1848
|
+
xFields,
|
|
1849
|
+
yFields,
|
|
1850
|
+
valueField,
|
|
1851
|
+
filters,
|
|
1852
|
+
labelStat,
|
|
1853
|
+
colorStat,
|
|
1854
|
+
blockField: colorField?.name
|
|
1855
|
+
}),
|
|
1856
|
+
[chartType, xFields, yFields, valueField, filters, labelStat, colorStat, colorField]
|
|
1500
1857
|
);
|
|
1501
1858
|
useEffect(() => {
|
|
1502
1859
|
if (engineMode !== "sql") return void 0;
|
|
@@ -1594,6 +1951,26 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1594
1951
|
config.marginAgg = marginAgg;
|
|
1595
1952
|
config.showTotals = showTotals;
|
|
1596
1953
|
}
|
|
1954
|
+
if (chartType === "heatmap-plus") {
|
|
1955
|
+
config.row = xFields[0]?.name;
|
|
1956
|
+
config.col = yFields[0]?.name;
|
|
1957
|
+
if (valueField) config.value = valueField.name;
|
|
1958
|
+
else delete config.value;
|
|
1959
|
+
config.labelStat = labelStat;
|
|
1960
|
+
config.colorStat = colorStat;
|
|
1961
|
+
if (colorField) config.blockField = colorField.name;
|
|
1962
|
+
else delete config.blockField;
|
|
1963
|
+
config.scope = scope;
|
|
1964
|
+
config.method = method;
|
|
1965
|
+
const nMin = parseFloat(vmin), nMax = parseFloat(vmax);
|
|
1966
|
+
if (Number.isFinite(nMin)) config.vmin = nMin;
|
|
1967
|
+
else delete config.vmin;
|
|
1968
|
+
if (Number.isFinite(nMax)) config.vmax = nMax;
|
|
1969
|
+
else delete config.vmax;
|
|
1970
|
+
config.rowOrder = rowOrder;
|
|
1971
|
+
config.colOrder = colOrder;
|
|
1972
|
+
config.showMargins = showMargins;
|
|
1973
|
+
}
|
|
1597
1974
|
if (engineMode === "sql" && generatedSql) {
|
|
1598
1975
|
config.sql = generatedSql;
|
|
1599
1976
|
config.chartType = chartType;
|
|
@@ -1614,6 +1991,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1614
1991
|
height: fillContainer ? 8 : 2
|
|
1615
1992
|
});
|
|
1616
1993
|
};
|
|
1994
|
+
const isHeat = chartType === "heatmap" || chartType === "heatmap-plus";
|
|
1617
1995
|
return /* @__PURE__ */ jsxs("div", { className: "flex h-full", children: [
|
|
1618
1996
|
/* @__PURE__ */ jsxs("div", { className: `w-[280px] shrink-0 border-r ${theme.border} bg-midnight-elevated overflow-y-auto p-3 space-y-4`, children: [
|
|
1619
1997
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
@@ -1649,30 +2027,30 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1649
2027
|
/* @__PURE__ */ jsx(
|
|
1650
2028
|
DropZone,
|
|
1651
2029
|
{
|
|
1652
|
-
label:
|
|
2030
|
+
label: isHeat ? "Rows" : "X Axis / Group By",
|
|
1653
2031
|
fields: xFields,
|
|
1654
2032
|
columns,
|
|
1655
2033
|
onAdd: (f) => addField("x", f),
|
|
1656
2034
|
onRemove: (i) => removeField("x", i),
|
|
1657
2035
|
onChangeAgg: () => {
|
|
1658
2036
|
},
|
|
1659
|
-
maxFields: chartType === "pie" ||
|
|
2037
|
+
maxFields: chartType === "pie" || isHeat ? 1 : 3
|
|
1660
2038
|
}
|
|
1661
2039
|
),
|
|
1662
2040
|
/* @__PURE__ */ jsx(
|
|
1663
2041
|
DropZone,
|
|
1664
2042
|
{
|
|
1665
|
-
label:
|
|
2043
|
+
label: isHeat ? "Columns" : "Y Axis / Values",
|
|
1666
2044
|
fields: yFields,
|
|
1667
2045
|
columns,
|
|
1668
2046
|
onAdd: (f) => addField("y", f),
|
|
1669
2047
|
onRemove: (i) => removeField("y", i),
|
|
1670
2048
|
onChangeAgg: (i, agg) => changeAgg("y", i, agg),
|
|
1671
|
-
showAgg:
|
|
1672
|
-
maxFields:
|
|
2049
|
+
showAgg: !isHeat,
|
|
2050
|
+
maxFields: isHeat ? 1 : 5
|
|
1673
2051
|
}
|
|
1674
2052
|
),
|
|
1675
|
-
|
|
2053
|
+
isHeat && /* @__PURE__ */ jsx(
|
|
1676
2054
|
DropZone,
|
|
1677
2055
|
{
|
|
1678
2056
|
label: "Cell Value (optional \u2014 defaults to count)",
|
|
@@ -1681,10 +2059,93 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1681
2059
|
onAdd: (f) => addField("value", f),
|
|
1682
2060
|
onRemove: () => removeField("value"),
|
|
1683
2061
|
onChangeAgg: (i, agg) => changeAgg("value", i, agg),
|
|
1684
|
-
showAgg:
|
|
2062
|
+
showAgg: chartType === "heatmap",
|
|
1685
2063
|
maxFields: 1
|
|
1686
2064
|
}
|
|
1687
2065
|
),
|
|
2066
|
+
chartType === "heatmap-plus" && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2067
|
+
/* @__PURE__ */ jsx(
|
|
2068
|
+
DropZone,
|
|
2069
|
+
{
|
|
2070
|
+
label: "Block (optional \u2014 color-scope grouping)",
|
|
2071
|
+
fields: colorField ? [colorField] : [],
|
|
2072
|
+
columns,
|
|
2073
|
+
onAdd: (f) => addField("color", f),
|
|
2074
|
+
onRemove: () => removeField("color"),
|
|
2075
|
+
onChangeAgg: () => {
|
|
2076
|
+
},
|
|
2077
|
+
maxFields: 1
|
|
2078
|
+
}
|
|
2079
|
+
),
|
|
2080
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2081
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Cell Stats" }),
|
|
2082
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2083
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2084
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Label stat" }),
|
|
2085
|
+
/* @__PURE__ */ jsx(SelectControl, { value: labelStat, options: HEAT_STAT_OPTIONS, onChange: (v) => setLabelStat(v) })
|
|
2086
|
+
] }),
|
|
2087
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2088
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Color stat" }),
|
|
2089
|
+
/* @__PURE__ */ jsx(SelectControl, { value: colorStat, options: HEAT_STAT_OPTIONS, onChange: (v) => setColorStat(v) })
|
|
2090
|
+
] })
|
|
2091
|
+
] })
|
|
2092
|
+
] }),
|
|
2093
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2094
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Color Engine" }),
|
|
2095
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2096
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2097
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Scale scope" }),
|
|
2098
|
+
/* @__PURE__ */ jsx(SelectControl, { value: scope, options: SCOPE_OPTIONS, onChange: (v) => setScope(v) })
|
|
2099
|
+
] }),
|
|
2100
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2101
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Scale method" }),
|
|
2102
|
+
/* @__PURE__ */ jsx(SelectControl, { value: method, options: METHOD_OPTIONS, onChange: (v) => setMethod(v) })
|
|
2103
|
+
] }),
|
|
2104
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2105
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Fixed min / max" }),
|
|
2106
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-1", children: [
|
|
2107
|
+
/* @__PURE__ */ jsx(
|
|
2108
|
+
"input",
|
|
2109
|
+
{
|
|
2110
|
+
value: vmin,
|
|
2111
|
+
onChange: (e) => setVmin(e.target.value),
|
|
2112
|
+
placeholder: "auto",
|
|
2113
|
+
inputMode: "decimal",
|
|
2114
|
+
className: "w-14 px-2 py-1 text-xs font-mono bg-midnight-surface border border-midnight-border text-midnight-text-body outline-none focus:border-midnight-accent"
|
|
2115
|
+
}
|
|
2116
|
+
),
|
|
2117
|
+
/* @__PURE__ */ jsx(
|
|
2118
|
+
"input",
|
|
2119
|
+
{
|
|
2120
|
+
value: vmax,
|
|
2121
|
+
onChange: (e) => setVmax(e.target.value),
|
|
2122
|
+
placeholder: "auto",
|
|
2123
|
+
inputMode: "decimal",
|
|
2124
|
+
className: "w-14 px-2 py-1 text-xs font-mono bg-midnight-surface border border-midnight-border text-midnight-text-body outline-none focus:border-midnight-accent"
|
|
2125
|
+
}
|
|
2126
|
+
)
|
|
2127
|
+
] })
|
|
2128
|
+
] })
|
|
2129
|
+
] })
|
|
2130
|
+
] }),
|
|
2131
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
2132
|
+
/* @__PURE__ */ jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Order & Margins" }),
|
|
2133
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
2134
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2135
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Order rows" }),
|
|
2136
|
+
/* @__PURE__ */ jsx(SelectControl, { value: rowOrder, options: ORDER_OPTIONS, onChange: (v) => setRowOrder(v) })
|
|
2137
|
+
] }),
|
|
2138
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2139
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Order columns" }),
|
|
2140
|
+
/* @__PURE__ */ jsx(SelectControl, { value: colOrder, options: ORDER_OPTIONS, onChange: (v) => setColOrder(v) })
|
|
2141
|
+
] }),
|
|
2142
|
+
/* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
|
|
2143
|
+
/* @__PURE__ */ jsx("input", { type: "checkbox", checked: showMargins, onChange: (e) => setShowMargins(e.target.checked), className: "accent-midnight-accent" }),
|
|
2144
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Show margins (row / col / grand)" })
|
|
2145
|
+
] })
|
|
2146
|
+
] })
|
|
2147
|
+
] })
|
|
2148
|
+
] }),
|
|
1688
2149
|
chartType === "heatmap" && /* @__PURE__ */ jsxs("div", { children: [
|
|
1689
2150
|
/* @__PURE__ */ jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Order & Totals" }),
|
|
1690
2151
|
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
@@ -1818,6 +2279,18 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1818
2279
|
sqlLoading,
|
|
1819
2280
|
sqlError,
|
|
1820
2281
|
heatmapOpts: { rowOrder, colOrder, marginAgg, showTotals },
|
|
2282
|
+
heatPlusOpts: {
|
|
2283
|
+
labelStat,
|
|
2284
|
+
colorStat,
|
|
2285
|
+
blockField: colorField?.name,
|
|
2286
|
+
scope,
|
|
2287
|
+
method,
|
|
2288
|
+
vmin: Number.isFinite(parseFloat(vmin)) ? parseFloat(vmin) : void 0,
|
|
2289
|
+
vmax: Number.isFinite(parseFloat(vmax)) ? parseFloat(vmax) : void 0,
|
|
2290
|
+
rowOrder,
|
|
2291
|
+
colOrder,
|
|
2292
|
+
showMargins
|
|
2293
|
+
},
|
|
1821
2294
|
style
|
|
1822
2295
|
}
|
|
1823
2296
|
) })
|
|
@@ -1826,7 +2299,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1826
2299
|
function ViewLoading() {
|
|
1827
2300
|
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Loading..." });
|
|
1828
2301
|
}
|
|
1829
|
-
var SQL_CHART_TYPES = /* @__PURE__ */ new Set(["bar", "grouped-bar", "pie", "line", "scatter", "heatmap"]);
|
|
2302
|
+
var SQL_CHART_TYPES = /* @__PURE__ */ new Set(["bar", "grouped-bar", "pie", "line", "scatter", "heatmap", "heatmap-plus"]);
|
|
1830
2303
|
var SQL_DATA_TYPES = /* @__PURE__ */ new Set(["table", "metric"]);
|
|
1831
2304
|
var TABLE_PREVIEW_ROWS = 200;
|
|
1832
2305
|
function tableSql(config) {
|
|
@@ -1857,6 +2330,14 @@ function panelToChartConfig(chartType, config) {
|
|
|
1857
2330
|
if (config.col) cfg.yFields = [{ name: config.col }];
|
|
1858
2331
|
if (config.value) cfg.valueField = { name: config.value, agg: config.agg };
|
|
1859
2332
|
break;
|
|
2333
|
+
case "heatmap-plus":
|
|
2334
|
+
if (config.row) cfg.xFields = [{ name: config.row }];
|
|
2335
|
+
if (config.col) cfg.yFields = [{ name: config.col }];
|
|
2336
|
+
if (config.value) cfg.valueField = { name: config.value, agg: config.labelStat };
|
|
2337
|
+
cfg.labelStat = config.labelStat;
|
|
2338
|
+
cfg.colorStat = config.colorStat;
|
|
2339
|
+
cfg.blockField = config.blockField;
|
|
2340
|
+
break;
|
|
1860
2341
|
}
|
|
1861
2342
|
return cfg;
|
|
1862
2343
|
}
|
|
@@ -2005,6 +2486,9 @@ function SqlPanel({ panel }) {
|
|
|
2005
2486
|
case "heatmap":
|
|
2006
2487
|
chart = shaped.length ? /* @__PURE__ */ jsx(HeatmapView, { data: shaped, rowColumn: config.row || "", colColumn: config.col || "", rowOrder: config.rowOrder, colOrder: config.colOrder, marginAgg: config.marginAgg, showTotals: config.showTotals, style: config.style }) : /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "No data" });
|
|
2007
2488
|
break;
|
|
2489
|
+
case "heatmap-plus":
|
|
2490
|
+
chart = shaped.length ? /* @__PURE__ */ jsx(HeatmapPlusView, { data: shaped, rowColumn: config.row || "", colColumn: config.col || "", scope: config.scope, method: config.method, vmin: config.vmin, vmax: config.vmax, rowOrder: config.rowOrder, colOrder: config.colOrder, showMargins: config.showMargins, style: config.style }) : /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "No data" });
|
|
2491
|
+
break;
|
|
2008
2492
|
default:
|
|
2009
2493
|
chart = /* @__PURE__ */ jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
2010
2494
|
"Unsupported SQL chart: ",
|
|
@@ -2038,6 +2522,8 @@ function PanelContent({ panel, records, columns }) {
|
|
|
2038
2522
|
return config.x && config.y ? /* @__PURE__ */ jsx(ScatterView, { records, xColumn: config.x, yColumn: config.y, style: config.style }) : null;
|
|
2039
2523
|
case "heatmap":
|
|
2040
2524
|
return config.row && config.col ? /* @__PURE__ */ jsx(HeatmapView, { records, rowColumn: config.row, colColumn: config.col, valueColumn: config.value, aggFn: config.agg || "count", rowOrder: config.rowOrder, colOrder: config.colOrder, marginAgg: config.marginAgg, showTotals: config.showTotals, style: config.style }) : null;
|
|
2525
|
+
case "heatmap-plus":
|
|
2526
|
+
return config.row && config.col ? /* @__PURE__ */ jsx(HeatmapPlusView, { records, rowColumn: config.row, colColumn: config.col, valueColumn: config.value, labelStat: config.labelStat, colorStat: config.colorStat, blockField: config.blockField, scope: config.scope, method: config.method, vmin: config.vmin, vmax: config.vmax, rowOrder: config.rowOrder, colOrder: config.colOrder, showMargins: config.showMargins, style: config.style }) : null;
|
|
2041
2527
|
case "pivot":
|
|
2042
2528
|
return /* @__PURE__ */ jsx(PivotView, { records });
|
|
2043
2529
|
case "metric":
|
|
@@ -2497,6 +2983,6 @@ function DataExplorer({
|
|
|
2497
2983
|
] });
|
|
2498
2984
|
}
|
|
2499
2985
|
|
|
2500
|
-
export { BarView, ChartBuilder, ChartStyleControls, DEFAULT_CHART_STYLE, DashboardProvider, DashboardRenderer, DataExplorer, HeatmapView, InsightView, LEGEND_ANCHORS, LineView, MetricView, PanelChart, PieView, PivotView, ScatterView, SqlConsole, aggExpr, aggregate, axisLegend, buildChartSQL, buildNivoTheme, compileWhere, groupBy, legendConfig, qIdent, qLit, shapeChartData, useCapabilities, useDashboard, withStyleDefaults };
|
|
2986
|
+
export { BarView, ChartBuilder, ChartStyleControls, DEFAULT_CHART_STYLE, DEFAULT_SERIES_COLORS, DashboardProvider, DashboardRenderer, DataExplorer, HEAT_STATS, HeatmapPlusView, HeatmapView, InsightView, LEGEND_ANCHORS, LineView, MetricView, PanelChart, PieView, PivotView, SERIES_OVERFLOW_COLOR, ScatterView, SparklineView, SqlConsole, aggExpr, aggregate, axisLegend, buildChartSQL, buildNivoTheme, chartSizing, compileWhere, groupBy, heatColor, heatLabelColor, legendConfig, normalizeCells, qIdent, qLit, seriesColor, shapeChartData, thinTicks, useCapabilities, useDashboard, withStyleDefaults };
|
|
2501
2987
|
//# sourceMappingURL=index.js.map
|
|
2502
2988
|
//# sourceMappingURL=index.js.map
|