@quantumwake/terminal-ux-dashboard-components 0.1.18 → 0.1.19
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 +419 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +75 -8
- package/dist/index.d.ts +75 -8
- package/dist/index.js +416 -13
- 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
|
}
|
|
@@ -350,10 +375,73 @@ var aggregate = (records, column, fn) => {
|
|
|
350
375
|
return Math.min(...values.map(Number));
|
|
351
376
|
case "max":
|
|
352
377
|
return Math.max(...values.map(Number));
|
|
378
|
+
case "median": {
|
|
379
|
+
const nums = values.map(Number).filter((v) => !Number.isNaN(v)).sort((a, b) => a - b);
|
|
380
|
+
if (!nums.length) return 0;
|
|
381
|
+
const mid = Math.floor(nums.length / 2);
|
|
382
|
+
return nums.length % 2 ? nums[mid] : (nums[mid - 1] + nums[mid]) / 2;
|
|
383
|
+
}
|
|
353
384
|
default:
|
|
354
385
|
return records.length;
|
|
355
386
|
}
|
|
356
387
|
};
|
|
388
|
+
var HEAT_STATS = ["count", "sum", "avg", "min", "max", "median"];
|
|
389
|
+
var cellKey = (row, col) => `${row}\0${col}`;
|
|
390
|
+
var normalizePartition = (cells, method, vmin, vmax) => {
|
|
391
|
+
const out = /* @__PURE__ */ new Map();
|
|
392
|
+
const values = cells.map((c) => c.colorValue).filter((v) => Number.isFinite(v));
|
|
393
|
+
if (!values.length) return out;
|
|
394
|
+
if (method === "rank") {
|
|
395
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
396
|
+
for (const c of cells) {
|
|
397
|
+
const below = sorted.findIndex((v) => v >= c.colorValue);
|
|
398
|
+
const ties = sorted.filter((v) => v === c.colorValue).length;
|
|
399
|
+
out.set(cellKey(c.row, c.col), (below + 0.5 * ties) / sorted.length);
|
|
400
|
+
}
|
|
401
|
+
return out;
|
|
402
|
+
}
|
|
403
|
+
const lo = vmin ?? Math.min(...values);
|
|
404
|
+
const hi = vmax ?? Math.max(...values);
|
|
405
|
+
for (const c of cells) {
|
|
406
|
+
const t = hi === lo ? 0.5 : (c.colorValue - lo) / (hi - lo);
|
|
407
|
+
out.set(cellKey(c.row, c.col), Math.max(0, Math.min(1, t)));
|
|
408
|
+
}
|
|
409
|
+
return out;
|
|
410
|
+
};
|
|
411
|
+
var normalizeCells = (cells, { scope = "global", method = "linear", vmin, vmax } = {}) => {
|
|
412
|
+
const partitions = /* @__PURE__ */ new Map();
|
|
413
|
+
const partOf = (c) => {
|
|
414
|
+
switch (scope) {
|
|
415
|
+
case "row":
|
|
416
|
+
return `r:${c.row}`;
|
|
417
|
+
case "column":
|
|
418
|
+
return `c:${c.col}`;
|
|
419
|
+
case "block":
|
|
420
|
+
return c.block != null ? `b:${c.block}` : "global";
|
|
421
|
+
default:
|
|
422
|
+
return "global";
|
|
423
|
+
}
|
|
424
|
+
};
|
|
425
|
+
for (const c of cells) {
|
|
426
|
+
const k = partOf(c);
|
|
427
|
+
const part = partitions.get(k);
|
|
428
|
+
if (part) part.push(c);
|
|
429
|
+
else partitions.set(k, [c]);
|
|
430
|
+
}
|
|
431
|
+
const out = /* @__PURE__ */ new Map();
|
|
432
|
+
for (const part of partitions.values()) {
|
|
433
|
+
for (const [k, t] of normalizePartition(part, method, vmin, vmax)) out.set(k, t);
|
|
434
|
+
}
|
|
435
|
+
return out;
|
|
436
|
+
};
|
|
437
|
+
var heatColor = (t) => d3ScaleChromatic.interpolateRdYlGn(Math.max(0, Math.min(1, t)));
|
|
438
|
+
var heatLabelColor = (t) => {
|
|
439
|
+
const m = heatColor(t).match(/\d+/g);
|
|
440
|
+
if (!m) return "#0f172a";
|
|
441
|
+
const [r, g, b] = m.map(Number);
|
|
442
|
+
const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
|
|
443
|
+
return lum > 140 ? "#0f172a" : "#f8fafc";
|
|
444
|
+
};
|
|
357
445
|
function MetricView({ records, config, value: presetValue }) {
|
|
358
446
|
const value = react.useMemo(() => {
|
|
359
447
|
if (presetValue != null) return presetValue;
|
|
@@ -669,6 +757,159 @@ function HeatmapView({
|
|
|
669
757
|
}
|
|
670
758
|
) });
|
|
671
759
|
}
|
|
760
|
+
var MAX_AXIS2 = 30;
|
|
761
|
+
var TOTAL_ID = "\u03A3";
|
|
762
|
+
var NULL = "(null)";
|
|
763
|
+
var fmtNum2 = (n) => {
|
|
764
|
+
if (typeof n !== "number" || Number.isNaN(n)) return "";
|
|
765
|
+
return Number.isInteger(n) ? n.toLocaleString() : n.toLocaleString(void 0, { maximumFractionDigits: 2 });
|
|
766
|
+
};
|
|
767
|
+
var orderIds2 = (ids, margin, order) => {
|
|
768
|
+
const sorted = [...ids];
|
|
769
|
+
if (order.endsWith("-desc")) sorted.sort((a, b) => (margin[b]?.lv ?? 0) - (margin[a]?.lv ?? 0));
|
|
770
|
+
else if (order.endsWith("-asc")) sorted.sort((a, b) => (margin[a]?.lv ?? 0) - (margin[b]?.lv ?? 0));
|
|
771
|
+
else sorted.sort();
|
|
772
|
+
return sorted;
|
|
773
|
+
};
|
|
774
|
+
function fromRecords(records, rowCol, colCol, valueCol, labelStat, colorStat, blockField) {
|
|
775
|
+
const col = valueCol || rowCol;
|
|
776
|
+
const statOf = (rs, s) => valueCol ? aggregate(rs, col, s) : rs.length;
|
|
777
|
+
const both = (rs) => ({ lv: statOf(rs, labelStat), cv: statOf(rs, colorStat) });
|
|
778
|
+
const byRow = groupBy(records, rowCol);
|
|
779
|
+
const rowIds = Object.keys(byRow);
|
|
780
|
+
const colIds = [...new Set(records.map((r) => String(r[colCol] ?? NULL)))];
|
|
781
|
+
const cells = {};
|
|
782
|
+
for (const rid of rowIds) {
|
|
783
|
+
cells[rid] = {};
|
|
784
|
+
const byCol2 = groupBy(byRow[rid], colCol);
|
|
785
|
+
for (const cid of colIds) {
|
|
786
|
+
const cellRows = byCol2[cid] || [];
|
|
787
|
+
const stat = both(cellRows);
|
|
788
|
+
if (blockField && cellRows.length) stat.block = String(cellRows[0][blockField] ?? NULL);
|
|
789
|
+
cells[rid][cid] = stat;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
const rowMargin = {};
|
|
793
|
+
for (const rid of rowIds) rowMargin[rid] = both(byRow[rid]);
|
|
794
|
+
const colMargin = {};
|
|
795
|
+
const byCol = groupBy(records, colCol);
|
|
796
|
+
for (const cid of colIds) colMargin[cid] = both(byCol[cid] || []);
|
|
797
|
+
return { rowIds, colIds, cells, rowMargin, colMargin, grand: both(records) };
|
|
798
|
+
}
|
|
799
|
+
function fromData(data) {
|
|
800
|
+
const cells = {};
|
|
801
|
+
const rowMargin = {};
|
|
802
|
+
const colMargin = {};
|
|
803
|
+
let grand = { lv: 0, cv: 0 };
|
|
804
|
+
const rowIds = [];
|
|
805
|
+
const colIds = [];
|
|
806
|
+
for (const d of data) {
|
|
807
|
+
const stat = { lv: d.lv, cv: d.cv, ...d.block != null ? { block: d.block } : {} };
|
|
808
|
+
if (d.gr && d.gc) {
|
|
809
|
+
grand = stat;
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
if (d.gr) {
|
|
813
|
+
if (d.c != null) colMargin[d.c] = stat;
|
|
814
|
+
continue;
|
|
815
|
+
}
|
|
816
|
+
if (d.gc) {
|
|
817
|
+
if (d.r != null) rowMargin[d.r] = stat;
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
const r = d.r ?? NULL, c = d.c ?? NULL;
|
|
821
|
+
if (!cells[r]) {
|
|
822
|
+
cells[r] = {};
|
|
823
|
+
rowIds.push(r);
|
|
824
|
+
}
|
|
825
|
+
if (!colIds.includes(c)) colIds.push(c);
|
|
826
|
+
cells[r][c] = stat;
|
|
827
|
+
}
|
|
828
|
+
return { rowIds, colIds, cells, rowMargin, colMargin, grand };
|
|
829
|
+
}
|
|
830
|
+
function HeatmapPlusView({
|
|
831
|
+
records,
|
|
832
|
+
rowColumn,
|
|
833
|
+
colColumn,
|
|
834
|
+
valueColumn,
|
|
835
|
+
labelStat = "count",
|
|
836
|
+
colorStat = "count",
|
|
837
|
+
blockField,
|
|
838
|
+
data: presetData,
|
|
839
|
+
scope = "global",
|
|
840
|
+
method = "linear",
|
|
841
|
+
vmin,
|
|
842
|
+
vmax,
|
|
843
|
+
rowOrder = "alpha",
|
|
844
|
+
colOrder = "alpha",
|
|
845
|
+
showMargins = false,
|
|
846
|
+
style
|
|
847
|
+
}) {
|
|
848
|
+
const s = withStyleDefaults(style);
|
|
849
|
+
const model = react.useMemo(() => {
|
|
850
|
+
if (presetData) return fromData(presetData);
|
|
851
|
+
if (records) return fromRecords(records, rowColumn, colColumn, valueColumn, labelStat, colorStat, blockField);
|
|
852
|
+
return null;
|
|
853
|
+
}, [presetData, records, rowColumn, colColumn, valueColumn, labelStat, colorStat, blockField]);
|
|
854
|
+
const built = react.useMemo(() => {
|
|
855
|
+
if (!model || !model.rowIds.length || !model.colIds.length) return null;
|
|
856
|
+
const { cells, rowMargin, colMargin, grand } = model;
|
|
857
|
+
const rows = orderIds2(model.rowIds, rowMargin, rowOrder).slice(0, MAX_AXIS2);
|
|
858
|
+
const cols = orderIds2(model.colIds, colMargin, colOrder).slice(0, MAX_AXIS2);
|
|
859
|
+
const bodyCells = [];
|
|
860
|
+
for (const rid of rows) for (const cid of cols) {
|
|
861
|
+
bodyCells.push({ row: rid, col: cid, colorValue: cells[rid]?.[cid]?.cv ?? NaN, block: cells[rid]?.[cid]?.block });
|
|
862
|
+
}
|
|
863
|
+
const t2 = new Map(normalizeCells(bodyCells, { scope, method, vmin, vmax }));
|
|
864
|
+
if (showMargins) {
|
|
865
|
+
const rowMc = rows.map((rid) => ({ row: rid, col: TOTAL_ID, colorValue: rowMargin[rid]?.cv ?? NaN }));
|
|
866
|
+
const colMc = cols.map((cid) => ({ row: TOTAL_ID, col: cid, colorValue: colMargin[cid]?.cv ?? NaN }));
|
|
867
|
+
for (const [k, v] of normalizeCells(rowMc, { scope: "global", method })) t2.set(k, v);
|
|
868
|
+
for (const [k, v] of normalizeCells(colMc, { scope: "global", method })) t2.set(k, v);
|
|
869
|
+
t2.set(cellKey(TOTAL_ID, TOTAL_ID), 0.5);
|
|
870
|
+
}
|
|
871
|
+
const colKeys = showMargins ? [...cols, TOTAL_ID] : cols;
|
|
872
|
+
const nivoData2 = rows.map((rid) => ({
|
|
873
|
+
id: rid,
|
|
874
|
+
data: colKeys.map((cid) => ({
|
|
875
|
+
x: cid,
|
|
876
|
+
y: cid === TOTAL_ID ? rowMargin[rid]?.lv ?? 0 : cells[rid]?.[cid]?.lv ?? 0
|
|
877
|
+
}))
|
|
878
|
+
}));
|
|
879
|
+
if (showMargins) {
|
|
880
|
+
nivoData2.push({
|
|
881
|
+
id: TOTAL_ID,
|
|
882
|
+
data: colKeys.map((cid) => ({
|
|
883
|
+
x: cid,
|
|
884
|
+
y: cid === TOTAL_ID ? grand.lv : colMargin[cid]?.lv ?? 0
|
|
885
|
+
}))
|
|
886
|
+
});
|
|
887
|
+
}
|
|
888
|
+
return { nivoData: nivoData2, t: t2 };
|
|
889
|
+
}, [model, rowOrder, colOrder, scope, method, vmin, vmax, showMargins]);
|
|
890
|
+
if (!built) {
|
|
891
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-midnight-text-muted", children: "Not enough distinct values for a heatmap" });
|
|
892
|
+
}
|
|
893
|
+
const { nivoData, t } = built;
|
|
894
|
+
const tAt = (serieId, x) => t.get(cellKey(serieId, x)) ?? 0;
|
|
895
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
896
|
+
heatmap.ResponsiveHeatMap,
|
|
897
|
+
{
|
|
898
|
+
data: nivoData,
|
|
899
|
+
margin: { top: 60, right: 20, bottom: showMargins ? 60 : 20, left: 100 },
|
|
900
|
+
valueFormat: ((v) => fmtNum2(v)),
|
|
901
|
+
axisTop: { tickSize: 5, tickPadding: 5, tickRotation: s.xTickRotation, legend: s.showXLegend ? s.xAxisLabel || colColumn : "", legendPosition: s.xLegendPosition, legendOffset: -50 },
|
|
902
|
+
axisLeft: { tickSize: 5, tickPadding: 5, legend: s.showYLegend ? s.yAxisLabel || rowColumn : "", legendPosition: s.yLegendPosition, legendOffset: -80 },
|
|
903
|
+
colors: ((cell) => heatColor(tAt(cell.serieId, cell.data.x))),
|
|
904
|
+
emptyColor: "#1e293b",
|
|
905
|
+
borderWidth: 1,
|
|
906
|
+
borderColor: "#334155",
|
|
907
|
+
labelTextColor: ((cell) => heatLabelColor(tAt(cell.serieId, cell.data.x))),
|
|
908
|
+
hoverTarget: "cell",
|
|
909
|
+
theme: buildNivoTheme(style)
|
|
910
|
+
}
|
|
911
|
+
) });
|
|
912
|
+
}
|
|
672
913
|
function PivotView({ records }) {
|
|
673
914
|
const [pivotState, setPivotState] = react.useState({});
|
|
674
915
|
if (!records?.length) {
|
|
@@ -1205,6 +1446,7 @@ var CHART_TYPES = [
|
|
|
1205
1446
|
{ id: "line", icon: lucideReact.TrendingUp, label: "Line" },
|
|
1206
1447
|
{ id: "scatter", icon: lucideReact.ScatterChart, label: "Scatter" },
|
|
1207
1448
|
{ id: "heatmap", icon: lucideReact.LayoutGrid, label: "Heatmap" },
|
|
1449
|
+
{ id: "heatmap-plus", icon: lucideReact.Grid3x3, label: "Heatmap+" },
|
|
1208
1450
|
{ id: "grouped-bar", icon: lucideReact.BarChart3, label: "Grouped Bar" }
|
|
1209
1451
|
];
|
|
1210
1452
|
var AGG_OPTIONS = ["count", "distinct", "sum", "avg", "min", "max"];
|
|
@@ -1225,6 +1467,17 @@ var ORDER_OPTIONS = [
|
|
|
1225
1467
|
{ id: "total-asc", label: "Total \u2191" }
|
|
1226
1468
|
];
|
|
1227
1469
|
var MARGIN_AGGS = ["sum", "avg", "min", "max", "count"].map((a) => ({ id: a, label: a }));
|
|
1470
|
+
var HEAT_STAT_OPTIONS = HEAT_STATS.map((a) => ({ id: a, label: a }));
|
|
1471
|
+
var SCOPE_OPTIONS = [
|
|
1472
|
+
{ id: "global", label: "Global" },
|
|
1473
|
+
{ id: "row", label: "Per row" },
|
|
1474
|
+
{ id: "column", label: "Per column" },
|
|
1475
|
+
{ id: "block", label: "Per block" }
|
|
1476
|
+
];
|
|
1477
|
+
var METHOD_OPTIONS = [
|
|
1478
|
+
{ id: "linear", label: "Linear" },
|
|
1479
|
+
{ id: "rank", label: "Rank" }
|
|
1480
|
+
];
|
|
1228
1481
|
var FILTER_OP_OPTIONS = FILTER_OPS.map((o) => ({ id: o, label: o }));
|
|
1229
1482
|
var FieldPill = ({ name, type, onRemove, onChangeAgg, agg, showAgg }) => {
|
|
1230
1483
|
const typeColor = {
|
|
@@ -1413,6 +1666,7 @@ var ChartPreview = ({
|
|
|
1413
1666
|
sqlLoading,
|
|
1414
1667
|
sqlError,
|
|
1415
1668
|
heatmapOpts = {},
|
|
1669
|
+
heatPlusOpts = {},
|
|
1416
1670
|
style
|
|
1417
1671
|
}) => {
|
|
1418
1672
|
const xCol = xFields[0]?.name;
|
|
@@ -1451,6 +1705,8 @@ var ChartPreview = ({
|
|
|
1451
1705
|
return /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(ScatterView, { data: shaped, xColumn: xCol, yColumn: yCol, style }) });
|
|
1452
1706
|
case "heatmap":
|
|
1453
1707
|
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" });
|
|
1708
|
+
case "heatmap-plus":
|
|
1709
|
+
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
1710
|
default:
|
|
1455
1711
|
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
1712
|
}
|
|
@@ -1472,6 +1728,8 @@ var ChartPreview = ({
|
|
|
1472
1728
|
return xCol && yCol ? /* @__PURE__ */ jsxRuntime.jsx(react.Suspense, { fallback: null, children: /* @__PURE__ */ jsxRuntime.jsx(ScatterView, { records, xColumn: xCol, yColumn: yCol, style }) }) : null;
|
|
1473
1729
|
case "heatmap":
|
|
1474
1730
|
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;
|
|
1731
|
+
case "heatmap-plus":
|
|
1732
|
+
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
1733
|
default:
|
|
1476
1734
|
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
1735
|
}
|
|
@@ -1495,6 +1753,13 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1495
1753
|
const [colOrder, setColOrder] = react.useState("alpha");
|
|
1496
1754
|
const [marginAgg, setMarginAgg] = react.useState("sum");
|
|
1497
1755
|
const [showTotals, setShowTotals] = react.useState(false);
|
|
1756
|
+
const [labelStat, setLabelStat] = react.useState("count");
|
|
1757
|
+
const [colorStat, setColorStat] = react.useState("count");
|
|
1758
|
+
const [scope, setScope] = react.useState("global");
|
|
1759
|
+
const [method, setMethod] = react.useState("linear");
|
|
1760
|
+
const [vmin, setVmin] = react.useState("");
|
|
1761
|
+
const [vmax, setVmax] = react.useState("");
|
|
1762
|
+
const [showMargins, setShowMargins] = react.useState(false);
|
|
1498
1763
|
const [style, setStyle] = react.useState(DEFAULT_CHART_STYLE);
|
|
1499
1764
|
const [showStyle, setShowStyle] = react.useState(false);
|
|
1500
1765
|
const [showFields, setShowFields] = react.useState(false);
|
|
@@ -1503,8 +1768,17 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1503
1768
|
const [sqlLoading, setSqlLoading] = react.useState(false);
|
|
1504
1769
|
const [sqlError, setSqlError] = react.useState(null);
|
|
1505
1770
|
const generatedSql = react.useMemo(
|
|
1506
|
-
() => buildChartSQL({
|
|
1507
|
-
|
|
1771
|
+
() => buildChartSQL({
|
|
1772
|
+
chartType,
|
|
1773
|
+
xFields,
|
|
1774
|
+
yFields,
|
|
1775
|
+
valueField,
|
|
1776
|
+
filters,
|
|
1777
|
+
labelStat,
|
|
1778
|
+
colorStat,
|
|
1779
|
+
blockField: colorField?.name
|
|
1780
|
+
}),
|
|
1781
|
+
[chartType, xFields, yFields, valueField, filters, labelStat, colorStat, colorField]
|
|
1508
1782
|
);
|
|
1509
1783
|
react.useEffect(() => {
|
|
1510
1784
|
if (engineMode !== "sql") return void 0;
|
|
@@ -1602,6 +1876,26 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1602
1876
|
config.marginAgg = marginAgg;
|
|
1603
1877
|
config.showTotals = showTotals;
|
|
1604
1878
|
}
|
|
1879
|
+
if (chartType === "heatmap-plus") {
|
|
1880
|
+
config.row = xFields[0]?.name;
|
|
1881
|
+
config.col = yFields[0]?.name;
|
|
1882
|
+
if (valueField) config.value = valueField.name;
|
|
1883
|
+
else delete config.value;
|
|
1884
|
+
config.labelStat = labelStat;
|
|
1885
|
+
config.colorStat = colorStat;
|
|
1886
|
+
if (colorField) config.blockField = colorField.name;
|
|
1887
|
+
else delete config.blockField;
|
|
1888
|
+
config.scope = scope;
|
|
1889
|
+
config.method = method;
|
|
1890
|
+
const nMin = parseFloat(vmin), nMax = parseFloat(vmax);
|
|
1891
|
+
if (Number.isFinite(nMin)) config.vmin = nMin;
|
|
1892
|
+
else delete config.vmin;
|
|
1893
|
+
if (Number.isFinite(nMax)) config.vmax = nMax;
|
|
1894
|
+
else delete config.vmax;
|
|
1895
|
+
config.rowOrder = rowOrder;
|
|
1896
|
+
config.colOrder = colOrder;
|
|
1897
|
+
config.showMargins = showMargins;
|
|
1898
|
+
}
|
|
1605
1899
|
if (engineMode === "sql" && generatedSql) {
|
|
1606
1900
|
config.sql = generatedSql;
|
|
1607
1901
|
config.chartType = chartType;
|
|
@@ -1622,6 +1916,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1622
1916
|
height: fillContainer ? 8 : 2
|
|
1623
1917
|
});
|
|
1624
1918
|
};
|
|
1919
|
+
const isHeat = chartType === "heatmap" || chartType === "heatmap-plus";
|
|
1625
1920
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full", children: [
|
|
1626
1921
|
/* @__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
1922
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
@@ -1657,30 +1952,30 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1657
1952
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1658
1953
|
DropZone,
|
|
1659
1954
|
{
|
|
1660
|
-
label:
|
|
1955
|
+
label: isHeat ? "Rows" : "X Axis / Group By",
|
|
1661
1956
|
fields: xFields,
|
|
1662
1957
|
columns,
|
|
1663
1958
|
onAdd: (f) => addField("x", f),
|
|
1664
1959
|
onRemove: (i) => removeField("x", i),
|
|
1665
1960
|
onChangeAgg: () => {
|
|
1666
1961
|
},
|
|
1667
|
-
maxFields: chartType === "pie" ||
|
|
1962
|
+
maxFields: chartType === "pie" || isHeat ? 1 : 3
|
|
1668
1963
|
}
|
|
1669
1964
|
),
|
|
1670
1965
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1671
1966
|
DropZone,
|
|
1672
1967
|
{
|
|
1673
|
-
label:
|
|
1968
|
+
label: isHeat ? "Columns" : "Y Axis / Values",
|
|
1674
1969
|
fields: yFields,
|
|
1675
1970
|
columns,
|
|
1676
1971
|
onAdd: (f) => addField("y", f),
|
|
1677
1972
|
onRemove: (i) => removeField("y", i),
|
|
1678
1973
|
onChangeAgg: (i, agg) => changeAgg("y", i, agg),
|
|
1679
|
-
showAgg:
|
|
1680
|
-
maxFields:
|
|
1974
|
+
showAgg: !isHeat,
|
|
1975
|
+
maxFields: isHeat ? 1 : 5
|
|
1681
1976
|
}
|
|
1682
1977
|
),
|
|
1683
|
-
|
|
1978
|
+
isHeat && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1684
1979
|
DropZone,
|
|
1685
1980
|
{
|
|
1686
1981
|
label: "Cell Value (optional \u2014 defaults to count)",
|
|
@@ -1689,10 +1984,93 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1689
1984
|
onAdd: (f) => addField("value", f),
|
|
1690
1985
|
onRemove: () => removeField("value"),
|
|
1691
1986
|
onChangeAgg: (i, agg) => changeAgg("value", i, agg),
|
|
1692
|
-
showAgg:
|
|
1987
|
+
showAgg: chartType === "heatmap",
|
|
1693
1988
|
maxFields: 1
|
|
1694
1989
|
}
|
|
1695
1990
|
),
|
|
1991
|
+
chartType === "heatmap-plus" && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
1992
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1993
|
+
DropZone,
|
|
1994
|
+
{
|
|
1995
|
+
label: "Block (optional \u2014 color-scope grouping)",
|
|
1996
|
+
fields: colorField ? [colorField] : [],
|
|
1997
|
+
columns,
|
|
1998
|
+
onAdd: (f) => addField("color", f),
|
|
1999
|
+
onRemove: () => removeField("color"),
|
|
2000
|
+
onChangeAgg: () => {
|
|
2001
|
+
},
|
|
2002
|
+
maxFields: 1
|
|
2003
|
+
}
|
|
2004
|
+
),
|
|
2005
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2006
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Cell Stats" }),
|
|
2007
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2008
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2009
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Label stat" }),
|
|
2010
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: labelStat, options: HEAT_STAT_OPTIONS, onChange: (v) => setLabelStat(v) })
|
|
2011
|
+
] }),
|
|
2012
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2013
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Color stat" }),
|
|
2014
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: colorStat, options: HEAT_STAT_OPTIONS, onChange: (v) => setColorStat(v) })
|
|
2015
|
+
] })
|
|
2016
|
+
] })
|
|
2017
|
+
] }),
|
|
2018
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2019
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Color Engine" }),
|
|
2020
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2021
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2022
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Scale scope" }),
|
|
2023
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: scope, options: SCOPE_OPTIONS, onChange: (v) => setScope(v) })
|
|
2024
|
+
] }),
|
|
2025
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2026
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Scale method" }),
|
|
2027
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: method, options: METHOD_OPTIONS, onChange: (v) => setMethod(v) })
|
|
2028
|
+
] }),
|
|
2029
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2030
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Fixed min / max" }),
|
|
2031
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-1", children: [
|
|
2032
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2033
|
+
"input",
|
|
2034
|
+
{
|
|
2035
|
+
value: vmin,
|
|
2036
|
+
onChange: (e) => setVmin(e.target.value),
|
|
2037
|
+
placeholder: "auto",
|
|
2038
|
+
inputMode: "decimal",
|
|
2039
|
+
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"
|
|
2040
|
+
}
|
|
2041
|
+
),
|
|
2042
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
2043
|
+
"input",
|
|
2044
|
+
{
|
|
2045
|
+
value: vmax,
|
|
2046
|
+
onChange: (e) => setVmax(e.target.value),
|
|
2047
|
+
placeholder: "auto",
|
|
2048
|
+
inputMode: "decimal",
|
|
2049
|
+
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"
|
|
2050
|
+
}
|
|
2051
|
+
)
|
|
2052
|
+
] })
|
|
2053
|
+
] })
|
|
2054
|
+
] })
|
|
2055
|
+
] }),
|
|
2056
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
2057
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Order & Margins" }),
|
|
2058
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
2059
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2060
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Order rows" }),
|
|
2061
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: rowOrder, options: ORDER_OPTIONS, onChange: (v) => setRowOrder(v) })
|
|
2062
|
+
] }),
|
|
2063
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between gap-3 min-h-[28px]", children: [
|
|
2064
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Order columns" }),
|
|
2065
|
+
/* @__PURE__ */ jsxRuntime.jsx(SelectControl, { value: colOrder, options: ORDER_OPTIONS, onChange: (v) => setColOrder(v) })
|
|
2066
|
+
] }),
|
|
2067
|
+
/* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-2 cursor-pointer", children: [
|
|
2068
|
+
/* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", checked: showMargins, onChange: (e) => setShowMargins(e.target.checked), className: "accent-midnight-accent" }),
|
|
2069
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs font-mono text-midnight-text-muted", children: "Show margins (row / col / grand)" })
|
|
2070
|
+
] })
|
|
2071
|
+
] })
|
|
2072
|
+
] })
|
|
2073
|
+
] }),
|
|
1696
2074
|
chartType === "heatmap" && /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
|
|
1697
2075
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs uppercase text-midnight-text-muted mb-2 font-mono", children: "Order & Totals" }),
|
|
1698
2076
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
|
|
@@ -1826,6 +2204,18 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1826
2204
|
sqlLoading,
|
|
1827
2205
|
sqlError,
|
|
1828
2206
|
heatmapOpts: { rowOrder, colOrder, marginAgg, showTotals },
|
|
2207
|
+
heatPlusOpts: {
|
|
2208
|
+
labelStat,
|
|
2209
|
+
colorStat,
|
|
2210
|
+
blockField: colorField?.name,
|
|
2211
|
+
scope,
|
|
2212
|
+
method,
|
|
2213
|
+
vmin: Number.isFinite(parseFloat(vmin)) ? parseFloat(vmin) : void 0,
|
|
2214
|
+
vmax: Number.isFinite(parseFloat(vmax)) ? parseFloat(vmax) : void 0,
|
|
2215
|
+
rowOrder,
|
|
2216
|
+
colOrder,
|
|
2217
|
+
showMargins
|
|
2218
|
+
},
|
|
1829
2219
|
style
|
|
1830
2220
|
}
|
|
1831
2221
|
) })
|
|
@@ -1834,7 +2224,7 @@ function ChartBuilder({ records, columns, stateId, onSave }) {
|
|
|
1834
2224
|
function ViewLoading() {
|
|
1835
2225
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-center h-full text-xs text-midnight-text-muted", children: "Loading..." });
|
|
1836
2226
|
}
|
|
1837
|
-
var SQL_CHART_TYPES = /* @__PURE__ */ new Set(["bar", "grouped-bar", "pie", "line", "scatter", "heatmap"]);
|
|
2227
|
+
var SQL_CHART_TYPES = /* @__PURE__ */ new Set(["bar", "grouped-bar", "pie", "line", "scatter", "heatmap", "heatmap-plus"]);
|
|
1838
2228
|
var SQL_DATA_TYPES = /* @__PURE__ */ new Set(["table", "metric"]);
|
|
1839
2229
|
var TABLE_PREVIEW_ROWS = 200;
|
|
1840
2230
|
function tableSql(config) {
|
|
@@ -1865,6 +2255,14 @@ function panelToChartConfig(chartType, config) {
|
|
|
1865
2255
|
if (config.col) cfg.yFields = [{ name: config.col }];
|
|
1866
2256
|
if (config.value) cfg.valueField = { name: config.value, agg: config.agg };
|
|
1867
2257
|
break;
|
|
2258
|
+
case "heatmap-plus":
|
|
2259
|
+
if (config.row) cfg.xFields = [{ name: config.row }];
|
|
2260
|
+
if (config.col) cfg.yFields = [{ name: config.col }];
|
|
2261
|
+
if (config.value) cfg.valueField = { name: config.value, agg: config.labelStat };
|
|
2262
|
+
cfg.labelStat = config.labelStat;
|
|
2263
|
+
cfg.colorStat = config.colorStat;
|
|
2264
|
+
cfg.blockField = config.blockField;
|
|
2265
|
+
break;
|
|
1868
2266
|
}
|
|
1869
2267
|
return cfg;
|
|
1870
2268
|
}
|
|
@@ -2013,6 +2411,9 @@ function SqlPanel({ panel }) {
|
|
|
2013
2411
|
case "heatmap":
|
|
2014
2412
|
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
2413
|
break;
|
|
2414
|
+
case "heatmap-plus":
|
|
2415
|
+
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" });
|
|
2416
|
+
break;
|
|
2016
2417
|
default:
|
|
2017
2418
|
chart = /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 text-xs text-midnight-text-muted", children: [
|
|
2018
2419
|
"Unsupported SQL chart: ",
|
|
@@ -2046,6 +2447,8 @@ function PanelContent({ panel, records, columns }) {
|
|
|
2046
2447
|
return config.x && config.y ? /* @__PURE__ */ jsxRuntime.jsx(ScatterView, { records, xColumn: config.x, yColumn: config.y, style: config.style }) : null;
|
|
2047
2448
|
case "heatmap":
|
|
2048
2449
|
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;
|
|
2450
|
+
case "heatmap-plus":
|
|
2451
|
+
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
2452
|
case "pivot":
|
|
2050
2453
|
return /* @__PURE__ */ jsxRuntime.jsx(PivotView, { records });
|
|
2051
2454
|
case "metric":
|
|
@@ -2512,6 +2915,8 @@ exports.DEFAULT_CHART_STYLE = DEFAULT_CHART_STYLE;
|
|
|
2512
2915
|
exports.DashboardProvider = DashboardProvider;
|
|
2513
2916
|
exports.DashboardRenderer = DashboardRenderer;
|
|
2514
2917
|
exports.DataExplorer = DataExplorer;
|
|
2918
|
+
exports.HEAT_STATS = HEAT_STATS;
|
|
2919
|
+
exports.HeatmapPlusView = HeatmapPlusView;
|
|
2515
2920
|
exports.HeatmapView = HeatmapView;
|
|
2516
2921
|
exports.InsightView = InsightView;
|
|
2517
2922
|
exports.LEGEND_ANCHORS = LEGEND_ANCHORS;
|
|
@@ -2529,7 +2934,10 @@ exports.buildChartSQL = buildChartSQL;
|
|
|
2529
2934
|
exports.buildNivoTheme = buildNivoTheme;
|
|
2530
2935
|
exports.compileWhere = compileWhere;
|
|
2531
2936
|
exports.groupBy = groupBy;
|
|
2937
|
+
exports.heatColor = heatColor;
|
|
2938
|
+
exports.heatLabelColor = heatLabelColor;
|
|
2532
2939
|
exports.legendConfig = legendConfig;
|
|
2940
|
+
exports.normalizeCells = normalizeCells;
|
|
2533
2941
|
exports.qIdent = qIdent;
|
|
2534
2942
|
exports.qLit = qLit;
|
|
2535
2943
|
exports.shapeChartData = shapeChartData;
|