@tangle-network/charts 0.2.0 → 0.3.0
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 +12 -1
- package/dist/react/index.d.ts +52 -2
- package/dist/react/index.js +221 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
# @tangle-network/charts
|
|
2
2
|
|
|
3
|
-
Benchmark figures as static SVG and HTML tables.
|
|
3
|
+
Benchmark figures as static SVG and HTML tables, plus optional React timing primitives.
|
|
4
|
+
|
|
5
|
+
`WaterfallHeader`, `WaterfallTimeAxis`, `WaterfallBar` and `WaterfallRow` from
|
|
6
|
+
`@tangle-network/charts/react` own the timing presentation extracted from
|
|
7
|
+
Intelligence's trace viewer. Supply actual span start/end milliseconds and the
|
|
8
|
+
same `{ startMs, endMs }` window to every row. Hierarchy, selection, attribution,
|
|
9
|
+
formatting and failure interpretation stay with the consumer. The original
|
|
10
|
+
minimum/clipped marker remains visible; its tooltip reports the actual duration.
|
|
11
|
+
Set `--waterfall-timing-width`, `--waterfall-duration-width` and the existing
|
|
12
|
+
`--intel-waterfall-*` colors for the consumer's layout and theme. No stylesheet,
|
|
13
|
+
router or React DOM dependency is required.
|
|
14
|
+
|
|
4
15
|
Five figures cover the decisions a benchmark reader makes:
|
|
5
16
|
|
|
6
17
|
| Figure | Question |
|
package/dist/react/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ReactElement } from "react";
|
|
1
|
+
import { ReactElement, ReactNode } from "react";
|
|
2
2
|
//#region src/react/sparkline.d.ts
|
|
3
3
|
type SparklineDirection = "rising" | "falling" | "flat";
|
|
4
4
|
type SparklineValues = readonly (number | null)[];
|
|
@@ -81,4 +81,54 @@ interface StackedBarChartProps {
|
|
|
81
81
|
}
|
|
82
82
|
export declare function StackedBarChart(props: StackedBarChartProps): import("react").JSX.Element;
|
|
83
83
|
//#endregion
|
|
84
|
-
|
|
84
|
+
//#region src/react/waterfall.d.ts
|
|
85
|
+
interface WaterfallWindow {
|
|
86
|
+
readonly startMs: number;
|
|
87
|
+
readonly endMs: number;
|
|
88
|
+
}
|
|
89
|
+
interface WaterfallSpanGeometry {
|
|
90
|
+
readonly offsetPct: number;
|
|
91
|
+
readonly widthPct: number;
|
|
92
|
+
readonly startOffsetMs: number;
|
|
93
|
+
readonly durationMs: number;
|
|
94
|
+
readonly clipped: boolean;
|
|
95
|
+
}
|
|
96
|
+
/** The Intelligence clipping/minimum-marker policy, applied to relative times. */
|
|
97
|
+
export declare function waterfallSpanGeometry(startMs: number, endMs: number, window: WaterfallWindow): WaterfallSpanGeometry;
|
|
98
|
+
/** Intelligence's round-duration ticks, with the original 208px default. */
|
|
99
|
+
export declare function waterfallTicks(windowMs: number, widthPx?: number, minLabelGapPx?: number): number[];
|
|
100
|
+
export declare function formatWaterfallDuration(ms: number): string;
|
|
101
|
+
interface WaterfallTimeAxisProps {
|
|
102
|
+
windowMs: number;
|
|
103
|
+
originMs?: number;
|
|
104
|
+
widthPx?: number;
|
|
105
|
+
formatDuration?: (ms: number) => string;
|
|
106
|
+
className?: string;
|
|
107
|
+
}
|
|
108
|
+
export declare function WaterfallTimeAxis({ windowMs, originMs, widthPx, formatDuration, className }: WaterfallTimeAxisProps): import("react").JSX.Element | null;
|
|
109
|
+
interface WaterfallBarProps {
|
|
110
|
+
startMs: number;
|
|
111
|
+
endMs: number;
|
|
112
|
+
window: WaterfallWindow;
|
|
113
|
+
tone?: "default" | "error" | "critical";
|
|
114
|
+
formatDuration?: (ms: number) => string;
|
|
115
|
+
className?: string;
|
|
116
|
+
}
|
|
117
|
+
export declare function WaterfallBar({ startMs, endMs, window, tone, formatDuration, className }: WaterfallBarProps): import("react").JSX.Element;
|
|
118
|
+
interface WaterfallRowProps extends Omit<WaterfallBarProps, "className"> {
|
|
119
|
+
label: ReactNode;
|
|
120
|
+
attribution?: ReactNode;
|
|
121
|
+
timingClassName?: string;
|
|
122
|
+
durationClassName?: string;
|
|
123
|
+
className?: string;
|
|
124
|
+
}
|
|
125
|
+
/** Non-interactive row body; its caller owns treeitems, buttons and selection. */
|
|
126
|
+
export declare function WaterfallRow({ label, attribution, startMs, endMs, window, tone, formatDuration, className, timingClassName, durationClassName }: WaterfallRowProps): import("react").JSX.Element;
|
|
127
|
+
interface WaterfallHeaderProps extends WaterfallTimeAxisProps {
|
|
128
|
+
label?: ReactNode;
|
|
129
|
+
attribution?: ReactNode;
|
|
130
|
+
durationLabel?: ReactNode;
|
|
131
|
+
}
|
|
132
|
+
export declare function WaterfallHeader({ label, attribution, durationLabel, ...axis }: WaterfallHeaderProps): import("react").JSX.Element;
|
|
133
|
+
//#endregion
|
|
134
|
+
export type { SparklineDirection, SparklineGeometry, SparklineGeometryOptions, SparklineLabelOptions, SparklinePoint, SparklineProps, SparklineValues, StackedBarBucket, StackedBarChartProps, StackedBarSegment, StackedBarSeries, WaterfallBarProps, WaterfallHeaderProps, WaterfallRowProps, WaterfallSpanGeometry, WaterfallTimeAxisProps, WaterfallWindow };
|
package/dist/react/index.js
CHANGED
|
@@ -535,4 +535,224 @@ function StackedBarChart(props) {
|
|
|
535
535
|
});
|
|
536
536
|
}
|
|
537
537
|
//#endregion
|
|
538
|
-
|
|
538
|
+
//#region src/react/waterfall.tsx
|
|
539
|
+
/** The Intelligence clipping/minimum-marker policy, applied to relative times. */
|
|
540
|
+
function waterfallSpanGeometry(startMs, endMs, window) {
|
|
541
|
+
if (![
|
|
542
|
+
startMs,
|
|
543
|
+
endMs,
|
|
544
|
+
window.startMs,
|
|
545
|
+
window.endMs
|
|
546
|
+
].every(Number.isFinite) || endMs < startMs || window.endMs < window.startMs) throw new RangeError("Waterfall requires finite, ordered span and window times");
|
|
547
|
+
const durationMs = endMs - startMs;
|
|
548
|
+
const startOffsetMs = startMs - window.startMs;
|
|
549
|
+
const windowMs = window.endMs - window.startMs;
|
|
550
|
+
const span = windowMs === 0 ? 1 : windowMs;
|
|
551
|
+
const rawOffsetPct = startOffsetMs / span * 100;
|
|
552
|
+
const endPct = rawOffsetPct + durationMs / span * 100;
|
|
553
|
+
const fullyOutside = endPct <= 0 || rawOffsetPct >= 100;
|
|
554
|
+
const offsetPct = Math.min(99, Math.max(0, rawOffsetPct));
|
|
555
|
+
return {
|
|
556
|
+
offsetPct,
|
|
557
|
+
widthPct: fullyOutside ? .5 : Math.max(.5, Math.min(100 - offsetPct, endPct - offsetPct)),
|
|
558
|
+
startOffsetMs,
|
|
559
|
+
durationMs,
|
|
560
|
+
clipped: fullyOutside || rawOffsetPct < 0 || endPct > 100
|
|
561
|
+
};
|
|
562
|
+
}
|
|
563
|
+
function niceTickMs(spanMs, target = 4) {
|
|
564
|
+
if (!(spanMs > 0)) return 1;
|
|
565
|
+
const rough = spanMs / target;
|
|
566
|
+
const mag = 10 ** Math.floor(Math.log10(rough));
|
|
567
|
+
const norm = rough / mag;
|
|
568
|
+
return (norm >= 5 ? 5 : norm >= 2 ? 2 : 1) * mag;
|
|
569
|
+
}
|
|
570
|
+
/** Intelligence's round-duration ticks, with the original 208px default. */
|
|
571
|
+
function waterfallTicks(windowMs, widthPx = 208, minLabelGapPx = 64) {
|
|
572
|
+
if (!Number.isFinite(windowMs) || windowMs < 0 || !Number.isFinite(widthPx) || widthPx <= 0 || !Number.isFinite(minLabelGapPx) || minLabelGapPx <= 0) throw new RangeError("Waterfall axis requires finite, nonnegative time and positive dimensions");
|
|
573
|
+
if (windowMs === 0) return [];
|
|
574
|
+
const step = niceTickMs(windowMs, Math.max(2, Math.floor(widthPx / minLabelGapPx)));
|
|
575
|
+
const raw = [];
|
|
576
|
+
for (let t = 0; t <= windowMs + 1e-6; t += step) raw.push(t);
|
|
577
|
+
const minGapPct = minLabelGapPx / widthPx * 100;
|
|
578
|
+
const kept = [];
|
|
579
|
+
let lastPct = Number.NEGATIVE_INFINITY;
|
|
580
|
+
for (const t of raw) {
|
|
581
|
+
const pct = t / windowMs * 100;
|
|
582
|
+
if (kept.length === 0 || pct - lastPct >= minGapPct) {
|
|
583
|
+
kept.push(t);
|
|
584
|
+
lastPct = pct;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
return kept;
|
|
588
|
+
}
|
|
589
|
+
function formatWaterfallDuration(ms) {
|
|
590
|
+
if (ms < 1e3) return `${Math.round(ms)}ms`;
|
|
591
|
+
if (ms < 6e4) return `${(ms / 1e3).toFixed(1)}s`;
|
|
592
|
+
if (ms < 36e5) return `${(ms / 6e4).toFixed(1)}m`;
|
|
593
|
+
return `${(ms / 36e5).toFixed(1)}h`;
|
|
594
|
+
}
|
|
595
|
+
const timingCell = {
|
|
596
|
+
width: "var(--waterfall-timing-width, 208px)",
|
|
597
|
+
flexShrink: 0
|
|
598
|
+
};
|
|
599
|
+
const rowStyle = {
|
|
600
|
+
display: "flex",
|
|
601
|
+
alignItems: "center",
|
|
602
|
+
gap: "0.5rem",
|
|
603
|
+
width: "100%",
|
|
604
|
+
minWidth: 0
|
|
605
|
+
};
|
|
606
|
+
const labelStyle = {
|
|
607
|
+
display: "flex",
|
|
608
|
+
alignItems: "center",
|
|
609
|
+
gap: "0.5rem",
|
|
610
|
+
flex: "1 1 0%",
|
|
611
|
+
minWidth: 0
|
|
612
|
+
};
|
|
613
|
+
const durationStyle = {
|
|
614
|
+
width: "var(--waterfall-duration-width, 4rem)",
|
|
615
|
+
flexShrink: 0,
|
|
616
|
+
textAlign: "right",
|
|
617
|
+
fontFamily: "var(--font-mono, monospace)",
|
|
618
|
+
fontVariantNumeric: "tabular-nums"
|
|
619
|
+
};
|
|
620
|
+
const ordered = (start, end) => Number.isFinite(start) && Number.isFinite(end) && end >= start;
|
|
621
|
+
function WaterfallTimeAxis({ windowMs, originMs = 0, widthPx = 208, formatDuration = formatWaterfallDuration, className }) {
|
|
622
|
+
if (!ordered(0, windowMs) || !Number.isFinite(originMs)) return null;
|
|
623
|
+
const ticks = waterfallTicks(windowMs, widthPx);
|
|
624
|
+
if (windowMs === 0) return null;
|
|
625
|
+
return /* @__PURE__ */ jsx("div", {
|
|
626
|
+
className,
|
|
627
|
+
"data-waterfall-axis": "",
|
|
628
|
+
"aria-hidden": "true",
|
|
629
|
+
style: {
|
|
630
|
+
...timingCell,
|
|
631
|
+
position: "relative",
|
|
632
|
+
height: "var(--waterfall-axis-height, 1.5rem)"
|
|
633
|
+
},
|
|
634
|
+
children: ticks.map((t) => {
|
|
635
|
+
const pct = t / windowMs * 100;
|
|
636
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
637
|
+
style: {
|
|
638
|
+
position: "absolute",
|
|
639
|
+
top: 0,
|
|
640
|
+
bottom: 0,
|
|
641
|
+
left: `${pct}%`,
|
|
642
|
+
display: "flex",
|
|
643
|
+
flexDirection: "column",
|
|
644
|
+
alignItems: "start"
|
|
645
|
+
},
|
|
646
|
+
children: [/* @__PURE__ */ jsx("span", { style: {
|
|
647
|
+
height: "0.5rem",
|
|
648
|
+
width: 1,
|
|
649
|
+
background: "var(--intel-chart-grid, var(--color-border, #d1d3e1))"
|
|
650
|
+
} }), /* @__PURE__ */ jsx("span", {
|
|
651
|
+
style: {
|
|
652
|
+
marginTop: "0.25rem",
|
|
653
|
+
whiteSpace: "nowrap",
|
|
654
|
+
fontFamily: "var(--font-mono, monospace)",
|
|
655
|
+
fontSize: "var(--waterfall-axis-font-size, 11px)",
|
|
656
|
+
lineHeight: 1,
|
|
657
|
+
fontVariantNumeric: "tabular-nums",
|
|
658
|
+
transform: pct > 75 ? "translateX(-100%)" : void 0
|
|
659
|
+
},
|
|
660
|
+
children: formatDuration(originMs + t)
|
|
661
|
+
})]
|
|
662
|
+
}, t);
|
|
663
|
+
})
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
function WaterfallBar({ startMs, endMs, window, tone = "default", formatDuration = formatWaterfallDuration, className }) {
|
|
667
|
+
if (!ordered(startMs, endMs) || !ordered(window.startMs, window.endMs)) return /* @__PURE__ */ jsx("span", {
|
|
668
|
+
className,
|
|
669
|
+
"data-waterfall-track": "",
|
|
670
|
+
"data-unavailable": "true",
|
|
671
|
+
title: "Timing unavailable",
|
|
672
|
+
style: {
|
|
673
|
+
...timingCell,
|
|
674
|
+
textAlign: "center"
|
|
675
|
+
},
|
|
676
|
+
children: "—"
|
|
677
|
+
});
|
|
678
|
+
const geometry = waterfallSpanGeometry(startMs, endMs, window);
|
|
679
|
+
const color = tone === "error" ? "var(--intel-waterfall-error, #dc2626)" : tone === "critical" ? "var(--intel-waterfall-critical, #0891b2)" : "var(--intel-waterfall-bar, #2563eb)";
|
|
680
|
+
return /* @__PURE__ */ jsx("span", {
|
|
681
|
+
className,
|
|
682
|
+
"data-waterfall-track": "",
|
|
683
|
+
"data-tone": tone,
|
|
684
|
+
title: `+${formatDuration(geometry.startOffsetMs)} · ${formatDuration(geometry.durationMs)}${tone === "critical" ? " · critical path" : ""}`,
|
|
685
|
+
style: {
|
|
686
|
+
...timingCell,
|
|
687
|
+
position: "relative",
|
|
688
|
+
height: "var(--waterfall-bar-height, 0.875rem)",
|
|
689
|
+
overflow: "hidden",
|
|
690
|
+
borderRadius: "var(--waterfall-bar-radius, 999px)",
|
|
691
|
+
background: "var(--intel-waterfall-track, rgb(148 163 184 / 22%))"
|
|
692
|
+
},
|
|
693
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
694
|
+
"data-waterfall-bar": "",
|
|
695
|
+
"data-clipped": geometry.clipped || void 0,
|
|
696
|
+
style: {
|
|
697
|
+
position: "absolute",
|
|
698
|
+
top: 0,
|
|
699
|
+
height: "100%",
|
|
700
|
+
borderRadius: "inherit",
|
|
701
|
+
background: color,
|
|
702
|
+
left: `${geometry.offsetPct}%`,
|
|
703
|
+
width: `${geometry.widthPct}%`,
|
|
704
|
+
boxShadow: tone === "default" ? void 0 : `0 0 0 1px ${color}`
|
|
705
|
+
}
|
|
706
|
+
})
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
/** Non-interactive row body; its caller owns treeitems, buttons and selection. */
|
|
710
|
+
function WaterfallRow({ label, attribution, startMs, endMs, window, tone, formatDuration = formatWaterfallDuration, className, timingClassName, durationClassName }) {
|
|
711
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
712
|
+
"data-waterfall-row": "",
|
|
713
|
+
className,
|
|
714
|
+
style: rowStyle,
|
|
715
|
+
children: [
|
|
716
|
+
/* @__PURE__ */ jsx("span", {
|
|
717
|
+
"data-waterfall-label": "",
|
|
718
|
+
style: labelStyle,
|
|
719
|
+
children: label
|
|
720
|
+
}),
|
|
721
|
+
attribution,
|
|
722
|
+
/* @__PURE__ */ jsx(WaterfallBar, {
|
|
723
|
+
startMs,
|
|
724
|
+
endMs,
|
|
725
|
+
window,
|
|
726
|
+
tone,
|
|
727
|
+
formatDuration,
|
|
728
|
+
className: timingClassName
|
|
729
|
+
}),
|
|
730
|
+
/* @__PURE__ */ jsx("span", {
|
|
731
|
+
"data-waterfall-duration": "",
|
|
732
|
+
className: durationClassName,
|
|
733
|
+
style: durationStyle,
|
|
734
|
+
children: ordered(startMs, endMs) ? formatDuration(endMs - startMs) : "—"
|
|
735
|
+
})
|
|
736
|
+
]
|
|
737
|
+
});
|
|
738
|
+
}
|
|
739
|
+
function WaterfallHeader({ label = "Span", attribution, durationLabel = "Duration", ...axis }) {
|
|
740
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
741
|
+
"data-waterfall-header": "",
|
|
742
|
+
style: rowStyle,
|
|
743
|
+
children: [
|
|
744
|
+
/* @__PURE__ */ jsx("span", {
|
|
745
|
+
style: labelStyle,
|
|
746
|
+
children: label
|
|
747
|
+
}),
|
|
748
|
+
attribution,
|
|
749
|
+
/* @__PURE__ */ jsx(WaterfallTimeAxis, { ...axis }),
|
|
750
|
+
/* @__PURE__ */ jsx("span", {
|
|
751
|
+
style: durationStyle,
|
|
752
|
+
children: durationLabel
|
|
753
|
+
})
|
|
754
|
+
]
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
//#endregion
|
|
758
|
+
export { DEFAULT_SPARKLINE_EMPTY_LABEL, DEFAULT_SPARKLINE_HEIGHT, DEFAULT_SPARKLINE_LABEL, DEFAULT_SPARKLINE_UNAVAILABLE_LABEL, DEFAULT_SPARKLINE_WIDTH, Sparkline, StackedBarChart, WaterfallBar, WaterfallHeader, WaterfallRow, WaterfallTimeAxis, formatSparklineValue, formatWaterfallDuration, sparklineGeometry, sparklineLabel, sparklinePointsAttribute, sparklineReadings, waterfallSpanGeometry, waterfallTicks };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tangle-network/charts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Benchmark figures as static SVG and HTML tables: ranked rates, cost frontier, comparison intervals, task matrix and breakdown. Draws from producer data and refuses what it cannot draw honestly.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|