@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.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
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
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: { top: 20, right: 20, bottom: 60, left: 60 },
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
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
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: { top: 20, right: 120, bottom: 20, left: 20 },
626
+ margin,
488
627
  innerRadius: 0.4,
489
628
  padAngle: 1,
490
629
  cornerRadius: 3,
491
630
  activeOuterRadiusOffset: 8,
492
- colors: { scheme: "set2" },
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
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
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: { top: 20, right: 20, bottom: 60, left: 60 },
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: ["rgba(96, 165, 250, 0.9)"],
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
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
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: { top: 20, right: 20, bottom: 60, left: 60 },
723
+ margin,
554
724
  xScale: { type: "linear", min: "auto", max: "auto" },
555
725
  yScale: { type: "linear", min: "auto", max: "auto" },
556
- colors: ["rgba(167, 139, 250, 0.7)"],
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: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
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({ chartType, xFields, yFields, valueField, filters }),
1507
- [chartType, xFields, yFields, valueField, filters]
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: chartType === "heatmap" ? "Rows" : "X Axis / Group By",
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" || chartType === "heatmap" ? 1 : 3
2045
+ maxFields: chartType === "pie" || isHeat ? 1 : 3
1668
2046
  }
1669
2047
  ),
1670
2048
  /* @__PURE__ */ jsxRuntime.jsx(
1671
2049
  DropZone,
1672
2050
  {
1673
- label: chartType === "heatmap" ? "Columns" : "Y Axis / Values",
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: chartType !== "heatmap",
1680
- maxFields: chartType === "heatmap" ? 1 : 5
2057
+ showAgg: !isHeat,
2058
+ maxFields: isHeat ? 1 : 5
1681
2059
  }
1682
2060
  ),
1683
- chartType === "heatmap" && /* @__PURE__ */ jsxRuntime.jsx(
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: true,
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;