@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/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
- return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
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: { top: 20, right: 20, bottom: 60, left: 60 },
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
- return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
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: { top: 20, right: 120, bottom: 20, left: 20 },
618
+ margin,
480
619
  innerRadius: 0.4,
481
620
  padAngle: 1,
482
621
  cornerRadius: 3,
483
622
  activeOuterRadiusOffset: 8,
484
- colors: { scheme: "set2" },
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
- return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
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: { top: 20, right: 20, bottom: 60, left: 60 },
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: ["rgba(96, 165, 250, 0.9)"],
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
- return /* @__PURE__ */ jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
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: { top: 20, right: 20, bottom: 60, left: 60 },
715
+ margin,
546
716
  xScale: { type: "linear", min: "auto", max: "auto" },
547
717
  yScale: { type: "linear", min: "auto", max: "auto" },
548
- colors: ["rgba(167, 139, 250, 0.7)"],
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: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsx(
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({ chartType, xFields, yFields, valueField, filters }),
1499
- [chartType, xFields, yFields, valueField, filters]
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: chartType === "heatmap" ? "Rows" : "X Axis / Group By",
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" || chartType === "heatmap" ? 1 : 3
2037
+ maxFields: chartType === "pie" || isHeat ? 1 : 3
1660
2038
  }
1661
2039
  ),
1662
2040
  /* @__PURE__ */ jsx(
1663
2041
  DropZone,
1664
2042
  {
1665
- label: chartType === "heatmap" ? "Columns" : "Y Axis / Values",
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: chartType !== "heatmap",
1672
- maxFields: chartType === "heatmap" ? 1 : 5
2049
+ showAgg: !isHeat,
2050
+ maxFields: isHeat ? 1 : 5
1673
2051
  }
1674
2052
  ),
1675
- chartType === "heatmap" && /* @__PURE__ */ jsx(
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: true,
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