@quantumwake/terminal-ux-dashboard-components 0.1.19 → 0.1.21

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
@@ -276,8 +276,57 @@ var DEFAULT_CHART_STYLE = {
276
276
  titleAlign: "left",
277
277
  titleBold: false,
278
278
  titleBackground: "",
279
- titleColor: ""
279
+ titleColor: "",
280
+ // Chart frame + series color model.
281
+ height: 0,
282
+ margin: null,
283
+ maxXTicks: 0,
284
+ maxYTicks: 0,
285
+ areaOpacity: 0.15,
286
+ barLabels: true,
287
+ seriesColors: []
280
288
  };
289
+ var DEFAULT_SERIES_COLORS = [
290
+ "#3987e5",
291
+ // blue
292
+ "#d95926",
293
+ // orange
294
+ "#199e70",
295
+ // aqua
296
+ "#c98500",
297
+ // yellow
298
+ "#d55181",
299
+ // magenta
300
+ "#008300",
301
+ // green
302
+ "#9085e9",
303
+ // violet
304
+ "#e66767"
305
+ // red
306
+ ];
307
+ var SERIES_OVERFLOW_COLOR = "#707078";
308
+ var seriesColor = (i, style) => {
309
+ const s = withStyleDefaults(style);
310
+ const palette = s.seriesColors.length ? s.seriesColors : DEFAULT_SERIES_COLORS;
311
+ return i < palette.length ? palette[i] : SERIES_OVERFLOW_COLOR;
312
+ };
313
+ var chartSizing = (style, defaultMargin) => {
314
+ const s = withStyleDefaults(style);
315
+ return {
316
+ frameClass: s.height > 0 ? "w-full" : "h-full w-full min-h-[160px]",
317
+ frameStyle: s.height > 0 ? { height: s.height } : {},
318
+ margin: { ...defaultMargin, ...s.margin || {} }
319
+ };
320
+ };
321
+ function thinTicks(values, max) {
322
+ if (max <= 0 || values.length <= max) return void 0;
323
+ if (max === 1) return [values[values.length - 1]];
324
+ const picked = [];
325
+ for (let i = 0; i < max; i++) {
326
+ picked.push(values[Math.round(i * (values.length - 1) / (max - 1))]);
327
+ }
328
+ return [...new Set(picked)];
329
+ }
281
330
  var LEGEND_ANCHORS = ["right", "top-left", "top-right", "bottom-left", "bottom-right", "none"];
282
331
  var withStyleDefaults = (style) => ({
283
332
  ...DEFAULT_CHART_STYLE,
@@ -527,27 +576,37 @@ function makeAxis(style, axis, columnName, opts2 = {}) {
527
576
  out.tickRotation = rotate;
528
577
  out.format = (v) => formatVal(v, s, numeric);
529
578
  }
579
+ const maxTicks = isX ? s.maxXTicks : s.maxYTicks;
580
+ if (numeric && maxTicks > 0) out.tickValues = maxTicks;
530
581
  return out;
531
582
  }
532
- function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: presetData, style }) {
583
+ function BarView({ records, groupColumn, valueColumn, aggFn = "count", data: presetData, layout = "vertical", style }) {
533
584
  const computed = react.useMemo(() => {
534
585
  if (presetData || !records) return [];
535
586
  const groups = groupBy(records, groupColumn);
536
587
  return Object.entries(groups).map(([key, recs]) => ({ group: key, value: aggregate(recs, valueColumn, aggFn) })).sort((a, b) => b.value - a.value).slice(0, 50);
537
588
  }, [records, groupColumn, valueColumn, aggFn, presetData]);
538
589
  const data = presetData || computed;
539
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
590
+ const s = withStyleDefaults(style);
591
+ const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 20, bottom: 60, left: 60 });
592
+ const horizontal = layout === "horizontal";
593
+ const plotted = horizontal ? [...data].reverse() : data;
594
+ const axisBottom = horizontal ? makeAxis(style, "x", valueColumn, { numeric: true }) : makeAxis(style, "x", groupColumn);
595
+ const axisLeft = horizontal ? makeAxis(style, "y", groupColumn) : makeAxis(style, "y", valueColumn, { numeric: true });
596
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
540
597
  bar.ResponsiveBar,
541
598
  {
542
- data,
599
+ data: plotted,
543
600
  keys: ["value"],
544
601
  indexBy: "group",
545
- margin: { top: 20, right: 20, bottom: 60, left: 60 },
602
+ layout,
603
+ margin,
546
604
  padding: 0.3,
547
- colors: ["rgba(74, 222, 128, 0.8)"],
605
+ colors: [s.seriesColors[0] || "rgba(74, 222, 128, 0.8)"],
548
606
  borderColor: { from: "color", modifiers: [["darker", 1.6]] },
549
- axisBottom: makeAxis(style, "x", groupColumn),
550
- axisLeft: makeAxis(style, "y", valueColumn, { numeric: true }),
607
+ axisBottom,
608
+ axisLeft,
609
+ enableLabel: s.barLabels,
551
610
  labelSkipWidth: 12,
552
611
  labelSkipHeight: 12,
553
612
  labelTextColor: { from: "color", modifiers: [["darker", 3]] },
@@ -568,16 +627,19 @@ function PieView({ records, groupColumn, data: presetData, style }) {
568
627
  }, [records, groupColumn, presetData]);
569
628
  const data = presetData || computed;
570
629
  const legend = legendConfig(style);
571
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
630
+ const s = withStyleDefaults(style);
631
+ const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 120, bottom: 20, left: 20 });
632
+ const colorById = new Map(data.map((d, i) => [d.id, seriesColor(i, s)]));
633
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
572
634
  pie.ResponsivePie,
573
635
  {
574
636
  data,
575
- margin: { top: 20, right: 120, bottom: 20, left: 20 },
637
+ margin,
576
638
  innerRadius: 0.4,
577
639
  padAngle: 1,
578
640
  cornerRadius: 3,
579
641
  activeOuterRadiusOffset: 8,
580
- colors: { scheme: "set2" },
642
+ colors: ((d) => colorById.get(d.id)),
581
643
  borderWidth: 1,
582
644
  borderColor: { from: "color", modifiers: [["darker", 0.2]] },
583
645
  arcLinkLabelsSkipAngle: 10,
@@ -590,7 +652,7 @@ function PieView({ records, groupColumn, data: presetData, style }) {
590
652
  }
591
653
  ) });
592
654
  }
593
- function LineView({ records, xColumn, yColumn, data: presetData, style }) {
655
+ function LineView({ records, xColumn, yColumn, data: presetData, dashedIds, style }) {
594
656
  const computed = react.useMemo(() => {
595
657
  if (presetData || !records) return [];
596
658
  const sorted = [...records].filter((r) => r[xColumn] != null && r[yColumn] != null).sort((a, b) => {
@@ -604,29 +666,69 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
604
666
  }];
605
667
  }, [records, xColumn, yColumn, presetData]);
606
668
  const data = presetData || computed;
607
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
669
+ const s = withStyleDefaults(style);
670
+ const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 20, bottom: 60, left: 60 });
671
+ const colorById = new Map(data.map((serie, i) => [
672
+ serie.id,
673
+ s.seriesColors.length || data.length > 1 ? seriesColor(i, s) : "rgba(96, 165, 250, 0.9)"
674
+ ]));
675
+ const longest = data.reduce((a, b) => b.data.length > a.data.length ? b : a, { id: "", data: [] });
676
+ const tickValues = thinTicks(longest.data.map((d) => d.x), s.maxXTicks);
677
+ const dashed = new Set(dashedIds || []);
678
+ const linesLayer = ({ series, lineGenerator }) => /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: series.map((serie) => /* @__PURE__ */ jsxRuntime.jsx(
679
+ "path",
680
+ {
681
+ d: lineGenerator(serie.data.map((d) => d.position)),
682
+ fill: "none",
683
+ stroke: serie.color,
684
+ strokeWidth: 2,
685
+ strokeLinecap: "round",
686
+ strokeDasharray: dashed.has(String(serie.id)) ? "1 5" : void 0
687
+ },
688
+ serie.id
689
+ )) });
690
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
608
691
  line.ResponsiveLine,
609
692
  {
610
693
  data,
611
- margin: { top: 20, right: 20, bottom: 60, left: 60 },
694
+ margin,
612
695
  xScale: { type: "point" },
613
696
  yScale: { type: "linear", min: "auto", max: "auto" },
614
697
  curve: "monotoneX",
615
- enableArea: true,
616
- areaOpacity: 0.15,
617
- colors: ["rgba(96, 165, 250, 0.9)"],
618
- pointSize: (data[0]?.data.length ?? 0) > 50 ? 0 : 6,
698
+ enableArea: s.areaOpacity > 0,
699
+ areaOpacity: s.areaOpacity,
700
+ colors: ((serie) => colorById.get(serie.id)),
701
+ pointSize: 0,
619
702
  pointColor: { theme: "background" },
620
703
  pointBorderWidth: 2,
621
704
  pointBorderColor: { from: "serieColor" },
622
705
  enableGridX: false,
623
- axisBottom: makeAxis(style, "x", xColumn),
706
+ axisBottom: { ...makeAxis(style, "x", xColumn), ...tickValues ? { tickValues } : {} },
624
707
  axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
625
708
  useMesh: true,
709
+ layers: ["grid", "markers", "axes", "areas", linesLayer, "crosshair", "slices", "mesh", "legends"],
626
710
  theme: buildNivoTheme(style)
627
711
  }
628
712
  ) });
629
713
  }
714
+ function SparklineView({ data, height = 56, color = "#3987e5" }) {
715
+ const gradientId = react.useId();
716
+ const width = 260;
717
+ const pts = data && data.length ? data : [0];
718
+ const max = Math.max(...pts, 1);
719
+ const dx = pts.length > 1 ? width / (pts.length - 1) : width;
720
+ const xy = (v, i) => [i * dx, height - v / max * (height - 4) - 2];
721
+ const line = pts.map((v, i) => `${i === 0 ? "M" : "L"} ${xy(v, i)[0].toFixed(1)} ${xy(v, i)[1].toFixed(1)}`).join(" ");
722
+ const area = `${line} L ${width} ${height} L 0 ${height} Z`;
723
+ return /* @__PURE__ */ jsxRuntime.jsxs("svg", { viewBox: `0 0 ${width} ${height}`, height, className: "w-full", preserveAspectRatio: "none", children: [
724
+ /* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs("linearGradient", { id: gradientId, x1: "0", y1: "0", x2: "0", y2: "1", children: [
725
+ /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "0%", stopColor: color, stopOpacity: "0.25" }),
726
+ /* @__PURE__ */ jsxRuntime.jsx("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
727
+ ] }) }),
728
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: area, fill: `url(#${gradientId})` }),
729
+ /* @__PURE__ */ jsxRuntime.jsx("path", { d: line, fill: "none", stroke: color, strokeWidth: "2", vectorEffect: "non-scaling-stroke" })
730
+ ] });
731
+ }
630
732
  function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
631
733
  const computed = react.useMemo(() => {
632
734
  if (presetData || !records) return [];
@@ -634,14 +736,20 @@ function ScatterView({ records, xColumn, yColumn, data: presetData, style }) {
634
736
  return [{ id: `${xColumn} vs ${yColumn}`, data: points }];
635
737
  }, [records, xColumn, yColumn, presetData]);
636
738
  const data = presetData || computed;
637
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
739
+ const s = withStyleDefaults(style);
740
+ const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 20, bottom: 60, left: 60 });
741
+ const colorById = new Map(data.map((serie, i) => [
742
+ serie.id,
743
+ s.seriesColors.length || data.length > 1 ? seriesColor(i, s) : "rgba(167, 139, 250, 0.7)"
744
+ ]));
745
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
638
746
  scatterplot.ResponsiveScatterPlot,
639
747
  {
640
748
  data,
641
- margin: { top: 20, right: 20, bottom: 60, left: 60 },
749
+ margin,
642
750
  xScale: { type: "linear", min: "auto", max: "auto" },
643
751
  yScale: { type: "linear", min: "auto", max: "auto" },
644
- colors: ["rgba(167, 139, 250, 0.7)"],
752
+ colors: ((serie) => colorById.get(serie.serieId ?? serie.id ?? "")),
645
753
  nodeSize: 6,
646
754
  axisBottom: makeAxis(style, "x", xColumn, { numeric: true }),
647
755
  axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
@@ -737,8 +845,8 @@ function HeatmapView({
737
845
  if (!data.length || !data[0].data.length) {
738
846
  return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-center text-midnight-text-muted", children: "Not enough distinct values for a heatmap" });
739
847
  }
740
- const margin = showTotals ? { top: 60, right: 70, bottom: 60, left: 100 } : { top: 60, right: 20, bottom: 20, left: 100 };
741
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-full w-full min-h-[160px]", children: /* @__PURE__ */ jsxRuntime.jsx(
848
+ const { frameClass, frameStyle, margin } = chartSizing(style, showTotals ? { top: 60, right: 70, bottom: 60, left: 100 } : { top: 60, right: 20, bottom: 20, left: 100 });
849
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
742
850
  heatmap.ResponsiveHeatMap,
743
851
  {
744
852
  data,
@@ -892,11 +1000,12 @@ function HeatmapPlusView({
892
1000
  }
893
1001
  const { nivoData, t } = built;
894
1002
  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(
1003
+ const { frameClass, frameStyle, margin } = chartSizing(style, { top: 60, right: 20, bottom: showMargins ? 60 : 20, left: 100 });
1004
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: frameClass, style: frameStyle, children: /* @__PURE__ */ jsxRuntime.jsx(
896
1005
  heatmap.ResponsiveHeatMap,
897
1006
  {
898
1007
  data: nivoData,
899
- margin: { top: 60, right: 20, bottom: showMargins ? 60 : 20, left: 100 },
1008
+ margin,
900
1009
  valueFormat: ((v) => fmtNum2(v)),
901
1010
  axisTop: { tickSize: 5, tickPadding: 5, tickRotation: s.xTickRotation, legend: s.showXLegend ? s.xAxisLabel || colColumn : "", legendPosition: s.xLegendPosition, legendOffset: -50 },
902
1011
  axisLeft: { tickSize: 5, tickPadding: 5, legend: s.showYLegend ? s.yAxisLabel || rowColumn : "", legendPosition: s.yLegendPosition, legendOffset: -80 },
@@ -2912,6 +3021,7 @@ exports.BarView = BarView;
2912
3021
  exports.ChartBuilder = ChartBuilder;
2913
3022
  exports.ChartStyleControls = ChartStyleControls;
2914
3023
  exports.DEFAULT_CHART_STYLE = DEFAULT_CHART_STYLE;
3024
+ exports.DEFAULT_SERIES_COLORS = DEFAULT_SERIES_COLORS;
2915
3025
  exports.DashboardProvider = DashboardProvider;
2916
3026
  exports.DashboardRenderer = DashboardRenderer;
2917
3027
  exports.DataExplorer = DataExplorer;
@@ -2925,13 +3035,16 @@ exports.MetricView = MetricView;
2925
3035
  exports.PanelChart = PanelChart;
2926
3036
  exports.PieView = PieView;
2927
3037
  exports.PivotView = PivotView;
3038
+ exports.SERIES_OVERFLOW_COLOR = SERIES_OVERFLOW_COLOR;
2928
3039
  exports.ScatterView = ScatterView;
3040
+ exports.SparklineView = SparklineView;
2929
3041
  exports.SqlConsole = SqlConsole;
2930
3042
  exports.aggExpr = aggExpr;
2931
3043
  exports.aggregate = aggregate;
2932
3044
  exports.axisLegend = axisLegend;
2933
3045
  exports.buildChartSQL = buildChartSQL;
2934
3046
  exports.buildNivoTheme = buildNivoTheme;
3047
+ exports.chartSizing = chartSizing;
2935
3048
  exports.compileWhere = compileWhere;
2936
3049
  exports.groupBy = groupBy;
2937
3050
  exports.heatColor = heatColor;
@@ -2940,7 +3053,9 @@ exports.legendConfig = legendConfig;
2940
3053
  exports.normalizeCells = normalizeCells;
2941
3054
  exports.qIdent = qIdent;
2942
3055
  exports.qLit = qLit;
3056
+ exports.seriesColor = seriesColor;
2943
3057
  exports.shapeChartData = shapeChartData;
3058
+ exports.thinTicks = thinTicks;
2944
3059
  exports.useCapabilities = useCapabilities;
2945
3060
  exports.useDashboard = useDashboard;
2946
3061
  exports.withStyleDefaults = withStyleDefaults;