@pharos-ds/charts 0.0.0-bootstrap.0 → 1.0.10

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.
Files changed (139) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/LICENSE +24 -0
  3. package/README.md +118 -0
  4. package/dist/Chart.d.ts +62 -0
  5. package/dist/Chart.d.ts.map +1 -0
  6. package/dist/Chart.js +401 -0
  7. package/dist/ChartAxis.d.ts +37 -0
  8. package/dist/ChartAxis.d.ts.map +1 -0
  9. package/dist/ChartAxis.js +233 -0
  10. package/dist/ChartContext.d.ts +4 -0
  11. package/dist/ChartContext.d.ts.map +1 -0
  12. package/dist/ChartContext.js +15 -0
  13. package/dist/ChartGrid.d.ts +26 -0
  14. package/dist/ChartGrid.d.ts.map +1 -0
  15. package/dist/ChartGrid.js +118 -0
  16. package/dist/ChartLegend.d.ts +14 -0
  17. package/dist/ChartLegend.d.ts.map +1 -0
  18. package/dist/ChartLegend.js +62 -0
  19. package/dist/ChartSwatch.d.ts +36 -0
  20. package/dist/ChartSwatch.d.ts.map +1 -0
  21. package/dist/ChartSwatch.js +85 -0
  22. package/dist/ChartTooltip.d.ts +46 -0
  23. package/dist/ChartTooltip.d.ts.map +1 -0
  24. package/dist/ChartTooltip.js +330 -0
  25. package/dist/charts.css +33 -0
  26. package/dist/dataTable.d.ts +56 -0
  27. package/dist/dataTable.d.ts.map +1 -0
  28. package/dist/dataTable.js +88 -0
  29. package/dist/formatters.d.ts +37 -0
  30. package/dist/formatters.d.ts.map +1 -0
  31. package/dist/formatters.js +156 -0
  32. package/dist/getChartColors.d.ts +67 -0
  33. package/dist/getChartColors.d.ts.map +1 -0
  34. package/dist/getChartColors.js +191 -0
  35. package/dist/index.d.ts +17 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +19 -0
  38. package/dist/layout.d.ts +40 -0
  39. package/dist/layout.d.ts.map +1 -0
  40. package/dist/layout.js +322 -0
  41. package/dist/legend.d.ts +17 -0
  42. package/dist/legend.d.ts.map +1 -0
  43. package/dist/legend.js +40 -0
  44. package/dist/markColor.d.ts +20 -0
  45. package/dist/markColor.d.ts.map +1 -0
  46. package/dist/markColor.js +56 -0
  47. package/dist/marks/area.d.ts +19 -0
  48. package/dist/marks/area.d.ts.map +1 -0
  49. package/dist/marks/area.js +121 -0
  50. package/dist/marks/band.d.ts +9 -0
  51. package/dist/marks/band.d.ts.map +1 -0
  52. package/dist/marks/band.js +64 -0
  53. package/dist/marks/bar.d.ts +21 -0
  54. package/dist/marks/bar.d.ts.map +1 -0
  55. package/dist/marks/bar.js +161 -0
  56. package/dist/marks/candlestick.d.ts +16 -0
  57. package/dist/marks/candlestick.d.ts.map +1 -0
  58. package/dist/marks/candlestick.js +110 -0
  59. package/dist/marks/dot.d.ts +19 -0
  60. package/dist/marks/dot.d.ts.map +1 -0
  61. package/dist/marks/dot.js +98 -0
  62. package/dist/marks/dotGL.d.ts +16 -0
  63. package/dist/marks/dotGL.d.ts.map +1 -0
  64. package/dist/marks/dotGL.js +231 -0
  65. package/dist/marks/dotGLInteractive.d.ts +35 -0
  66. package/dist/marks/dotGLInteractive.d.ts.map +1 -0
  67. package/dist/marks/dotGLInteractive.js +584 -0
  68. package/dist/marks/errorBar.d.ts +29 -0
  69. package/dist/marks/errorBar.d.ts.map +1 -0
  70. package/dist/marks/errorBar.js +95 -0
  71. package/dist/marks/heatmapGL.d.ts +27 -0
  72. package/dist/marks/heatmapGL.d.ts.map +1 -0
  73. package/dist/marks/heatmapGL.js +335 -0
  74. package/dist/marks/index.d.ts +13 -0
  75. package/dist/marks/index.d.ts.map +1 -0
  76. package/dist/marks/index.js +14 -0
  77. package/dist/marks/line.d.ts +18 -0
  78. package/dist/marks/line.d.ts.map +1 -0
  79. package/dist/marks/line.js +83 -0
  80. package/dist/marks/referenceLine.d.ts +37 -0
  81. package/dist/marks/referenceLine.d.ts.map +1 -0
  82. package/dist/marks/referenceLine.js +204 -0
  83. package/dist/marks/streamGL.d.ts +41 -0
  84. package/dist/marks/streamGL.d.ts.map +1 -0
  85. package/dist/marks/streamGL.js +317 -0
  86. package/dist/tooltip.d.ts +38 -0
  87. package/dist/tooltip.d.ts.map +1 -0
  88. package/dist/tooltip.js +57 -0
  89. package/dist/types.d.ts +193 -0
  90. package/dist/types.d.ts.map +1 -0
  91. package/dist/types.js +70 -0
  92. package/dist/useChartColors.d.ts +16 -0
  93. package/dist/useChartColors.d.ts.map +1 -0
  94. package/dist/useChartColors.js +37 -0
  95. package/dist/utils.d.ts +15 -0
  96. package/dist/utils.d.ts.map +1 -0
  97. package/dist/utils.js +31 -0
  98. package/dist/webgl.d.ts +77 -0
  99. package/dist/webgl.d.ts.map +1 -0
  100. package/dist/webgl.js +235 -0
  101. package/package.json +72 -4
  102. package/src/Chart.doc.mjs +174 -0
  103. package/src/Chart.test.tsx +273 -0
  104. package/src/Chart.tsx +535 -0
  105. package/src/ChartAxis.tsx +289 -0
  106. package/src/ChartContext.ts +18 -0
  107. package/src/ChartGrid.tsx +136 -0
  108. package/src/ChartLegend.tsx +77 -0
  109. package/src/ChartSwatch.doc.mjs +17 -0
  110. package/src/ChartSwatch.tsx +89 -0
  111. package/src/ChartTooltip.tsx +452 -0
  112. package/src/charts.css.d.ts +3 -0
  113. package/src/dataTable.test.ts +86 -0
  114. package/src/dataTable.ts +124 -0
  115. package/src/formatters.ts +167 -0
  116. package/src/getChartColors.test.ts +114 -0
  117. package/src/getChartColors.ts +282 -0
  118. package/src/index.ts +88 -0
  119. package/src/layout.ts +372 -0
  120. package/src/legend.ts +44 -0
  121. package/src/markColor.ts +70 -0
  122. package/src/marks/area.tsx +142 -0
  123. package/src/marks/band.tsx +86 -0
  124. package/src/marks/bar.tsx +210 -0
  125. package/src/marks/candlestick.tsx +128 -0
  126. package/src/marks/dot.tsx +119 -0
  127. package/src/marks/dotGL.tsx +274 -0
  128. package/src/marks/dotGLInteractive.tsx +660 -0
  129. package/src/marks/errorBar.tsx +119 -0
  130. package/src/marks/heatmapGL.tsx +392 -0
  131. package/src/marks/index.ts +17 -0
  132. package/src/marks/line.tsx +118 -0
  133. package/src/marks/referenceLine.tsx +252 -0
  134. package/src/marks/streamGL.tsx +381 -0
  135. package/src/tooltip.ts +73 -0
  136. package/src/types.ts +212 -0
  137. package/src/useChartColors.ts +41 -0
  138. package/src/utils.ts +38 -0
  139. package/src/webgl.ts +262 -0
@@ -0,0 +1,204 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file marks/referenceLine.tsx
5
+ * @output Horizontal or vertical reference line/band annotation. Labels are
6
+ * clamped to stay fully inside the plot (which is clipped) for both
7
+ * orientations, and non-finite y/x values render nothing.
8
+ */
9
+
10
+ import { isBandScale } from "../utils.js";
11
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
12
+ // Badge dimensions — matches crosshair label style.
13
+ const BADGE_H = 14;
14
+ const BADGE_RX = 3;
15
+ const LABEL_FONT_SIZE = 10;
16
+ // Rough per-glyph advance + horizontal padding for width estimation.
17
+ const LABEL_CHAR_W = 6.5;
18
+ const LABEL_PAD = 12;
19
+ const clamp = (v, lo, hi) => Math.max(lo, Math.min(v, hi));
20
+
21
+ /** Estimated badge width for a label (0 when there's no label). */
22
+ function labelWidth(label) {
23
+ return label ? label.length * LABEL_CHAR_W + LABEL_PAD : 0;
24
+ }
25
+
26
+ /**
27
+ * The label badge, positioned by its top-left corner so callers can clamp it
28
+ * into the plot uniformly regardless of orientation.
29
+ */
30
+ function ReferenceLabel({
31
+ x,
32
+ topY,
33
+ width,
34
+ color,
35
+ text
36
+ }) {
37
+ return /*#__PURE__*/_jsxs("g", {
38
+ transform: `translate(${x},${topY})`,
39
+ pointerEvents: "none",
40
+ children: [/*#__PURE__*/_jsx("rect", {
41
+ x: 0,
42
+ y: 0,
43
+ width: width,
44
+ height: BADGE_H,
45
+ rx: BADGE_RX,
46
+ fill: "var(--color-background-popover)",
47
+ fillOpacity: 0.85,
48
+ stroke: color,
49
+ strokeWidth: 0.5
50
+ }), /*#__PURE__*/_jsx("text", {
51
+ x: width / 2,
52
+ y: BADGE_H / 2,
53
+ dy: "0.35em",
54
+ textAnchor: "middle",
55
+ fontSize: LABEL_FONT_SIZE,
56
+ fontWeight: 500,
57
+ fill: color,
58
+ children: text
59
+ })]
60
+ });
61
+ }
62
+
63
+ /**
64
+ * Reference line or band annotation. Not a data series — renders at fixed values.
65
+ *
66
+ * @example
67
+ * ```
68
+ * series={[line('revenue'), referenceLine({y: 100, label: 'Target'})]}
69
+ * ```
70
+ */
71
+ export function referenceLine(options) {
72
+ const color = options.color ?? 'var(--color-border-emphasized)';
73
+ const strokeWidth = options.strokeWidth ?? 1;
74
+ const strokeDasharray = options.strokeDasharray ?? '6 3';
75
+ const label = options.label;
76
+ const labelPosition = options.labelPosition ?? 'end';
77
+ const bandOpacity = options.bandOpacity ?? 0.1;
78
+ const textW = labelWidth(label);
79
+ return {
80
+ type: 'referenceLine',
81
+ key: `ref-${options.y ?? options.x ?? 'none'}`,
82
+ dataKeys: [],
83
+ layout: {},
84
+ // No data points to resolve — this is a fixed annotation
85
+ resolve() {
86
+ return [];
87
+ },
88
+ render(_resolved, ctx) {
89
+ const {
90
+ width,
91
+ height,
92
+ xScale,
93
+ yScale
94
+ } = ctx;
95
+ // Keep the badge fully inside the (clipped) plot on both axes.
96
+ const bxMax = width - textW - 2;
97
+
98
+ // Horizontal reference line or band
99
+ if (options.y != null) {
100
+ if (!Number.isFinite(options.y)) {
101
+ return null;
102
+ }
103
+ const py = yScale(options.y);
104
+ if (!Number.isFinite(py)) {
105
+ return null;
106
+ }
107
+ const desiredBx = labelPosition === 'end' ? bxMax : 2;
108
+ const bx = clamp(desiredBx, 2, bxMax);
109
+ const labelTopY = clamp(py - BADGE_H / 2, 0, height - BADGE_H);
110
+
111
+ // Band mode: shaded region between y and y2
112
+ if (options.y2 != null && Number.isFinite(options.y2)) {
113
+ const py2 = yScale(options.y2);
114
+ const top = Math.min(py, py2);
115
+ const bandHeight = Math.abs(py2 - py);
116
+ return /*#__PURE__*/_jsxs("g", {
117
+ children: [/*#__PURE__*/_jsx("rect", {
118
+ x: 0,
119
+ y: top,
120
+ width: width,
121
+ height: bandHeight,
122
+ fill: color,
123
+ opacity: bandOpacity
124
+ }), /*#__PURE__*/_jsx("line", {
125
+ x1: 0,
126
+ x2: width,
127
+ y1: py,
128
+ y2: py,
129
+ stroke: color,
130
+ strokeWidth: strokeWidth,
131
+ strokeDasharray: strokeDasharray
132
+ }), /*#__PURE__*/_jsx("line", {
133
+ x1: 0,
134
+ x2: width,
135
+ y1: py2,
136
+ y2: py2,
137
+ stroke: color,
138
+ strokeWidth: strokeWidth,
139
+ strokeDasharray: strokeDasharray
140
+ }), label && /*#__PURE__*/_jsx(ReferenceLabel, {
141
+ x: bx,
142
+ topY: labelTopY,
143
+ width: textW,
144
+ color: color,
145
+ text: label
146
+ })]
147
+ });
148
+ }
149
+
150
+ // Single horizontal line
151
+ return /*#__PURE__*/_jsxs("g", {
152
+ children: [/*#__PURE__*/_jsx("line", {
153
+ x1: 0,
154
+ x2: width,
155
+ y1: py,
156
+ y2: py,
157
+ stroke: color,
158
+ strokeWidth: strokeWidth,
159
+ strokeDasharray: strokeDasharray
160
+ }), label && /*#__PURE__*/_jsx(ReferenceLabel, {
161
+ x: bx,
162
+ topY: labelTopY,
163
+ width: textW,
164
+ color: color,
165
+ text: label
166
+ })]
167
+ });
168
+ }
169
+
170
+ // Vertical reference line (linear x only — a band scale has no pixel
171
+ // position for an arbitrary numeric x).
172
+ if (options.x != null && !isBandScale(xScale)) {
173
+ if (!Number.isFinite(options.x)) {
174
+ return null;
175
+ }
176
+ const px = xScale(options.x);
177
+ if (!Number.isFinite(px)) {
178
+ return null;
179
+ }
180
+ const desiredTopY = labelPosition === 'end' ? 4 : height - BADGE_H - 4;
181
+ const labelTopY = clamp(desiredTopY, 0, height - BADGE_H);
182
+ const bx = clamp(px - textW / 2, 2, bxMax);
183
+ return /*#__PURE__*/_jsxs("g", {
184
+ children: [/*#__PURE__*/_jsx("line", {
185
+ x1: px,
186
+ x2: px,
187
+ y1: 0,
188
+ y2: height,
189
+ stroke: color,
190
+ strokeWidth: strokeWidth,
191
+ strokeDasharray: strokeDasharray
192
+ }), label && /*#__PURE__*/_jsx(ReferenceLabel, {
193
+ x: bx,
194
+ topY: labelTopY,
195
+ width: textW,
196
+ color: color,
197
+ text: label
198
+ })]
199
+ });
200
+ }
201
+ return null;
202
+ }
203
+ };
204
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @file marks/streamGL.tsx
3
+ * @output WebGL streaming time-series line — ring buffer, canvas outside SVG
4
+ *
5
+ * Data is pushed imperatively via the returned handle, not from the static dataset.
6
+ * The chart's xScale/yScale map pushed values to pixel coordinates.
7
+ *
8
+ * Draws are coalesced onto a single requestAnimationFrame so a burst of pushes
9
+ * repaints once per frame. The GL context, program, and vertex buffer are made
10
+ * once and reused; all are released (rAF cancelled, context eagerly lost) on
11
+ * unmount. Redraws on resize / scale change and recovers from context loss.
12
+ */
13
+ import { type MutableRefObject } from 'react';
14
+ import type { SeriesDef } from '../types';
15
+ export interface StreamGLHandle {
16
+ push(x: number, y: number): void;
17
+ clear(): void;
18
+ }
19
+ export interface StreamGLOptions {
20
+ color: string;
21
+ bufferSize?: number;
22
+ lineWidth?: number;
23
+ opacity?: number;
24
+ /** Ref to receive the push/clear handle */
25
+ handleRef?: MutableRefObject<StreamGLHandle | null>;
26
+ }
27
+ /**
28
+ * WebGL streaming line. Data is pushed imperatively via handleRef,
29
+ * not from the chart's static dataset. Internally uses a ring buffer.
30
+ *
31
+ * @example
32
+ * ```
33
+ * const streamRef = useRef<StreamGLHandle>(null);
34
+ * // Push data points in a rAF loop or event handler:
35
+ * streamRef.current?.push(Date.now(), value);
36
+ *
37
+ * <Chart series={[streamGL({color: '#10b981', handleRef: streamRef})]} />
38
+ * ```
39
+ */
40
+ export declare function streamGL(options: StreamGLOptions): SeriesDef;
41
+ //# sourceMappingURL=streamGL.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"streamGL.d.ts","sourceRoot":"","sources":["../../src/marks/streamGL.tsx"],"names":[],"mappings":"AAEA;;;;;;;;;;;GAWG;AAEH,OAAO,EAML,KAAK,gBAAgB,EACtB,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,UAAU,CAAC;AAaxC,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,KAAK,IAAI,IAAI,CAAC;CACf;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,gBAAgB,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;CACrD;AA+RD;;;;;;;;;;;;GAYG;AACH,wBAAgB,QAAQ,CAAC,OAAO,EAAE,eAAe,GAAG,SAAS,CAgC5D"}
@@ -0,0 +1,317 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file marks/streamGL.tsx
5
+ * @output WebGL streaming time-series line — ring buffer, canvas outside SVG
6
+ *
7
+ * Data is pushed imperatively via the returned handle, not from the static dataset.
8
+ * The chart's xScale/yScale map pushed values to pixel coordinates.
9
+ *
10
+ * Draws are coalesced onto a single requestAnimationFrame so a burst of pushes
11
+ * repaints once per frame. The GL context, program, and vertex buffer are made
12
+ * once and reused; all are released (rAF cancelled, context eagerly lost) on
13
+ * unmount. Redraws on resize / scale change and recovers from context loss.
14
+ */
15
+
16
+ import { useRef, useEffect, useImperativeHandle, useCallback, useState } from 'react';
17
+ import { useChart } from "../ChartContext.js";
18
+ import { hexToGL, getWebGLContext, setupGLState, sizeCanvas, mountCanvasOverSVG, createProgram, registerContextLossHandlers, loseContext } from "../webgl.js";
19
+ import { jsx as _jsx } from "react/jsx-runtime";
20
+ const VERT = `
21
+ attribute vec2 a_position;
22
+ uniform vec2 u_resolution;
23
+ void main() {
24
+ vec2 clip = (a_position / u_resolution) * 2.0 - 1.0;
25
+ gl_Position = vec4(clip.x, -clip.y, 0.0, 1.0);
26
+ }
27
+ `;
28
+ const FRAG = `
29
+ precision mediump float;
30
+ uniform vec3 u_color;
31
+ uniform float u_opacity;
32
+ void main() {
33
+ float a = u_opacity;
34
+ gl_FragColor = vec4(u_color * a, a);
35
+ }
36
+ `;
37
+ /**
38
+ * Inline component — manages the ring buffer, canvas, and rAF draw loop.
39
+ */
40
+ function StreamGLCanvas({
41
+ color,
42
+ bufferSize,
43
+ lineWidth,
44
+ opacity,
45
+ width,
46
+ height,
47
+ handleRef
48
+ }) {
49
+ const {
50
+ xScale,
51
+ yScale
52
+ } = useChart();
53
+ const canvasRef = useRef(null);
54
+ const glRef = useRef(null);
55
+ const programRef = useRef(null);
56
+ const bufRef = useRef(null);
57
+ const locsRef = useRef(null);
58
+ const markerRef = useRef(null);
59
+ const rafRef = useRef(null);
60
+ // Bumped on context restore to force a rebuild + repaint.
61
+ const [contextEpoch, setContextEpoch] = useState(0);
62
+
63
+ // Ring buffer + a reusable scratch array for the pixel-space draw data. The
64
+ // capacity is captured on the ring itself so head/count math stays consistent
65
+ // even if the `bufferSize` prop changes (handled by the resize effect below).
66
+ const ringRef = useRef({
67
+ data: new Float32Array(bufferSize * 2),
68
+ head: 0,
69
+ count: 0,
70
+ capacity: bufferSize
71
+ });
72
+ const scratchRef = useRef(new Float32Array(bufferSize * 2));
73
+
74
+ // Re-allocate the ring if bufferSize changes (rare — resets the stream).
75
+ useEffect(() => {
76
+ const ring = ringRef.current;
77
+ if (ring.capacity !== bufferSize) {
78
+ ring.data = new Float32Array(bufferSize * 2);
79
+ ring.head = 0;
80
+ ring.count = 0;
81
+ ring.capacity = bufferSize;
82
+ scratchRef.current = new Float32Array(bufferSize * 2);
83
+ }
84
+ }, [bufferSize]);
85
+ const draw = useCallback(() => {
86
+ const canvas = canvasRef.current;
87
+ if (!canvas || width <= 0 || height <= 0) {
88
+ return;
89
+ }
90
+ if (!glRef.current) {
91
+ glRef.current = getWebGLContext(canvas);
92
+ }
93
+ const gl = glRef.current;
94
+ if (!gl || gl.isContextLost()) {
95
+ return;
96
+ }
97
+ if (!programRef.current) {
98
+ programRef.current = createProgram(gl, VERT, FRAG);
99
+ locsRef.current = null;
100
+ }
101
+ const program = programRef.current;
102
+ if (!program) {
103
+ return;
104
+ }
105
+ if (!bufRef.current) {
106
+ bufRef.current = gl.createBuffer();
107
+ }
108
+ const buffer = bufRef.current;
109
+ if (!buffer) {
110
+ return;
111
+ }
112
+ if (!locsRef.current) {
113
+ locsRef.current = {
114
+ aPosition: gl.getAttribLocation(program, 'a_position'),
115
+ uResolution: gl.getUniformLocation(program, 'u_resolution'),
116
+ uColor: gl.getUniformLocation(program, 'u_color'),
117
+ uOpacity: gl.getUniformLocation(program, 'u_opacity')
118
+ };
119
+ }
120
+ const locs = locsRef.current;
121
+ gl.viewport(0, 0, canvas.width, canvas.height);
122
+ setupGLState(gl);
123
+ const {
124
+ data: ringData,
125
+ head,
126
+ count,
127
+ capacity
128
+ } = ringRef.current;
129
+ if (count < 2) {
130
+ // Buffer already cleared above — nothing to stroke yet.
131
+ return;
132
+ }
133
+ const linearX = xScale;
134
+ const drawBuf = scratchRef.current;
135
+ for (let i = 0; i < count; i++) {
136
+ const idx = (head - count + i + capacity) % capacity * 2;
137
+ drawBuf[i * 2] = linearX(ringData[idx]);
138
+ drawBuf[i * 2 + 1] = yScale(ringData[idx + 1]);
139
+ }
140
+ gl.useProgram(program);
141
+ gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
142
+ gl.bufferData(gl.ARRAY_BUFFER, drawBuf.subarray(0, count * 2), gl.DYNAMIC_DRAW);
143
+ gl.enableVertexAttribArray(locs.aPosition);
144
+ gl.vertexAttribPointer(locs.aPosition, 2, gl.FLOAT, false, 0, 0);
145
+ const [r, g, b] = hexToGL(color);
146
+ gl.uniform2f(locs.uResolution, width, height);
147
+ gl.uniform3f(locs.uColor, r, g, b);
148
+ gl.uniform1f(locs.uOpacity, opacity);
149
+ gl.lineWidth(lineWidth);
150
+ gl.drawArrays(gl.LINE_STRIP, 0, count);
151
+ }, [width, height, color, lineWidth, opacity, xScale, yScale]);
152
+
153
+ // Keep the latest draw reachable from the single coalesced rAF callback, so a
154
+ // frame scheduled before a scale/size change still paints with current state.
155
+ const drawRef = useRef(draw);
156
+ useEffect(() => {
157
+ drawRef.current = draw;
158
+ }, [draw]);
159
+
160
+ // Coalesce redraws onto one rAF so a burst of pushes paints once per frame.
161
+ // Stable identity keeps the imperative push/clear handle from churning.
162
+ const scheduleDraw = useCallback(() => {
163
+ if (rafRef.current != null) {
164
+ return;
165
+ }
166
+ rafRef.current = requestAnimationFrame(() => {
167
+ rafRef.current = null;
168
+ drawRef.current();
169
+ });
170
+ }, []);
171
+
172
+ // Mount the canvas outside the SVG and wire context-loss recovery.
173
+ useEffect(() => {
174
+ const marker = markerRef.current;
175
+ if (!marker) {
176
+ return;
177
+ }
178
+ if (!canvasRef.current) {
179
+ canvasRef.current = document.createElement('canvas');
180
+ }
181
+ const canvas = canvasRef.current;
182
+ const detach = mountCanvasOverSVG(marker, canvas, width, height);
183
+ const unregister = registerContextLossHandlers(canvas, () => {
184
+ if (rafRef.current != null) {
185
+ cancelAnimationFrame(rafRef.current);
186
+ rafRef.current = null;
187
+ }
188
+ glRef.current = null;
189
+ programRef.current = null;
190
+ bufRef.current = null;
191
+ locsRef.current = null;
192
+ }, () => setContextEpoch(e => e + 1));
193
+ return () => {
194
+ unregister();
195
+ detach?.();
196
+ };
197
+ }, [width, height]);
198
+
199
+ // Size the canvas when the plot area changes.
200
+ useEffect(() => {
201
+ const canvas = canvasRef.current;
202
+ if (!canvas || width <= 0 || height <= 0) {
203
+ return;
204
+ }
205
+ sizeCanvas(canvas, width, height);
206
+ }, [width, height]);
207
+
208
+ // Redraw the retained buffer when scales, size, or the GL context change so a
209
+ // paused stream doesn't leave a stale frame after a resize or window slide.
210
+ useEffect(() => {
211
+ scheduleDraw();
212
+ }, [draw, scheduleDraw, contextEpoch]);
213
+
214
+ // Expose push/clear handle via ref.
215
+ useImperativeHandle(handleRef, () => ({
216
+ push(x, y) {
217
+ if (!Number.isFinite(x) || !Number.isFinite(y)) {
218
+ return;
219
+ }
220
+ const ring = ringRef.current;
221
+ const idx = ring.head * 2;
222
+ ring.data[idx] = x;
223
+ ring.data[idx + 1] = y;
224
+ ring.head = (ring.head + 1) % ring.capacity;
225
+ ring.count = Math.min(ring.count + 1, ring.capacity);
226
+ scheduleDraw();
227
+ },
228
+ clear() {
229
+ const ring = ringRef.current;
230
+ ring.head = 0;
231
+ ring.count = 0;
232
+ if (rafRef.current != null) {
233
+ cancelAnimationFrame(rafRef.current);
234
+ rafRef.current = null;
235
+ }
236
+ const gl = glRef.current;
237
+ if (gl && !gl.isContextLost()) {
238
+ gl.clearColor(0, 0, 0, 0);
239
+ gl.clear(gl.COLOR_BUFFER_BIT);
240
+ }
241
+ }
242
+ }), [scheduleDraw]);
243
+
244
+ // Cancel any pending frame and release all GL resources on unmount.
245
+ useEffect(() => {
246
+ return () => {
247
+ if (rafRef.current != null) {
248
+ cancelAnimationFrame(rafRef.current);
249
+ rafRef.current = null;
250
+ }
251
+ const gl = glRef.current;
252
+ if (gl) {
253
+ if (bufRef.current) {
254
+ gl.deleteBuffer(bufRef.current);
255
+ }
256
+ if (programRef.current) {
257
+ gl.deleteProgram(programRef.current);
258
+ }
259
+ loseContext(gl);
260
+ }
261
+ glRef.current = null;
262
+ programRef.current = null;
263
+ bufRef.current = null;
264
+ locsRef.current = null;
265
+ };
266
+ }, []);
267
+ if (width <= 0 || height <= 0) {
268
+ return null;
269
+ }
270
+ return /*#__PURE__*/_jsx("g", {
271
+ ref: markerRef
272
+ });
273
+ }
274
+
275
+ /**
276
+ * WebGL streaming line. Data is pushed imperatively via handleRef,
277
+ * not from the chart's static dataset. Internally uses a ring buffer.
278
+ *
279
+ * @example
280
+ * ```
281
+ * const streamRef = useRef<StreamGLHandle>(null);
282
+ * // Push data points in a rAF loop or event handler:
283
+ * streamRef.current?.push(Date.now(), value);
284
+ *
285
+ * <Chart series={[streamGL({color: '#10b981', handleRef: streamRef})]} />
286
+ * ```
287
+ */
288
+ export function streamGL(options) {
289
+ const {
290
+ color
291
+ } = options;
292
+ const bufferSize = options.bufferSize ?? 500;
293
+ const lineWidth = options.lineWidth ?? 2;
294
+ const opacity = options.opacity ?? 1;
295
+ const handleRef = options.handleRef;
296
+ return {
297
+ type: 'streamGL',
298
+ key: 'stream',
299
+ dataKeys: [],
300
+ layout: {},
301
+ // Streaming data is pushed, not in the static dataset
302
+ resolve() {
303
+ return [];
304
+ },
305
+ render(_resolved, ctx) {
306
+ return /*#__PURE__*/_jsx(StreamGLCanvas, {
307
+ color: color,
308
+ bufferSize: bufferSize,
309
+ lineWidth: lineWidth,
310
+ opacity: opacity,
311
+ width: ctx.width,
312
+ height: ctx.height,
313
+ handleRef: handleRef
314
+ });
315
+ }
316
+ };
317
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @file tooltip.ts
3
+ * @output Tooltip types + series-value derivation
4
+ * @position Used by Chart to bridge series + hovered datum → tooltip rows.
5
+ * Mirrors the legend.ts split: data shaping lives here, presentation
6
+ * lives in ChartTooltip.tsx.
7
+ */
8
+ import { type SeriesDef } from './types';
9
+ /**
10
+ * One row of tooltip data — represents a single series' value at the
11
+ * hovered x-position. Stable shape so custom `render` functions can rely on it.
12
+ */
13
+ export interface TooltipSeriesValue {
14
+ /** Series key (matches SeriesDef.key) */
15
+ key: string;
16
+ /** Display label — falls back to key when not set on the series */
17
+ label: string;
18
+ /** Series color token */
19
+ color: string;
20
+ /** Mark type — drives swatch variant in the default render */
21
+ type: string;
22
+ /** Raw value at the hovered x for this series */
23
+ value: unknown;
24
+ }
25
+ /**
26
+ * Build the ordered list of tooltip rows for a given hovered datum.
27
+ * Skips utility marks and any series the layout couldn't resolve.
28
+ *
29
+ * Pure / synchronous — safe to memoize on the caller.
30
+ */
31
+ export declare function deriveTooltipSeriesValues(series: readonly SeriesDef[], datum: Record<string, unknown> | undefined, resolvedKeys: ReadonlySet<string>): TooltipSeriesValue[];
32
+ /**
33
+ * Whether a series should render a hover dot at the focused index.
34
+ * Bars get a band-highlight rect instead, and utility marks have no
35
+ * data point to highlight.
36
+ */
37
+ export declare function shouldRenderHoverDot(seriesType: string): boolean;
38
+ //# sourceMappingURL=tooltip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../src/tooltip.ts"],"names":[],"mappings":"AAEA;;;;;;GAMG;AAEH,OAAO,EAA0C,KAAK,SAAS,EAAC,MAAM,SAAS,CAAC;AAEhF;;;GAGG;AACH,MAAM,WAAW,kBAAkB;IACjC,yCAAyC;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,mEAAmE;IACnE,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,8DAA8D;IAC9D,IAAI,EAAE,MAAM,CAAC;IACb,iDAAiD;IACjD,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,SAAS,SAAS,EAAE,EAC5B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC1C,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,GAChC,kBAAkB,EAAE,CAwBtB;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAEhE"}
@@ -0,0 +1,57 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file tooltip.ts
5
+ * @output Tooltip types + series-value derivation
6
+ * @position Used by Chart to bridge series + hovered datum → tooltip rows.
7
+ * Mirrors the legend.ts split: data shaping lives here, presentation
8
+ * lives in ChartTooltip.tsx.
9
+ */
10
+
11
+ import { DEFAULT_SERIES_COLOR, isUtilityMarkType } from "./types.js";
12
+
13
+ /**
14
+ * One row of tooltip data — represents a single series' value at the
15
+ * hovered x-position. Stable shape so custom `render` functions can rely on it.
16
+ */
17
+
18
+ /**
19
+ * Build the ordered list of tooltip rows for a given hovered datum.
20
+ * Skips utility marks and any series the layout couldn't resolve.
21
+ *
22
+ * Pure / synchronous — safe to memoize on the caller.
23
+ */
24
+ export function deriveTooltipSeriesValues(series, datum, resolvedKeys) {
25
+ if (!datum) {
26
+ return [];
27
+ }
28
+ const out = [];
29
+ for (const s of series) {
30
+ if (isUtilityMarkType(s.type)) {
31
+ continue;
32
+ }
33
+ // resolvedKeys holds layout-assigned _uids (collision-free), not `key`.
34
+ if (!s._uid || !resolvedKeys.has(s._uid)) {
35
+ continue;
36
+ }
37
+ out.push({
38
+ key: s.key,
39
+ label: s.label ?? s.key,
40
+ // Same representative-color precedence as the legend, so a series' tooltip
41
+ // swatch and legend swatch always match.
42
+ color: s.color ?? s._resolvedColor ?? DEFAULT_SERIES_COLOR,
43
+ type: s.type,
44
+ value: datum[s.dataKeys[0]]
45
+ });
46
+ }
47
+ return out;
48
+ }
49
+
50
+ /**
51
+ * Whether a series should render a hover dot at the focused index.
52
+ * Bars get a band-highlight rect instead, and utility marks have no
53
+ * data point to highlight.
54
+ */
55
+ export function shouldRenderHoverDot(seriesType) {
56
+ return seriesType !== 'bar' && !isUtilityMarkType(seriesType);
57
+ }