@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.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
var react = require('react');
|
|
4
4
|
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
var d3ScaleChromatic = require('d3-scale-chromatic');
|
|
5
6
|
var bar = require('@nivo/bar');
|
|
6
7
|
var pie = require('@nivo/pie');
|
|
7
8
|
var line = require('@nivo/line');
|
|
@@ -83,6 +84,8 @@ var aggExpr = (agg, col) => {
|
|
|
83
84
|
return `MIN(${qNum(col)})`;
|
|
84
85
|
case "max":
|
|
85
86
|
return `MAX(${qNum(col)})`;
|
|
87
|
+
case "median":
|
|
88
|
+
return `MEDIAN(${qNum(col)})`;
|
|
86
89
|
default:
|
|
87
90
|
return "COUNT(*)";
|
|
88
91
|
}
|
|
@@ -120,7 +123,10 @@ var buildChartSQL = ({
|
|
|
120
123
|
xFields = [],
|
|
121
124
|
yFields = [],
|
|
122
125
|
valueField = null,
|
|
123
|
-
filters = []
|
|
126
|
+
filters = [],
|
|
127
|
+
labelStat,
|
|
128
|
+
colorStat,
|
|
129
|
+
blockField
|
|
124
130
|
}) => {
|
|
125
131
|
const where = compileWhere(filters);
|
|
126
132
|
const x = xFields[0]?.name;
|
|
@@ -157,6 +163,14 @@ var buildChartSQL = ({
|
|
|
157
163
|
const metric = valueField ? aggExpr(valueField.agg || "count", valueField.name) : "COUNT(*)";
|
|
158
164
|
return `SELECT ${qIdent(x)} AS r, ${qIdent(y)} AS c, ${metric} AS v FROM ${TABLE}${where} GROUP BY 1, 2`;
|
|
159
165
|
}
|
|
166
|
+
case "heatmap-plus": {
|
|
167
|
+
if (!x || !y) return null;
|
|
168
|
+
const col = valueField?.name;
|
|
169
|
+
const lv = col ? aggExpr(labelStat || valueField?.agg || "count", col) : "COUNT(*)";
|
|
170
|
+
const cv = col ? aggExpr(colorStat || labelStat || valueField?.agg || "count", col) : "COUNT(*)";
|
|
171
|
+
const blockSel = blockField ? `, ANY_VALUE(${qIdent(blockField)}) AS block` : "";
|
|
172
|
+
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)}), ())`;
|
|
173
|
+
}
|
|
160
174
|
default:
|
|
161
175
|
return null;
|
|
162
176
|
}
|
|
@@ -205,6 +219,17 @@ var shapeChartData = (chartType, rows, { yFields = [] } = {}) => {
|
|
|
205
219
|
data: colKeys.map((ck) => ({ x: ck, y: cell[rk][ck] || 0 }))
|
|
206
220
|
}));
|
|
207
221
|
}
|
|
222
|
+
case "heatmap-plus": {
|
|
223
|
+
return rows.map((r) => ({
|
|
224
|
+
r: r.r == null ? null : String(r.r),
|
|
225
|
+
c: r.c == null ? null : String(r.c),
|
|
226
|
+
lv: Number(r.lv) || 0,
|
|
227
|
+
cv: Number(r.cv) || 0,
|
|
228
|
+
gr: Number(r.gr) || 0,
|
|
229
|
+
gc: Number(r.gc) || 0,
|
|
230
|
+
...r.block != null ? { block: String(r.block) } : {}
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
208
233
|
default:
|
|
209
234
|
return [];
|
|
210
235
|
}
|
|
@@ -251,8 +276,54 @@ var DEFAULT_CHART_STYLE = {
|
|
|
251
276
|
titleAlign: "left",
|
|
252
277
|
titleBold: false,
|
|
253
278
|
titleBackground: "",
|
|
254
|
-
titleColor: ""
|
|
279
|
+
titleColor: "",
|
|
280
|
+
// Chart frame + series color model.
|
|
281
|
+
height: 0,
|
|
282
|
+
margin: null,
|
|
283
|
+
maxXTicks: 0,
|
|
284
|
+
seriesColors: []
|
|
285
|
+
};
|
|
286
|
+
var DEFAULT_SERIES_COLORS = [
|
|
287
|
+
"#3987e5",
|
|
288
|
+
// blue
|
|
289
|
+
"#d95926",
|
|
290
|
+
// orange
|
|
291
|
+
"#199e70",
|
|
292
|
+
// aqua
|
|
293
|
+
"#c98500",
|
|
294
|
+
// yellow
|
|
295
|
+
"#d55181",
|
|
296
|
+
// magenta
|
|
297
|
+
"#008300",
|
|
298
|
+
// green
|
|
299
|
+
"#9085e9",
|
|
300
|
+
// violet
|
|
301
|
+
"#e66767"
|
|
302
|
+
// red
|
|
303
|
+
];
|
|
304
|
+
var SERIES_OVERFLOW_COLOR = "#707078";
|
|
305
|
+
var seriesColor = (i, style) => {
|
|
306
|
+
const s = withStyleDefaults(style);
|
|
307
|
+
const palette = s.seriesColors.length ? s.seriesColors : DEFAULT_SERIES_COLORS;
|
|
308
|
+
return i < palette.length ? palette[i] : SERIES_OVERFLOW_COLOR;
|
|
309
|
+
};
|
|
310
|
+
var chartSizing = (style, defaultMargin) => {
|
|
311
|
+
const s = withStyleDefaults(style);
|
|
312
|
+
return {
|
|
313
|
+
frameClass: s.height > 0 ? "w-full" : "h-full w-full min-h-[160px]",
|
|
314
|
+
frameStyle: s.height > 0 ? { height: s.height } : {},
|
|
315
|
+
margin: { ...defaultMargin, ...s.margin || {} }
|
|
316
|
+
};
|
|
255
317
|
};
|
|
318
|
+
function thinTicks(values, max) {
|
|
319
|
+
if (max <= 0 || values.length <= max) return void 0;
|
|
320
|
+
if (max === 1) return [values[values.length - 1]];
|
|
321
|
+
const picked = [];
|
|
322
|
+
for (let i = 0; i < max; i++) {
|
|
323
|
+
picked.push(values[Math.round(i * (values.length - 1) / (max - 1))]);
|
|
324
|
+
}
|
|
325
|
+
return [...new Set(picked)];
|
|
326
|
+
}
|
|
256
327
|
var LEGEND_ANCHORS = ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"];
|
|
257
328
|
var withStyleDefaults = (style) => ({
|
|
258
329
|
...DEFAULT_CHART_STYLE,
|
|
@@ -350,10 +421,73 @@ var aggregate = (records, column, fn) => {
|
|
|
350
421
|
return Math.min(...values.map(Number));
|
|
351
422
|
case "max":
|
|
352
423
|
return Math.max(...values.map(Number));
|
|
424
|
+
case "median": {
|
|
425
|
+
const nums = values.map(Number).filter((v) => !Number.isNaN(v)).sort((a, b) => a - b);
|
|
426
|
+
if (!nums.length) return 0;
|
|
427
|
+
const mid = Math.floor(nums.length / 2);
|
|
428
|
+
return nums.length % 2 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
|
|
429
|
+
}
|
|
353
430
|
default:
|
|
354
431
|
return records.length;
|
|
355
432
|
}
|
|
356
433
|
};
|
|
434
|
+
var HEAT_STATS = ["count", "sum", "avg", "min", "max", "median"];
|
|
435
|
+
var cellKey = (row, col) => `${row}\0${col}`;
|
|
436
|
+
var normalizePartition = (cells, method, vmin, vmax) => {
|
|
437
|
+
const out = /* @__PURE__ */ new Map();
|
|
438
|
+
const values = cells.map((c) => c.colorValue).filter((v) => Number.isFinite(v));
|
|
439
|
+
if (!values.length) return out;
|
|
440
|
+
if (method === "rank") {
|
|
441
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
442
|
+
for (const c of cells) {
|
|
443
|
+
const below = sorted.findIndex((v) => v >= c.colorValue);
|
|
444
|
+
const ties = sorted.filter((v) => v === c.colorValue).length;
|
|
445
|
+
out.set(cellKey(c.row, c.col), (below + 0.5 * ties) / sorted.length);
|
|
446
|
+
}
|
|
447
|
+
return out;
|
|
448
|
+
}
|
|
449
|
+
const lo = vmin ?? Math.min(...values);
|
|
450
|
+
const hi = vmax ?? Math.max(...values);
|
|
451
|
+
for (const c of cells) {
|
|
452
|
+
const t = hi === lo ? 0.5 : (c.colorValue - lo) / (hi - lo);
|
|
453
|
+
out.set(cellKey(c.row, c.col), Math.max(0, Math.min(1, t)));
|
|
454
|
+
}
|
|
455
|
+
return out;
|
|
456
|
+
};
|
|
457
|
+
var normalizeCells = (cells, { scope = "global", method = "linear", vmin, vmax } = {}) => {
|
|
458
|
+
const partitions = /* @__PURE__ */ new Map();
|
|
459
|
+
const partOf = (c) => {
|
|
460
|
+
switch (scope) {
|
|
461
|
+
case "row":
|
|
462
|
+
return `r:${c.row}`;
|
|
463
|
+
case "column":
|
|
464
|
+
return `c:${c.col}`;
|
|
465
|
+
case "block":
|
|
466
|
+
return c.block != null ? `b:${c.block}` : "global";
|
|
467
|
+
default:
|
|
468
|
+
return "global";
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
for (const c of cells) {
|
|
472
|
+
const k = partOf(c);
|
|
473
|
+
const part = partitions.get(k);
|
|
474
|
+
if (part) part.push(c);
|
|
475
|
+
else partitions.set(k, [c]);
|
|
476
|
+
}
|
|
477
|
+
const out = /* @__PURE__ */ new Map();
|
|
478
|
+
for (const part of partitions.values()) {
|
|
479
|
+
for (const [k, t] of normalizePartition(part, method, vmin, vmax)) out.set(k, t);
|
|
480
|
+
}
|
|
481
|
+
return out;
|
|
482
|
+
};
|
|
483
|
+
var heatColor = (t) => d3ScaleChromatic.interpolateRdYlGn(Math.max(0, Math.min(1, t)));
|
|
484
|
+
var heatLabelColor = (t) => {
|
|
485
|
+
const m = heatColor(t).match(/\d+/g);
|
|
486
|
+
if (!m) return "#0f172a";
|
|
487
|
+
const [r, g, b] = m.map(Number);
|
|
488
|
+
const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
489
|
+
return lum > 140 ? "#0f172a" : "#f8fafc";
|
|
490
|
+
};
|
|
357
491
|
function MetricView({ records, config, value: presetValue }) {
|
|
358
492
|
const value = react.useMemo(() => {
|
|
359
493
|
if (presetValue != null) return presetValue;
|
|
@@ -448,15 +582,17 @@ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: pre
|
|
|
448
582
|
return Object.entries(groups).map(([key, recs]) => ({ group: key, value: aggregate(recs, valueColumn, aggFn) })).sort((a, b) => b.value - a.value).slice(0, 50);
|
|
449
583
|
}, [records, groupColumn, valueColumn, aggFn, presetData]);
|
|
450
584
|
const data = presetData || computed;
|
|
451
|
-
|
|
585
|
+
const s = withStyleDefaults(style);
|
|
586
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 20, bottom: 60, left: 60 });
|
|
587
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
452
588
|
bar.ResponsiveBar,
|
|
453
589
|
{
|
|
454
590
|
data,
|
|
455
591
|
keys: ["value"],
|
|
456
592
|
indexBy: "group",
|
|
457
|
-
margin
|
|
593
|
+
margin,
|
|
458
594
|
padding: 0.3,
|
|
459
|
-
colors: ["rgba(74, 222, 128, 0.8)"],
|
|
595
|
+
colors: [s.seriesColors[0] || "rgba(74, 222, 128, 0.8)"],
|
|
460
596
|
borderColor: { from: "color", modifiers: [["darker", 1.6]] },
|
|
461
597
|
axisBottom: makeAxis(style, "x", groupColumn),
|
|
462
598
|
axisLeft: makeAxis(style, "y", valueColumn, { numeric: true }),
|
|
@@ -480,16 +616,19 @@ function PieView({ records, groupColumn, data: presetData, style }) {
|
|
|
480
616
|
}, [records, groupColumn, presetData]);
|
|
481
617
|
const data = presetData || computed;
|
|
482
618
|
const legend = legendConfig(style);
|
|
483
|
-
|
|
619
|
+
const s = withStyleDefaults(style);
|
|
620
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 120, bottom: 20, left: 20 });
|
|
621
|
+
const colorById = new Map(data.map((d, i) => [d.id, seriesColor(i, s)]));
|
|
622
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
484
623
|
pie.ResponsivePie,
|
|
485
624
|
{
|
|
486
625
|
data,
|
|
487
|
-
margin
|
|
626
|
+
margin,
|
|
488
627
|
innerRadius: 0.4,
|
|
489
628
|
padAngle: 1,
|
|
490
629
|
cornerRadius: 3,
|
|
491
630
|
activeOuterRadiusOffset: 8,
|
|
492
|
-
colors:
|
|
631
|
+
colors: ((d) => colorById.get(d.id)),
|
|
493
632
|
borderWidth: 1,
|
|
494
633
|
borderColor: { from: "color", modifiers: [["darker", 0.2]] },
|
|
495
634
|
arcLinkLabelsSkipAngle: 10,
|
|
@@ -516,29 +655,54 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
516
655
|
}];
|
|
517
656
|
}, [records, xColumn, yColumn, presetData]);
|
|
518
657
|
const data = presetData || computed;
|
|
519
|
-
|
|
658
|
+
const s = withStyleDefaults(style);
|
|
659
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 20, bottom: 60, left: 60 });
|
|
660
|
+
const colorById = new Map(data.map((serie, i) => [
|
|
661
|
+
serie.id,
|
|
662
|
+
s.seriesColors.length || data.length > 1 ? seriesColor(i, s) : "rgba(96, 165, 250, 0.9)"
|
|
663
|
+
]));
|
|
664
|
+
const tickValues = thinTicks(data[0]?.data.map((d) => d.x) ?? [], s.maxXTicks);
|
|
665
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
520
666
|
line.ResponsiveLine,
|
|
521
667
|
{
|
|
522
668
|
data,
|
|
523
|
-
margin
|
|
669
|
+
margin,
|
|
524
670
|
xScale: { type: "point" },
|
|
525
671
|
yScale: { type: "linear", min: "auto", max: "auto" },
|
|
526
672
|
curve: "monotoneX",
|
|
527
673
|
enableArea: true,
|
|
528
674
|
areaOpacity: 0.15,
|
|
529
|
-
colors:
|
|
675
|
+
colors: ((serie) => colorById.get(serie.id)),
|
|
530
676
|
pointSize: (data[0]?.data.length ?? 0) > 50 ? 0 : 6,
|
|
531
677
|
pointColor: { theme: "background" },
|
|
532
678
|
pointBorderWidth: 2,
|
|
533
679
|
pointBorderColor: { from: "serieColor" },
|
|
534
680
|
enableGridX: false,
|
|
535
|
-
axisBottom: makeAxis(style, "x", xColumn),
|
|
681
|
+
axisBottom: { ...makeAxis(style, "x", xColumn), ...tickValues ? { tickValues } : {} },
|
|
536
682
|
axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
|
|
537
683
|
useMesh: true,
|
|
538
684
|
theme: buildNivoTheme(style)
|
|
539
685
|
}
|
|
540
686
|
) });
|
|
541
687
|
}
|
|
688
|
+
function SparklineView({ data, height = 56, color = "#3987e5" }) {
|
|
689
|
+
const gradientId = react.useId();
|
|
690
|
+
const width = 260;
|
|
691
|
+
const pts = data && data.length ? data : [0];
|
|
692
|
+
const max = Math.max(...pts, 1);
|
|
693
|
+
const dx = pts.length > 1 ? width / (pts.length - 1) : width;
|
|
694
|
+
const xy = (v, i) => [i * dx, height - v / max * (height - 4) - 2];
|
|
695
|
+
const line = pts.map((v, i) => `${i === 0 ? "M" : "L"} ${xy(v, i)[0].toFixed(1)} ${xy(v, i)[1].toFixed(1)}`).join(" ");
|
|
696
|
+
const area = `${line} L ${width} ${height} L 0 ${height} Z`;
|
|
697
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: `0 0 ${width} ${height}`, height, className: "w-full", preserveAspectRatio: "none", children: [
|
|
698
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
|
|
699
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: color, stopOpacity: "0.25" }),
|
|
700
|
+
/* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
|
|
701
|
+
] }) }),
|
|
702
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: area, fill: `url(#${gradientId})` }),
|
|
703
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: line, fill: "none", stroke: color, strokeWidth: "2", vectorEffect: "non-scaling-stroke" })
|
|
704
|
+
] });
|
|
705
|
+
}
|
|
542
706
|
function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
543
707
|
const computed = react.useMemo(() => {
|
|
544
708
|
if (presetData || !records) return [];
|
|
@@ -546,14 +710,20 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
|
|
|
546
710
|
return [{ id: `${xColumn} vs ${yColumn}`, data: points }];
|
|
547
711
|
}, [records, xColumn, yColumn, presetData]);
|
|
548
712
|
const data = presetData || computed;
|
|
549
|
-
|
|
713
|
+
const s = withStyleDefaults(style);
|
|
714
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 20, bottom: 60, left: 60 });
|
|
715
|
+
const colorById = new Map(data.map((serie, i) => [
|
|
716
|
+
serie.id,
|
|
717
|
+
s.seriesColors.length || data.length > 1 ? seriesColor(i, s) : "rgba(167, 139, 250, 0.7)"
|
|
718
|
+
]));
|
|
719
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
550
720
|
scatterplot.ResponsiveScatterPlot,
|
|
551
721
|
{
|
|
552
722
|
data,
|
|
553
|
-
margin
|
|
723
|
+
margin,
|
|
554
724
|
xScale: { type: "linear", min: "auto", max: "auto" },
|
|
555
725
|
yScale: { type: "linear", min: "auto", max: "auto" },
|
|
556
|
-
colors:
|
|
726
|
+
colors: ((serie) => colorById.get(serie.serieId ?? serie.id ?? "")),
|
|
557
727
|
nodeSize: 6,
|
|
558
728
|
axisBottom: makeAxis(style, "x", xColumn, { numeric: true }),
|
|
559
729
|
axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
|
|
@@ -649,8 +819,8 @@ function HeatmapView({
|
|
|
649
819
|
if (!data.length || !data[0].data.length) {
|
|
650
820
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-midnight-text-muted", children: "Not enough distinct values for a heatmap" });
|
|
651
821
|
}
|
|
652
|
-
const margin = showTotals ? { top: 60, right: 70, bottom: 60, left: 100 } : { top: 60, right: 20, bottom: 20, left: 100 };
|
|
653
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className:
|
|
822
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, showTotals ? { top: 60, right: 70, bottom: 60, left: 100 } : { top: 60, right: 20, bottom: 20, left: 100 });
|
|
823
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
654
824
|
heatmap.ResponsiveHeatMap,
|
|
655
825
|
{
|
|
656
826
|
data,
|
|
@@ -669,6 +839,160 @@ function HeatmapView({
|
|
|
669
839
|
}
|
|
670
840
|
) });
|
|
671
841
|
}
|
|
842
|
+
var MAX_AXIS2 = 30;
|
|
843
|
+
var TOTAL_ID = "\u03A3";
|
|
844
|
+
var NULL = "(null)";
|
|
845
|
+
var fmtNum2 = (n) => {
|
|
846
|
+
if (typeof n !== "number" || Number.isNaN(n)) return "";
|
|
847
|
+
return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
848
|
+
};
|
|
849
|
+
var orderIds2 = (ids, margin, order) => {
|
|
850
|
+
const sorted = [...ids];
|
|
851
|
+
if (order.endsWith("-desc")) sorted.sort((a, b) => (margin[b]?.lv ?? 0) - (margin[a]?.lv ?? 0));
|
|
852
|
+
else if (order.endsWith("-asc")) sorted.sort((a, b) => (margin[a]?.lv ?? 0) - (margin[b]?.lv ?? 0));
|
|
853
|
+
else sorted.sort();
|
|
854
|
+
return sorted;
|
|
855
|
+
};
|
|
856
|
+
function fromRecords(records, rowCol, colCol, valueCol, labelStat, colorStat, blockField) {
|
|
857
|
+
const col = valueCol || rowCol;
|
|
858
|
+
const statOf = (rs, s) => valueCol ? aggregate(rs, col, s) : rs.length;
|
|
859
|
+
const both = (rs) => ({ lv: statOf(rs, labelStat), cv: statOf(rs, colorStat) });
|
|
860
|
+
const byRow = groupBy(records, rowCol);
|
|
861
|
+
const rowIds = Object.keys(byRow);
|
|
862
|
+
const colIds = [...new Set(records.map((r) => String(r[colCol] ?? NULL)))];
|
|
863
|
+
const cells = {};
|
|
864
|
+
for (const rid of rowIds) {
|
|
865
|
+
cells[rid] = {};
|
|
866
|
+
const byCol2 = groupBy(byRow[rid], colCol);
|
|
867
|
+
for (const cid of colIds) {
|
|
868
|
+
const cellRows = byCol2[cid] || [];
|
|
869
|
+
const stat = both(cellRows);
|
|
870
|
+
if (blockField && cellRows.length) stat.block = String(cellRows[0][blockField] ?? NULL);
|
|
871
|
+
cells[rid][cid] = stat;
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
const rowMargin = {};
|
|
875
|
+
for (const rid of rowIds) rowMargin[rid] = both(byRow[rid]);
|
|
876
|
+
const colMargin = {};
|
|
877
|
+
const byCol = groupBy(records, colCol);
|
|
878
|
+
for (const cid of colIds) colMargin[cid] = both(byCol[cid] || []);
|
|
879
|
+
return { rowIds, colIds, cells, rowMargin, colMargin, grand: both(records) };
|
|
880
|
+
}
|
|
881
|
+
function fromData(data) {
|
|
882
|
+
const cells = {};
|
|
883
|
+
const rowMargin = {};
|
|
884
|
+
const colMargin = {};
|
|
885
|
+
let grand = { lv: 0, cv: 0 };
|
|
886
|
+
const rowIds = [];
|
|
887
|
+
const colIds = [];
|
|
888
|
+
for (const d of data) {
|
|
889
|
+
const stat = { lv: d.lv, cv: d.cv, ...d.block != null ? { block: d.block } : {} };
|
|
890
|
+
if (d.gr && d.gc) {
|
|
891
|
+
grand = stat;
|
|
892
|
+
continue;
|
|
893
|
+
}
|
|
894
|
+
if (d.gr) {
|
|
895
|
+
if (d.c != null) colMargin[d.c] = stat;
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
if (d.gc) {
|
|
899
|
+
if (d.r != null) rowMargin[d.r] = stat;
|
|
900
|
+
continue;
|
|
901
|
+
}
|
|
902
|
+
const r = d.r ?? NULL, c = d.c ?? NULL;
|
|
903
|
+
if (!cells[r]) {
|
|
904
|
+
cells[r] = {};
|
|
905
|
+
rowIds.push(r);
|
|
906
|
+
}
|
|
907
|
+
if (!colIds.includes(c)) colIds.push(c);
|
|
908
|
+
cells[r][c] = stat;
|
|
909
|
+
}
|
|
910
|
+
return { rowIds, colIds, cells, rowMargin, colMargin, grand };
|
|
911
|
+
}
|
|
912
|
+
function HeatmapPlusView({
|
|
913
|
+
records,
|
|
914
|
+
rowColumn,
|
|
915
|
+
colColumn,
|
|
916
|
+
valueColumn,
|
|
917
|
+
labelStat = "count",
|
|
918
|
+
colorStat = "count",
|
|
919
|
+
blockField,
|
|
920
|
+
data: presetData,
|
|
921
|
+
scope = "global",
|
|
922
|
+
method = "linear",
|
|
923
|
+
vmin,
|
|
924
|
+
vmax,
|
|
925
|
+
rowOrder = "alpha",
|
|
926
|
+
colOrder = "alpha",
|
|
927
|
+
showMargins = false,
|
|
928
|
+
style
|
|
929
|
+
}) {
|
|
930
|
+
const s = withStyleDefaults(style);
|
|
931
|
+
const model = react.useMemo(() => {
|
|
932
|
+
if (presetData) return fromData(presetData);
|
|
933
|
+
if (records) return fromRecords(records, rowColumn, colColumn, valueColumn, labelStat, colorStat, blockField);
|
|
934
|
+
return null;
|
|
935
|
+
}, [presetData, records, rowColumn, colColumn, valueColumn, labelStat, colorStat, blockField]);
|
|
936
|
+
const built = react.useMemo(() => {
|
|
937
|
+
if (!model || !model.rowIds.length || !model.colIds.length) return null;
|
|
938
|
+
const { cells, rowMargin, colMargin, grand } = model;
|
|
939
|
+
const rows = orderIds2(model.rowIds, rowMargin, rowOrder).slice(0, MAX_AXIS2);
|
|
940
|
+
const cols = orderIds2(model.colIds, colMargin, colOrder).slice(0, MAX_AXIS2);
|
|
941
|
+
const bodyCells = [];
|
|
942
|
+
for (const rid of rows) for (const cid of cols) {
|
|
943
|
+
bodyCells.push({ row: rid, col: cid, colorValue: cells[rid]?.[cid]?.cv ?? NaN, block: cells[rid]?.[cid]?.block });
|
|
944
|
+
}
|
|
945
|
+
const t2 = new Map(normalizeCells(bodyCells, { scope, method, vmin, vmax }));
|
|
946
|
+
if (showMargins) {
|
|
947
|
+
const rowMc = rows.map((rid) => ({ row: rid, col: TOTAL_ID, colorValue: rowMargin[rid]?.cv ?? NaN }));
|
|
948
|
+
const colMc = cols.map((cid) => ({ row: TOTAL_ID, col: cid, colorValue: colMargin[cid]?.cv ?? NaN }));
|
|
949
|
+
for (const [k, v] of normalizeCells(rowMc, { scope: "global", method })) t2.set(k, v);
|
|
950
|
+
for (const [k, v] of normalizeCells(colMc, { scope: "global", method })) t2.set(k, v);
|
|
951
|
+
t2.set(cellKey(TOTAL_ID, TOTAL_ID), 0.5);
|
|
952
|
+
}
|
|
953
|
+
const colKeys = showMargins ? [...cols, TOTAL_ID] : cols;
|
|
954
|
+
const nivoData2 = rows.map((rid) => ({
|
|
955
|
+
id: rid,
|
|
956
|
+
data: colKeys.map((cid) => ({
|
|
957
|
+
x: cid,
|
|
958
|
+
y: cid === TOTAL_ID ? rowMargin[rid]?.lv ?? 0 : cells[rid]?.[cid]?.lv ?? 0
|
|
959
|
+
}))
|
|
960
|
+
}));
|
|
961
|
+
if (showMargins) {
|
|
962
|
+
nivoData2.push({
|
|
963
|
+
id: TOTAL_ID,
|
|
964
|
+
data: colKeys.map((cid) => ({
|
|
965
|
+
x: cid,
|
|
966
|
+
y: cid === TOTAL_ID ? grand.lv : colMargin[cid]?.lv ?? 0
|
|
967
|
+
}))
|
|
968
|
+
});
|
|
969
|
+
}
|
|
970
|
+
return { nivoData: nivoData2, t: t2 };
|
|
971
|
+
}, [model, rowOrder, colOrder, scope, method, vmin, vmax, showMargins]);
|
|
972
|
+
if (!built) {
|
|
973
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-midnight-text-muted", children: "Not enough distinct values for a heatmap" });
|
|
974
|
+
}
|
|
975
|
+
const { nivoData, t } = built;
|
|
976
|
+
const tAt = (serieId, x) => t.get(cellKey(serieId, x)) ?? 0;
|
|
977
|
+
const { frameClass, frameStyle, margin } = chartSizing(style, { top: 60, right: 20, bottom: showMargins ? 60 : 20, left: 100 });
|
|
978
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
979
|
+
heatmap.ResponsiveHeatMap,
|
|
980
|
+
{
|
|
981
|
+
data: nivoData,
|
|
982
|
+
margin,
|
|
983
|
+
valueFormat: ((v) => fmtNum2(v)),
|
|
984
|
+
axisTop: { tickSize: 5, tickPadding: 5, tickRotation: s.xTickRotation, legend: s.showXLegend ? s.xAxisLabel || colColumn : "", legendPosition: s.xLegendPosition, legendOffset: -50 },
|
|
985
|
+
axisLeft: { tickSize: 5, tickPadding: 5, legend: s.showYLegend ? s.yAxisLabel || rowColumn : "", legendPosition: s.yLegendPosition, legendOffset: -80 },
|
|
986
|
+
colors: ((cell) => heatColor(tAt(cell.serieId, cell.data.x))),
|
|
987
|
+
emptyColor: "#1e293b",
|
|
988
|
+
borderWidth: 1,
|
|
989
|
+
borderColor: "#334155",
|
|
990
|
+
labelTextColor: ((cell) => heatLabelColor(tAt(cell.serieId, cell.data.x))),
|
|
991
|
+
hoverTarget: "cell",
|
|
992
|
+
theme: buildNivoTheme(style)
|
|
993
|
+
}
|
|
994
|
+
) });
|
|
995
|
+
}
|
|
672
996
|
function PivotView({ records }) {
|
|
673
997
|
const [pivotState, setPivotState] = react.useState({});
|
|
674
998
|
if (!records?.length) {
|
|
@@ -1205,6 +1529,7 @@ var CHART_TYPES = [
|
|
|
1205
1529
|
{ id: "line", icon: lucideReact.TrendingUp, label: "Line" },
|
|
1206
1530
|
{ id: "scatter", icon: lucideReact.ScatterChart, label: "Scatter" },
|
|
1207
1531
|
{ id: "heatmap", icon: lucideReact.LayoutGrid, label: "Heatmap" },
|
|
1532
|
+
{ id: "heatmap-plus", icon: lucideReact.Grid3x3, label: "Heatmap+" },
|
|
1208
1533
|
{ id: "grouped-bar", icon: lucideReact.BarChart3, label: "Grouped Bar" }
|
|
1209
1534
|
];
|
|
1210
1535
|
var AGG_OPTIONS = ["count", "distinct", "sum", "avg", "min", "max"];
|
|
@@ -1225,6 +1550,17 @@ var ORDER_OPTIONS = [
|
|
|
1225
1550
|
{ id: "total-asc", label: "Total \u2191" }
|
|
1226
1551
|
];
|
|
1227
1552
|
var MARGIN_AGGS = ["sum", "avg", "min", "max", "count"].map((a) => ({ id: a, label: a }));
|
|
1553
|
+
var HEAT_STAT_OPTIONS = HEAT_STATS.map((a) => ({ id: a, label: a }));
|
|
1554
|
+
var SCOPE_OPTIONS = [
|
|
1555
|
+
{ id: "global", label: "Global" },
|
|
1556
|
+
{ id: "row", label: "Per row" },
|
|
1557
|
+
{ id: "column", label: "Per column" },
|
|
1558
|
+
{ id: "block", label: "Per block" }
|
|
1559
|
+
];
|
|
1560
|
+
var METHOD_OPTIONS = [
|
|
1561
|
+
{ id: "linear", label: "Linear" },
|
|
1562
|
+
{ id: "rank", label: "Rank" }
|
|
1563
|
+
];
|
|
1228
1564
|
var FILTER_OP_OPTIONS = FILTER_OPS.map((o) => ({ id: o, label: o }));
|
|
1229
1565
|
var FieldPill = ({ name, type, onRemove, onChangeAgg, agg, showAgg }) => {
|
|
1230
1566
|
const typeColor = {
|
|
@@ -1413,6 +1749,7 @@ var ChartPreview = ({
|
|
|
1413
1749
|
sqlLoading,
|
|
1414
1750
|
sqlError,
|
|
1415
1751
|
heatmapOpts = {},
|
|
1752
|
+
heatPlusOpts = {},
|
|
1416
1753
|
style
|
|
1417
1754
|
}) => {
|
|
1418
1755
|
const xCol = xFields[0]?.name;
|
|
@@ -1451,6 +1788,8 @@ var ChartPreview = ({
|
|
|
1451
1788
|
return /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(ScatterView, { data: shaped, xColumn: xCol, yColumn: yCol, style }) });
|
|
1452
1789
|
case "heatmap":
|
|
1453
1790
|
return shaped.length ? /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(HeatmapView, { data: shaped, rowColumn: xCol, colColumn: yCol, ...heatmapOpts, style }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-sm text-midnight-text-muted", children: "No data" });
|
|
1791
|
+
case "heatmap-plus":
|
|
1792
|
+
return shaped.length ? /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(HeatmapPlusView, { data: shaped, rowColumn: xCol, colColumn: yCol, ...heatPlusOpts, style }) }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-sm text-midnight-text-muted", children: "No data" });
|
|
1454
1793
|
default:
|
|
1455
1794
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-sm text-midnight-text-muted", children: "Select a chart type" });
|
|
1456
1795
|
}
|
|
@@ -1472,6 +1811,8 @@ var ChartPreview = ({
|
|
|
1472
1811
|
return xCol && yCol ? /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(ScatterView, { records, xColumn: xCol, yColumn: yCol, style }) }) : null;
|
|
1473
1812
|
case "heatmap":
|
|
1474
1813
|
return xCol && yCol ? /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(HeatmapView, { records, rowColumn: xCol, colColumn: yCol, valueColumn: valueField?.name, aggFn: valueField?.agg || "count", ...heatmapOpts, style }) }) : null;
|
|
1814
|
+
case "heatmap-plus":
|
|
1815
|
+
return xCol && yCol ? /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(HeatmapPlusView, { records, rowColumn: xCol, colColumn: yCol, valueColumn: valueField?.name, ...heatPlusOpts, style }) }) : null;
|
|
1475
1816
|
default:
|
|
1476
1817
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-sm text-midnight-text-muted", children: "Select a chart type" });
|
|
1477
1818
|
}
|
|
@@ -1495,6 +1836,13 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1495
1836
|
const [colOrder, setColOrder] = react.useState("alpha");
|
|
1496
1837
|
const [marginAgg, setMarginAgg] = react.useState("sum");
|
|
1497
1838
|
const [showTotals, setShowTotals] = react.useState(false);
|
|
1839
|
+
const [labelStat, setLabelStat] = react.useState("count");
|
|
1840
|
+
const [colorStat, setColorStat] = react.useState("count");
|
|
1841
|
+
const [scope, setScope] = react.useState("global");
|
|
1842
|
+
const [method, setMethod] = react.useState("linear");
|
|
1843
|
+
const [vmin, setVmin] = react.useState("");
|
|
1844
|
+
const [vmax, setVmax] = react.useState("");
|
|
1845
|
+
const [showMargins, setShowMargins] = react.useState(false);
|
|
1498
1846
|
const [style, setStyle] = react.useState(DEFAULT_CHART_STYLE);
|
|
1499
1847
|
const [showStyle, setShowStyle] = react.useState(false);
|
|
1500
1848
|
const [showFields, setShowFields] = react.useState(false);
|
|
@@ -1503,8 +1851,17 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1503
1851
|
const [sqlLoading, setSqlLoading] = react.useState(false);
|
|
1504
1852
|
const [sqlError, setSqlError] = react.useState(null);
|
|
1505
1853
|
const generatedSql = react.useMemo(
|
|
1506
|
-
() => buildChartSQL({
|
|
1507
|
-
|
|
1854
|
+
() => buildChartSQL({
|
|
1855
|
+
chartType,
|
|
1856
|
+
xFields,
|
|
1857
|
+
yFields,
|
|
1858
|
+
valueField,
|
|
1859
|
+
filters,
|
|
1860
|
+
labelStat,
|
|
1861
|
+
colorStat,
|
|
1862
|
+
blockField: colorField?.name
|
|
1863
|
+
}),
|
|
1864
|
+
[chartType, xFields, yFields, valueField, filters, labelStat, colorStat, colorField]
|
|
1508
1865
|
);
|
|
1509
1866
|
react.useEffect(() => {
|
|
1510
1867
|
if (engineMode !== "sql") return void 0;
|
|
@@ -1602,6 +1959,26 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1602
1959
|
config.marginAgg = marginAgg;
|
|
1603
1960
|
config.showTotals = showTotals;
|
|
1604
1961
|
}
|
|
1962
|
+
if (chartType === "heatmap-plus") {
|
|
1963
|
+
config.row = xFields[0]?.name;
|
|
1964
|
+
config.col = yFields[0]?.name;
|
|
1965
|
+
if (valueField) config.value = valueField.name;
|
|
1966
|
+
else delete config.value;
|
|
1967
|
+
config.labelStat = labelStat;
|
|
1968
|
+
config.colorStat = colorStat;
|
|
1969
|
+
if (colorField) config.blockField = colorField.name;
|
|
1970
|
+
else delete config.blockField;
|
|
1971
|
+
config.scope = scope;
|
|
1972
|
+
config.method = method;
|
|
1973
|
+
const nMin = parseFloat(vmin), nMax = parseFloat(vmax);
|
|
1974
|
+
if (Number.isFinite(nMin)) config.vmin = nMin;
|
|
1975
|
+
else delete config.vmin;
|
|
1976
|
+
if (Number.isFinite(nMax)) config.vmax = nMax;
|
|
1977
|
+
else delete config.vmax;
|
|
1978
|
+
config.rowOrder = rowOrder;
|
|
1979
|
+
config.colOrder = colOrder;
|
|
1980
|
+
config.showMargins = showMargins;
|
|
1981
|
+
}
|
|
1605
1982
|
if (engineMode === "sql" && generatedSql) {
|
|
1606
1983
|
config.sql = generatedSql;
|
|
1607
1984
|
config.chartType = chartType;
|
|
@@ -1622,6 +1999,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1622
1999
|
height: fillContainer ? 8 : 2
|
|
1623
2000
|
});
|
|
1624
2001
|
};
|
|
2002
|
+
const isHeat = chartType === "heatmap" || chartType === "heatmap-plus";
|
|
1625
2003
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full", children: [
|
|
1626
2004
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: `w-[280px] shrink-0 border-r ${theme.border} bg-midnight-elevated overflow-y-auto p-3 space-y-4`, children: [
|
|
1627
2005
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
@@ -1657,30 +2035,30 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1657
2035
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1658
2036
|
DropZone,
|
|
1659
2037
|
{
|
|
1660
|
-
label:
|
|
2038
|
+
label: isHeat ? "Rows" : "X Axis / Group By",
|
|
1661
2039
|
fields: xFields,
|
|
1662
2040
|
columns,
|
|
1663
2041
|
onAdd: (f) => addField("x", f),
|
|
1664
2042
|
onRemove: (i) => removeField("x", i),
|
|
1665
2043
|
onChangeAgg: () => {
|
|
1666
2044
|
},
|
|
1667
|
-
maxFields: chartType === "pie" ||
|
|
2045
|
+
maxFields: chartType === "pie" || isHeat ? 1 : 3
|
|
1668
2046
|
}
|
|
1669
2047
|
),
|
|
1670
2048
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1671
2049
|
DropZone,
|
|
1672
2050
|
{
|
|
1673
|
-
label:
|
|
2051
|
+
label: isHeat ? "Columns" : "Y Axis / Values",
|
|
1674
2052
|
fields: yFields,
|
|
1675
2053
|
columns,
|
|
1676
2054
|
onAdd: (f) => addField("y", f),
|
|
1677
2055
|
onRemove: (i) => removeField("y", i),
|
|
1678
2056
|
onChangeAgg: (i, agg) => changeAgg("y", i, agg),
|
|
1679
|
-
showAgg:
|
|
1680
|
-
maxFields:
|
|
2057
|
+
showAgg: !isHeat,
|
|
2058
|
+
maxFields: isHeat ? 1 : 5
|
|
1681
2059
|
}
|
|
1682
2060
|
),
|
|
1683
|
-
|
|
2061
|
+
isHeat && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1684
2062
|
DropZone,
|
|
1685
2063
|
{
|
|
1686
2064
|
label: "Cell Value (optional \u2014 defaults to count)",
|
|
@@ -1689,10 +2067,93 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1689
2067
|
onAdd: (f) => addField("value", f),
|
|
1690
2068
|
onRemove: () => removeField("value"),
|
|
1691
2069
|
onChangeAgg: (i, agg) => changeAgg("value", i, agg),
|
|
1692
|
-
showAgg:
|
|
2070
|
+
showAgg: chartType === "heatmap",
|
|
1693
2071
|
maxFields: 1
|
|
1694
2072
|
}
|
|
1695
2073
|
),
|
|
2074
|
+
chartType === "heatmap-plus" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
2075
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2076
|
+
DropZone,
|
|
2077
|
+
{
|
|
2078
|
+
label: "Block (optional \u2014 color-scope grouping)",
|
|
2079
|
+
fields: colorField ? [colorField] : [],
|
|
2080
|
+
columns,
|
|
2081
|
+
onAdd: (f) => addField("color", f),
|
|
2082
|
+
onRemove: () => removeField("color"),
|
|
2083
|
+
onChangeAgg: () => {
|
|
2084
|
+
},
|
|
2085
|
+
maxFields: 1
|
|
2086
|
+
}
|
|
2087
|
+
),
|
|
2088
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2089
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Cell Stats" }),
|
|
2090
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2091
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2092
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Label stat" }),
|
|
2093
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: labelStat, options: HEAT_STAT_OPTIONS, onChange: (v) => setLabelStat(v) })
|
|
2094
|
+
] }),
|
|
2095
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2096
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Color stat" }),
|
|
2097
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: colorStat, options: HEAT_STAT_OPTIONS, onChange: (v) => setColorStat(v) })
|
|
2098
|
+
] })
|
|
2099
|
+
] })
|
|
2100
|
+
] }),
|
|
2101
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2102
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Color Engine" }),
|
|
2103
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2104
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2105
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Scale scope" }),
|
|
2106
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: scope, options: SCOPE_OPTIONS, onChange: (v) => setScope(v) })
|
|
2107
|
+
] }),
|
|
2108
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2109
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Scale method" }),
|
|
2110
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: method, options: METHOD_OPTIONS, onChange: (v) => setMethod(v) })
|
|
2111
|
+
] }),
|
|
2112
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2113
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Fixed min / max" }),
|
|
2114
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1", children: [
|
|
2115
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2116
|
+
"input",
|
|
2117
|
+
{
|
|
2118
|
+
value: vmin,
|
|
2119
|
+
onChange: (e) => setVmin(e.target.value),
|
|
2120
|
+
placeholder: "auto",
|
|
2121
|
+
inputMode: "decimal",
|
|
2122
|
+
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"
|
|
2123
|
+
}
|
|
2124
|
+
),
|
|
2125
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2126
|
+
"input",
|
|
2127
|
+
{
|
|
2128
|
+
value: vmax,
|
|
2129
|
+
onChange: (e) => setVmax(e.target.value),
|
|
2130
|
+
placeholder: "auto",
|
|
2131
|
+
inputMode: "decimal",
|
|
2132
|
+
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"
|
|
2133
|
+
}
|
|
2134
|
+
)
|
|
2135
|
+
] })
|
|
2136
|
+
] })
|
|
2137
|
+
] })
|
|
2138
|
+
] }),
|
|
2139
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2140
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Order & Margins" }),
|
|
2141
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2142
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2143
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Order rows" }),
|
|
2144
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: rowOrder, options: ORDER_OPTIONS, onChange: (v) => setRowOrder(v) })
|
|
2145
|
+
] }),
|
|
2146
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2147
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Order columns" }),
|
|
2148
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: colOrder, options: ORDER_OPTIONS, onChange: (v) => setColOrder(v) })
|
|
2149
|
+
] }),
|
|
2150
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
|
|
2151
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: showMargins, onChange: (e) => setShowMargins(e.target.checked), className: "accent-midnight-accent" }),
|
|
2152
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Show margins (row / col / grand)" })
|
|
2153
|
+
] })
|
|
2154
|
+
] })
|
|
2155
|
+
] })
|
|
2156
|
+
] }),
|
|
1696
2157
|
chartType === "heatmap" && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1697
2158
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Order & Totals" }),
|
|
1698
2159
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
@@ -1826,6 +2287,18 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1826
2287
|
sqlLoading,
|
|
1827
2288
|
sqlError,
|
|
1828
2289
|
heatmapOpts: { rowOrder, colOrder, marginAgg, showTotals },
|
|
2290
|
+
heatPlusOpts: {
|
|
2291
|
+
labelStat,
|
|
2292
|
+
colorStat,
|
|
2293
|
+
blockField: colorField?.name,
|
|
2294
|
+
scope,
|
|
2295
|
+
method,
|
|
2296
|
+
vmin: Number.isFinite(parseFloat(vmin)) ? parseFloat(vmin) : void 0,
|
|
2297
|
+
vmax: Number.isFinite(parseFloat(vmax)) ? parseFloat(vmax) : void 0,
|
|
2298
|
+
rowOrder,
|
|
2299
|
+
colOrder,
|
|
2300
|
+
showMargins
|
|
2301
|
+
},
|
|
1829
2302
|
style
|
|
1830
2303
|
}
|
|
1831
2304
|
) })
|
|
@@ -1834,7 +2307,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1834
2307
|
function ViewLoading() {
|
|
1835
2308
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Loading..." });
|
|
1836
2309
|
}
|
|
1837
|
-
var SQL_CHART_TYPES = /* @__PURE__ */ new Set(["bar", "grouped-bar", "pie", "line", "scatter", "heatmap"]);
|
|
2310
|
+
var SQL_CHART_TYPES = /* @__PURE__ */ new Set(["bar", "grouped-bar", "pie", "line", "scatter", "heatmap", "heatmap-plus"]);
|
|
1838
2311
|
var SQL_DATA_TYPES = /* @__PURE__ */ new Set(["table", "metric"]);
|
|
1839
2312
|
var TABLE_PREVIEW_ROWS = 200;
|
|
1840
2313
|
function tableSql(config) {
|
|
@@ -1865,6 +2338,14 @@ function panelToChartConfig(chartType, config) {
|
|
|
1865
2338
|
if (config.col) cfg.yFields = [{ name: config.col }];
|
|
1866
2339
|
if (config.value) cfg.valueField = { name: config.value, agg: config.agg };
|
|
1867
2340
|
break;
|
|
2341
|
+
case "heatmap-plus":
|
|
2342
|
+
if (config.row) cfg.xFields = [{ name: config.row }];
|
|
2343
|
+
if (config.col) cfg.yFields = [{ name: config.col }];
|
|
2344
|
+
if (config.value) cfg.valueField = { name: config.value, agg: config.labelStat };
|
|
2345
|
+
cfg.labelStat = config.labelStat;
|
|
2346
|
+
cfg.colorStat = config.colorStat;
|
|
2347
|
+
cfg.blockField = config.blockField;
|
|
2348
|
+
break;
|
|
1868
2349
|
}
|
|
1869
2350
|
return cfg;
|
|
1870
2351
|
}
|
|
@@ -2013,6 +2494,9 @@ function SqlPanel({ panel }) {
|
|
|
2013
2494
|
case "heatmap":
|
|
2014
2495
|
chart = shaped.length ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "No data" });
|
|
2015
2496
|
break;
|
|
2497
|
+
case "heatmap-plus":
|
|
2498
|
+
chart = shaped.length ? /* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "No data" });
|
|
2499
|
+
break;
|
|
2016
2500
|
default:
|
|
2017
2501
|
chart = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
2018
2502
|
"Unsupported SQL chart: ",
|
|
@@ -2046,6 +2530,8 @@ function PanelContent({ panel, records, columns }) {
|
|
|
2046
2530
|
return config.x && config.y ? /* @__PURE__ */ jsxRuntime.jsx(ScatterView, { records, xColumn: config.x, yColumn: config.y, style: config.style }) : null;
|
|
2047
2531
|
case "heatmap":
|
|
2048
2532
|
return config.row && config.col ? /* @__PURE__ */ jsxRuntime.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;
|
|
2533
|
+
case "heatmap-plus":
|
|
2534
|
+
return config.row && config.col ? /* @__PURE__ */ jsxRuntime.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;
|
|
2049
2535
|
case "pivot":
|
|
2050
2536
|
return /* @__PURE__ */ jsxRuntime.jsx(PivotView, { records });
|
|
2051
2537
|
case "metric":
|
|
@@ -2509,9 +2995,12 @@ exports.BarView = BarView;
|
|
|
2509
2995
|
exports.ChartBuilder = ChartBuilder;
|
|
2510
2996
|
exports.ChartStyleControls = ChartStyleControls;
|
|
2511
2997
|
exports.DEFAULT_CHART_STYLE = DEFAULT_CHART_STYLE;
|
|
2998
|
+
exports.DEFAULT_SERIES_COLORS = DEFAULT_SERIES_COLORS;
|
|
2512
2999
|
exports.DashboardProvider = DashboardProvider;
|
|
2513
3000
|
exports.DashboardRenderer = DashboardRenderer;
|
|
2514
3001
|
exports.DataExplorer = DataExplorer;
|
|
3002
|
+
exports.HEAT_STATS = HEAT_STATS;
|
|
3003
|
+
exports.HeatmapPlusView = HeatmapPlusView;
|
|
2515
3004
|
exports.HeatmapView = HeatmapView;
|
|
2516
3005
|
exports.InsightView = InsightView;
|
|
2517
3006
|
exports.LEGEND_ANCHORS = LEGEND_ANCHORS;
|
|
@@ -2520,19 +3009,27 @@ exports.MetricView = MetricView;
|
|
|
2520
3009
|
exports.PanelChart = PanelChart;
|
|
2521
3010
|
exports.PieView = PieView;
|
|
2522
3011
|
exports.PivotView = PivotView;
|
|
3012
|
+
exports.SERIES_OVERFLOW_COLOR = SERIES_OVERFLOW_COLOR;
|
|
2523
3013
|
exports.ScatterView = ScatterView;
|
|
3014
|
+
exports.SparklineView = SparklineView;
|
|
2524
3015
|
exports.SqlConsole = SqlConsole;
|
|
2525
3016
|
exports.aggExpr = aggExpr;
|
|
2526
3017
|
exports.aggregate = aggregate;
|
|
2527
3018
|
exports.axisLegend = axisLegend;
|
|
2528
3019
|
exports.buildChartSQL = buildChartSQL;
|
|
2529
3020
|
exports.buildNivoTheme = buildNivoTheme;
|
|
3021
|
+
exports.chartSizing = chartSizing;
|
|
2530
3022
|
exports.compileWhere = compileWhere;
|
|
2531
3023
|
exports.groupBy = groupBy;
|
|
3024
|
+
exports.heatColor = heatColor;
|
|
3025
|
+
exports.heatLabelColor = heatLabelColor;
|
|
2532
3026
|
exports.legendConfig = legendConfig;
|
|
3027
|
+
exports.normalizeCells = normalizeCells;
|
|
2533
3028
|
exports.qIdent = qIdent;
|
|
2534
3029
|
exports.qLit = qLit;
|
|
3030
|
+
exports.seriesColor = seriesColor;
|
|
2535
3031
|
exports.shapeChartData = shapeChartData;
|
|
3032
|
+
exports.thinTicks = thinTicks;
|
|
2536
3033
|
exports.useCapabilities = useCapabilities;
|
|
2537
3034
|
exports.useDashboard = useDashboard;
|
|
2538
3035
|
exports.withStyleDefaults = withStyleDefaults;
|