@quantumwake/terminal-ux-dashboard-components 0.1.27 → 0.1.29

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.d.cts CHANGED
@@ -325,12 +325,18 @@ declare function PieView({ records, groupColumn, data: presetData, style }: PieV
325
325
 
326
326
  interface LineSerie {
327
327
  id: string;
328
+ /** Explicit serie color — STABLE identity coloring: the host assigns a
329
+ * color to the entity, so toggling other series never repaints this
330
+ * one (index-based palette assignment reshuffles on every selection
331
+ * change). Absent ⇒ the fixed-order palette by index. */
332
+ color?: string;
328
333
  /** lo/hi (optional, per point) draw a soft envelope band behind the
329
334
  * line — the reading for "this line summarizes several": the band is
330
- * the min–max spread of what it aggregates. */
335
+ * the min–max spread of what it aggregates. y may be null for an
336
+ * honest gap (the line breaks instead of bridging). */
331
337
  data: {
332
- x: string;
333
- y: number;
338
+ x: string | Date | number;
339
+ y: number | null;
334
340
  lo?: number;
335
341
  hi?: number;
336
342
  }[];
@@ -342,8 +348,22 @@ interface LineViewProps {
342
348
  /** Pre-shaped Nivo line series bypasses client-side processing. */
343
349
  data?: LineSerie[];
344
350
  style?: Partial<ChartStyle>;
345
- }
346
- declare function LineView({ records, xColumn, yColumn, data: presetData, style }: LineViewProps): react.JSX.Element;
351
+ /** 'point' (default): x values are CATEGORIES, placed at equal spacing
352
+ * in encounter order and ticked by sampling every Nth one — right for
353
+ * labels, wrong for timestamps (a missing bucket collapses, and the
354
+ * ticks land on arbitrary samples). 'time': x values are Dates (or
355
+ * epoch ms / ISO strings) on a continuous scale — gaps stretch, ticks
356
+ * land on ROUND times (d3's time ticks, capped by maxXTicks), and the
357
+ * series need not share buckets. */
358
+ xScale?: 'point' | 'time';
359
+ /** Time axis tick + tooltip formatter. Default: HH:MM:SS, local, 24h. */
360
+ xFormat?: (x: Date) => string;
361
+ /** Time axis: pin the visible domain — a live window keeps a steady
362
+ * width instead of re-fitting to whatever samples exist. Default: fit
363
+ * the data. */
364
+ xDomain?: [Date, Date];
365
+ }
366
+ declare function LineView({ records, xColumn, yColumn, data: presetData, style, xScale, xFormat, xDomain }: LineViewProps): react.JSX.Element;
347
367
 
348
368
  interface SparklineViewProps {
349
369
  /** The series, oldest first. */
package/dist/index.d.ts CHANGED
@@ -325,12 +325,18 @@ declare function PieView({ records, groupColumn, data: presetData, style }: PieV
325
325
 
326
326
  interface LineSerie {
327
327
  id: string;
328
+ /** Explicit serie color — STABLE identity coloring: the host assigns a
329
+ * color to the entity, so toggling other series never repaints this
330
+ * one (index-based palette assignment reshuffles on every selection
331
+ * change). Absent ⇒ the fixed-order palette by index. */
332
+ color?: string;
328
333
  /** lo/hi (optional, per point) draw a soft envelope band behind the
329
334
  * line — the reading for "this line summarizes several": the band is
330
- * the min–max spread of what it aggregates. */
335
+ * the min–max spread of what it aggregates. y may be null for an
336
+ * honest gap (the line breaks instead of bridging). */
331
337
  data: {
332
- x: string;
333
- y: number;
338
+ x: string | Date | number;
339
+ y: number | null;
334
340
  lo?: number;
335
341
  hi?: number;
336
342
  }[];
@@ -342,8 +348,22 @@ interface LineViewProps {
342
348
  /** Pre-shaped Nivo line series bypasses client-side processing. */
343
349
  data?: LineSerie[];
344
350
  style?: Partial<ChartStyle>;
345
- }
346
- declare function LineView({ records, xColumn, yColumn, data: presetData, style }: LineViewProps): react.JSX.Element;
351
+ /** 'point' (default): x values are CATEGORIES, placed at equal spacing
352
+ * in encounter order and ticked by sampling every Nth one — right for
353
+ * labels, wrong for timestamps (a missing bucket collapses, and the
354
+ * ticks land on arbitrary samples). 'time': x values are Dates (or
355
+ * epoch ms / ISO strings) on a continuous scale — gaps stretch, ticks
356
+ * land on ROUND times (d3's time ticks, capped by maxXTicks), and the
357
+ * series need not share buckets. */
358
+ xScale?: 'point' | 'time';
359
+ /** Time axis tick + tooltip formatter. Default: HH:MM:SS, local, 24h. */
360
+ xFormat?: (x: Date) => string;
361
+ /** Time axis: pin the visible domain — a live window keeps a steady
362
+ * width instead of re-fitting to whatever samples exist. Default: fit
363
+ * the data. */
364
+ xDomain?: [Date, Date];
365
+ }
366
+ declare function LineView({ records, xColumn, yColumn, data: presetData, style, xScale, xFormat, xDomain }: LineViewProps): react.JSX.Element;
347
367
 
348
368
  interface SparklineViewProps {
349
369
  /** The series, oldest first. */
package/dist/index.js CHANGED
@@ -648,7 +648,11 @@ function PieView({ records, groupColumn, data: presetData, style }) {
648
648
  }
649
649
  ) });
650
650
  }
651
- function LineView({ records, xColumn, yColumn, data: presetData, style }) {
651
+ var fmtClock = (d) => d.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", second: "2-digit", hourCycle: "h23" });
652
+ var toDate = (x) => x instanceof Date ? x : new Date(x);
653
+ function LineView({ records, xColumn, yColumn, data: presetData, style, xScale = "point", xFormat, xDomain }) {
654
+ const timed = xScale === "time";
655
+ const clock = xFormat ?? fmtClock;
652
656
  const computed = useMemo(() => {
653
657
  if (presetData || !records) return [];
654
658
  const sorted = [...records].filter((r) => r[xColumn] != null && r[yColumn] != null).sort((a, b) => {
@@ -661,18 +665,29 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
661
665
  data: sorted.map((r) => ({ x: String(r[xColumn]), y: Number(r[yColumn]) || 0 }))
662
666
  }];
663
667
  }, [records, xColumn, yColumn, presetData]);
664
- const data = presetData || computed;
668
+ const shaped = presetData || computed;
669
+ const data = useMemo(
670
+ () => timed ? shaped.map((serie) => ({ ...serie, data: serie.data.map((d) => ({ ...d, x: toDate(d.x) })) })) : shaped,
671
+ [shaped, timed]
672
+ );
665
673
  const s = withStyleDefaults(style);
666
674
  const { frameClass, frameStyle, margin } = chartSizing(style, { top: 20, right: 20, bottom: 60, left: 60 });
667
675
  const colorById = new Map(data.map((serie, i) => [
668
676
  serie.id,
669
- s.seriesColors.length || data.length > 1 ? seriesColor(i, s) : "rgba(96, 165, 250, 0.9)"
677
+ serie.color ?? (s.seriesColors.length || data.length > 1 ? seriesColor(i, s) : "rgba(96, 165, 250, 0.9)")
670
678
  ]));
671
679
  const longest = data.reduce((a, b) => b.data.length > a.data.length ? b : a, { id: "", data: [] });
672
- const tickValues = thinTicks(longest.data.map((d) => d.x), s.maxXTicks);
680
+ const tickValues = timed ? void 0 : thinTicks(longest.data.map((d) => d.x), s.maxXTicks);
681
+ const axisBottom = (() => {
682
+ const base = makeAxis(style, "x", xColumn);
683
+ if (!timed) return { ...base, ...tickValues ? { tickValues } : {} };
684
+ delete base.renderTick;
685
+ return { ...base, tickRotation: s.xTickRotation, format: clock, ...s.maxXTicks > 0 ? { tickValues: s.maxXTicks } : {} };
686
+ })();
687
+ const nivoXScale = timed ? { type: "time", format: "native", precision: "millisecond", useUTC: false, min: xDomain?.[0] ?? "auto", max: xDomain?.[1] ?? "auto" } : { type: "point" };
673
688
  let yScale = { type: "linear", min: "auto", max: "auto" };
674
689
  if (s.yFromZero) {
675
- const dataMax = Math.max(0, ...data.flatMap((serie) => serie.data.map((d) => Math.max(d.y, d.hi ?? d.y))));
690
+ const dataMax = Math.max(0, ...data.flatMap((serie) => serie.data.map((d) => Math.max(d.y ?? 0, d.hi ?? d.y ?? 0))));
676
691
  yScale = { type: "linear", min: 0, max: niceCeil(dataMax) };
677
692
  }
678
693
  const bandedIds = new Set(data.filter((serie) => serie.data.some((d) => d.lo != null)).map((serie) => serie.id));
@@ -700,7 +715,8 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
700
715
  {
701
716
  data,
702
717
  margin,
703
- xScale: { type: "point" },
718
+ xScale: nivoXScale,
719
+ xFormat: timed ? (v) => clock(v) : void 0,
704
720
  yScale,
705
721
  curve: "monotoneX",
706
722
  enableArea: s.areaOpacity > 0,
@@ -711,7 +727,7 @@ function LineView({ records, xColumn, yColumn, data: presetData, style }) {
711
727
  pointBorderWidth: 2,
712
728
  pointBorderColor: { from: "serieColor" },
713
729
  enableGridX: false,
714
- axisBottom: { ...makeAxis(style, "x", xColumn), ...tickValues ? { tickValues } : {} },
730
+ axisBottom,
715
731
  axisLeft: makeAxis(style, "y", yColumn, { numeric: true }),
716
732
  useMesh: true,
717
733
  layers: ["grid", "markers", "axes", "areas", bandsLayer, linesLayer, "crosshair", "slices", "mesh", "legends"],