@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
package/dist/layout.js ADDED
@@ -0,0 +1,322 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file layout.ts
5
+ * @output Computes scales, stacking, grouping, then delegates to each series.resolve()
6
+ * @position Called by Chart root — the single layout pass
7
+ *
8
+ * The layout engine does NOT know about specific mark types.
9
+ * It handles cross-series coordination (scales, stacking, grouping)
10
+ * then calls each series' own resolve() method.
11
+ */
12
+
13
+ import { scaleLinear, scaleBand } from 'd3-scale';
14
+ import { stack as d3Stack, stackOrderNone, stackOffsetNone } from 'd3-shape';
15
+ /**
16
+ * Fraction of the data range added as padding above/below for continuous-only
17
+ * charts (no bar/area zero baseline), so endpoints and curve overshoot don't
18
+ * glue to the plot edges.
19
+ */
20
+ const CONTINUOUS_HEADROOM = 0.08;
21
+ /** True only when both ends of an explicit domain are usable finite numbers. */
22
+ function isFiniteDomain(domain) {
23
+ return Number.isFinite(domain[0]) && Number.isFinite(domain[1]);
24
+ }
25
+
26
+ /**
27
+ * Finite [min, max] of a numeric column, robust to the two failure modes of
28
+ * `Math.min(...arr)` / `Math.max(...arr)`: a NaN/Infinity poisoning the result,
29
+ * and a call-stack overflow when spreading a very large array (GL marks push
30
+ * hundreds of thousands of points). Empty or degenerate (all-equal) extents are
31
+ * expanded so the resulting scale never collapses onto a single pixel.
32
+ */
33
+ function finiteExtent(nums) {
34
+ let lo = Infinity;
35
+ let hi = -Infinity;
36
+ for (const v of nums) {
37
+ if (Number.isFinite(v)) {
38
+ if (v < lo) {
39
+ lo = v;
40
+ }
41
+ if (v > hi) {
42
+ hi = v;
43
+ }
44
+ }
45
+ }
46
+ if (!Number.isFinite(lo) || !Number.isFinite(hi)) {
47
+ return [0, 1];
48
+ }
49
+ if (lo === hi) {
50
+ const pad = Math.abs(lo) * 0.5 || 1;
51
+ return [lo - pad, hi + pad];
52
+ }
53
+ return [lo, hi];
54
+ }
55
+ export function computeLayout({
56
+ data,
57
+ xKey,
58
+ series,
59
+ width,
60
+ height,
61
+ yBaseline,
62
+ yDomain,
63
+ xDomain
64
+ }) {
65
+ // A categorical y-axis (heatmap) means BOTH axes are categorical: columns
66
+ // should tile the plot edge-to-edge just like the rows. Drop the bar-style
67
+ // outer padding on the x band scale for these charts so the grid meets the
68
+ // x/y axis lines flush on every side (no asymmetric gap).
69
+ const yBandKey = series.find(s => s.layout.yBandKey != null)?.layout.yBandKey;
70
+ const isCategoricalGrid = yBandKey != null;
71
+
72
+ // ─── 1. X scale ──────────────────────────────────────────────────────
73
+ const xValues = data.map(d => d[xKey]);
74
+ const isNumericX = xValues.length > 0 && xValues.every(v => typeof v === 'number');
75
+ let xScale;
76
+ if (xDomain && isFiniteDomain(xDomain)) {
77
+ // Explicit domain is authoritative (no .nice()) — enables a stable
78
+ // streaming window and a valid scale even when `data` is empty. Trusted
79
+ // only when finite; a NaN/Infinity domain would collapse the scale.
80
+ xScale = scaleLinear().domain(xDomain).range([0, width]);
81
+ } else if (isNumericX) {
82
+ // NaN passes `typeof === 'number'`, so derive the extent defensively.
83
+ const [lo, hi] = finiteExtent(xValues);
84
+ xScale = scaleLinear().domain([lo, hi]).range([0, width]).nice();
85
+ } else {
86
+ xScale = scaleBand().domain(xValues.map(String)).range([0, width]).padding(isCategoricalGrid ? 0 : 0.2);
87
+ }
88
+
89
+ // ─── 2. Y domain from all series dataKeys ────────────────────────────
90
+ // One pass collects the union of data keys, whether any mark wants a zero
91
+ // baseline, and the stack groupings. `stackGroups` is reused verbatim by the
92
+ // stacking pass below (§3) — previously these groups were built twice.
93
+ const allKeys = new Set();
94
+ let includeZero = false;
95
+ const stackGroups = new Map();
96
+ for (const s of series) {
97
+ for (const k of s.dataKeys) {
98
+ allKeys.add(k);
99
+ }
100
+ if (s.layout.includeZero) {
101
+ includeZero = true;
102
+ }
103
+ const primaryKey = s.dataKeys[0];
104
+ if (s.layout.stack && primaryKey != null) {
105
+ const group = stackGroups.get(s.layout.stack) ?? [];
106
+ group.push(primaryKey);
107
+ stackGroups.set(s.layout.stack, group);
108
+ }
109
+ }
110
+ let yMin = Infinity,
111
+ yMax = -Infinity;
112
+
113
+ // Stacked series contribute the full extent of their running cumulative — every
114
+ // layer boundary, not just the net total — mirroring the d3 stackOffsetNone pass
115
+ // in §3 that actually draws them. The 0 baseline is included because stacks are
116
+ // drawn from it. Tracking only the total would let the drawn bars/areas escape
117
+ // the scale: a mixed-sign stack whose interior peaks exceed the net total, or a
118
+ // 'data'-baseline stack whose bars start at 0 (below the smallest positive
119
+ // total), would render outside the y-domain and be clipped. `Number.isFinite`
120
+ // (not `typeof === 'number'`) keeps a stray NaN/Infinity from corrupting the
121
+ // domain into a NaN/Infinity scale; a non-finite layer carries the baseline
122
+ // forward (contributes nothing new) exactly as stackOffsetNone does.
123
+ const stackedKeys = new Set();
124
+ for (const [, keys] of stackGroups) {
125
+ for (const k of keys) {
126
+ stackedKeys.add(k);
127
+ }
128
+ for (const d of data) {
129
+ let acc = 0;
130
+ if (acc < yMin) {
131
+ yMin = acc;
132
+ }
133
+ if (acc > yMax) {
134
+ yMax = acc;
135
+ }
136
+ for (const k of keys) {
137
+ const v = d[k];
138
+ if (Number.isFinite(v)) {
139
+ acc += v;
140
+ if (acc < yMin) {
141
+ yMin = acc;
142
+ }
143
+ if (acc > yMax) {
144
+ yMax = acc;
145
+ }
146
+ }
147
+ }
148
+ }
149
+ }
150
+
151
+ // Non-stacked series contribute their individual finite values.
152
+ for (const d of data) {
153
+ for (const k of allKeys) {
154
+ if (stackedKeys.has(k)) {
155
+ continue;
156
+ } // already handled above
157
+ const v = d[k];
158
+ if (Number.isFinite(v)) {
159
+ const n = v;
160
+ if (n < yMin) {
161
+ yMin = n;
162
+ }
163
+ if (n > yMax) {
164
+ yMax = n;
165
+ }
166
+ }
167
+ }
168
+ }
169
+
170
+ // ─── Resolve the final y-domain ──────────────────────────────────────
171
+ let yDomainFinal;
172
+ let applyNice = true;
173
+ if (yDomain && isFiniteDomain(yDomain)) {
174
+ // Caller owns the exact domain (e.g. zoom/streaming). No baseline,
175
+ // headroom, or .nice() — rounding here would cause visual ratcheting.
176
+ // Trusted only when finite; a NaN/Infinity domain falls through to auto.
177
+ yDomainFinal = yDomain;
178
+ applyNice = false;
179
+ } else {
180
+ // Degenerate: no finite values (empty data / all non-numeric).
181
+ if (!Number.isFinite(yMin) || !Number.isFinite(yMax)) {
182
+ yMin = 0;
183
+ yMax = 1;
184
+ }
185
+ // Degenerate: zero range (single point / all-equal) — expand so the mark
186
+ // is visible and ticks are sane.
187
+ if (yMin === yMax) {
188
+ const pad = Math.abs(yMin) * 0.5 || 1;
189
+ yMin -= pad;
190
+ yMax += pad;
191
+ }
192
+ const mode = yBaseline ?? 'auto';
193
+ if (mode === 'zero') {
194
+ const abs = Math.max(Math.abs(yMin), Math.abs(yMax));
195
+ yMin = -abs;
196
+ yMax = abs;
197
+ } else if (mode === 'data') {
198
+ // Tight fit — no zero forcing, no headroom.
199
+ } else {
200
+ // 'auto': bar/area marks pin an honest zero baseline; continuous-only
201
+ // charts get headroom so endpoints/overshoot don't glue to the edges.
202
+ if (includeZero) {
203
+ if (yMin > 0) {
204
+ yMin = 0;
205
+ }
206
+ if (yMax < 0) {
207
+ yMax = 0;
208
+ }
209
+ } else {
210
+ const pad = (yMax - yMin || 1) * CONTINUOUS_HEADROOM;
211
+ yMin -= pad;
212
+ yMax += pad;
213
+ }
214
+ }
215
+ yDomainFinal = [yMin, yMax];
216
+ }
217
+ const yScaleBase = scaleLinear().domain(yDomainFinal).range([height, 0]);
218
+ const yScale = applyNice ? yScaleBase.nice() : yScaleBase;
219
+
220
+ // ─── 2b. Categorical y band scale ────────────────────────────────────
221
+ // If any series declares a `yBandKey` (e.g. a heatmap's rows), the y-axis is
222
+ // categorical: build one band scale over that key's unique values so the
223
+ // marks and the left axis share the same row layout. The linear yScale above
224
+ // is left in place but goes unused for these charts.
225
+ // No band padding: heatmap rows should tile the full plot height edge-to-edge
226
+ // (the mark's own `cellGap` draws the thin grid lines between cells). Padding
227
+ // here would inset the grid and leave a sliver at the top/bottom.
228
+ let yBandScale;
229
+ if (yBandKey != null) {
230
+ const cats = [...new Set(data.map(d => String(d[yBandKey])))];
231
+ yBandScale = scaleBand().domain(cats).range([0, height]).padding(0);
232
+ }
233
+
234
+ // ─── 3. Stacking ─────────────────────────────────────────────────────
235
+ // Reuses `stackGroups` collected during the domain pass (§2).
236
+ const stackedData = new Map();
237
+ for (const [, keys] of stackGroups) {
238
+ const stackGen = d3Stack().keys(keys).order(stackOrderNone).offset(stackOffsetNone);
239
+ const stacked = stackGen(data);
240
+ for (const layer of stacked) {
241
+ stackedData.set(layer.key, layer.map(d => ({
242
+ y0: d[0],
243
+ y1: d[1]
244
+ })));
245
+ }
246
+ }
247
+
248
+ // ─── 4. Bar grouping ─────────────────────────────────────────────────
249
+ // Bars can be grouped (side-by-side) whether or not they are stacked.
250
+ // For grouped stacks, each unique stack group name within the same bar
251
+ // group gets its own sub-band. For ungrouped non-stacked bars with a
252
+ // group key, each series gets its own sub-band.
253
+ const barGroups = new Map();
254
+ for (const s of series) {
255
+ if (s.type === 'bar' && s.layout.group) {
256
+ const group = barGroups.get(s.layout.group) ?? [];
257
+ // For grouped stacks: use the stack name as the group slot identifier
258
+ // so all series in the same stack share one slot.
259
+ const slotKey = s.layout.stack ?? s.key;
260
+ if (!group.includes(slotKey)) {
261
+ group.push(slotKey);
262
+ }
263
+ barGroups.set(s.layout.group, group);
264
+ }
265
+ }
266
+
267
+ // ─── 5. Resolve each series ──────────────────────────────────────────
268
+ const ctx = {
269
+ data,
270
+ xKey,
271
+ xScale,
272
+ yScale,
273
+ yBandScale,
274
+ width,
275
+ height
276
+ };
277
+ const resolved = new Map();
278
+
279
+ // Determine which series is the topmost in each stack group
280
+ const stackTopKeys = new Set();
281
+ for (const [, keys] of stackGroups) {
282
+ // Last key in the stack group is rendered on top
283
+ if (keys.length > 0) {
284
+ stackTopKeys.add(keys[keys.length - 1]);
285
+ }
286
+ }
287
+ for (let seriesIndex = 0; seriesIndex < series.length; seriesIndex++) {
288
+ const s = series[seriesIndex];
289
+ // Assign a collision-free identity from array position. Two series sharing
290
+ // a dataKey (and thus `key`) still get distinct uids, so neither the
291
+ // resolved map nor React keys silently drop one of them.
292
+ s._uid = `${seriesIndex}:${s.key}`;
293
+ const stackOffsets = stackedData.get(s.dataKeys[0]);
294
+
295
+ // Mark whether this series is the topmost in its stack
296
+ if (s.layout.stack) {
297
+ s._isTopOfStack = stackTopKeys.has(s.dataKeys[0]);
298
+ } else {
299
+ s._isTopOfStack = true;
300
+ }
301
+ let groupInfo;
302
+ if (s.layout.group) {
303
+ const groupKeys = barGroups.get(s.layout.group);
304
+ if (groupKeys) {
305
+ // For grouped stacks, the slot key is the stack name
306
+ const slotKey = s.layout.stack ?? s.key;
307
+ groupInfo = {
308
+ index: groupKeys.indexOf(slotKey),
309
+ count: groupKeys.length
310
+ };
311
+ }
312
+ }
313
+ const points = s.resolve(ctx, stackOffsets, groupInfo);
314
+ resolved.set(s._uid, points);
315
+ }
316
+ return {
317
+ xScale,
318
+ yScale,
319
+ yBandScale,
320
+ resolved
321
+ };
322
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @file legend.ts
3
+ * @output Legend utility — derives legend items from series definitions
4
+ * @position Used by Chart to bridge series → legend items
5
+ */
6
+ import { type SeriesDef } from './types';
7
+ export interface LegendItem {
8
+ label: string;
9
+ color: string;
10
+ /** Mark type — determines swatch shape (square for bar, line for everything else) */
11
+ type?: string;
12
+ }
13
+ /**
14
+ * Derive legend items from series definitions. Skips utility marks.
15
+ */
16
+ export declare function deriveLegendItems(series: readonly SeriesDef[]): LegendItem[];
17
+ //# sourceMappingURL=legend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"legend.d.ts","sourceRoot":"","sources":["../src/legend.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,OAAO,EAA0C,KAAK,SAAS,EAAC,MAAM,SAAS,CAAC;AAEhF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAeD;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,GAAG,UAAU,EAAE,CAU5E"}
package/dist/legend.js ADDED
@@ -0,0 +1,40 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file legend.ts
5
+ * @output Legend utility — derives legend items from series definitions
6
+ * @position Used by Chart to bridge series → legend items
7
+ */
8
+
9
+ import { DEFAULT_SERIES_COLOR, isUtilityMarkType } from "./types.js";
10
+ /**
11
+ * A series appears in the legend if it's a primary (non-utility) mark. Its
12
+ * representative color is the static color, else the root-assigned palette color
13
+ * (so auto-colored AND accessor-colored series still show up — previously an
14
+ * accessor color silently dropped the series from the legend).
15
+ */
16
+ function legendColor(series) {
17
+ if (isUtilityMarkType(series.type)) {
18
+ return undefined;
19
+ }
20
+ return series.color ?? series._resolvedColor ?? DEFAULT_SERIES_COLOR;
21
+ }
22
+
23
+ /**
24
+ * Derive legend items from series definitions. Skips utility marks.
25
+ */
26
+ export function deriveLegendItems(series) {
27
+ const items = [];
28
+ for (const s of series) {
29
+ const color = legendColor(s);
30
+ if (color == null) {
31
+ continue;
32
+ }
33
+ items.push({
34
+ label: s.label ?? s.key,
35
+ color,
36
+ type: s.type
37
+ });
38
+ }
39
+ return items;
40
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @file markColor.ts
3
+ * @output Color resolution shared by marks
4
+ * @position Used by mark renderers to turn an optional color into a concrete one
5
+ *
6
+ * Precedence: an explicit color (string or accessor) always wins; otherwise the
7
+ * chart root's palette color for the series (`_resolvedColor`); otherwise a real
8
+ * data-viz token fallback. This is what makes an un-colored `bar('revenue')`
9
+ * render a real color instead of the historical broken `--color-chart-1`.
10
+ *
11
+ * Empty strings are treated as "unset" at every level (an empty `fill`/`stroke`
12
+ * paints nothing), and a per-point accessor that returns a non-string or empty
13
+ * value falls through to the palette rather than emitting an invalid paint.
14
+ */
15
+ import { type SeriesDef, type ColorAccessor } from './types';
16
+ /** The single resolved color for a series (line/area/band and similar). */
17
+ export declare function seriesFill(def: SeriesDef, color: ColorAccessor | undefined): string;
18
+ /** Per-point fill: an accessor wins, then an explicit string, then the palette. */
19
+ export declare function pointFill(def: SeriesDef, color: ColorAccessor | undefined, datum: Record<string, unknown>, index: number): string;
20
+ //# sourceMappingURL=markColor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markColor.d.ts","sourceRoot":"","sources":["../src/markColor.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAEL,KAAK,SAAS,EACd,KAAK,aAAa,EACnB,MAAM,SAAS,CAAC;AAgBjB,2EAA2E;AAC3E,wBAAgB,UAAU,CACxB,GAAG,EAAE,SAAS,EACd,KAAK,EAAE,aAAa,GAAG,SAAS,GAC/B,MAAM,CAKR;AAED,mFAAmF;AACnF,wBAAgB,SAAS,CACvB,GAAG,EAAE,SAAS,EACd,KAAK,EAAE,aAAa,GAAG,SAAS,EAChC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,MAAM,GACZ,MAAM,CAeR"}
@@ -0,0 +1,56 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file markColor.ts
5
+ * @output Color resolution shared by marks
6
+ * @position Used by mark renderers to turn an optional color into a concrete one
7
+ *
8
+ * Precedence: an explicit color (string or accessor) always wins; otherwise the
9
+ * chart root's palette color for the series (`_resolvedColor`); otherwise a real
10
+ * data-viz token fallback. This is what makes an un-colored `bar('revenue')`
11
+ * render a real color instead of the historical broken `--color-chart-1`.
12
+ *
13
+ * Empty strings are treated as "unset" at every level (an empty `fill`/`stroke`
14
+ * paints nothing), and a per-point accessor that returns a non-string or empty
15
+ * value falls through to the palette rather than emitting an invalid paint.
16
+ */
17
+
18
+ import { DEFAULT_SERIES_COLOR } from "./types.js";
19
+
20
+ /** A usable CSS paint value — a non-blank string. */
21
+ function isPaint(value) {
22
+ // A whitespace-only string is an invalid paint (it renders as the initial
23
+ // color, not the intended fallback), so treat it as "unset" like an empty one.
24
+ return typeof value === 'string' && value.trim().length > 0;
25
+ }
26
+
27
+ /** The palette color assigned by the chart root, or the last-resort token. */
28
+ function fallbackFill(def) {
29
+ return isPaint(def._resolvedColor) ? def._resolvedColor : DEFAULT_SERIES_COLOR;
30
+ }
31
+
32
+ /** The single resolved color for a series (line/area/band and similar). */
33
+ export function seriesFill(def, color) {
34
+ if (isPaint(color)) {
35
+ return color;
36
+ }
37
+ return fallbackFill(def);
38
+ }
39
+
40
+ /** Per-point fill: an accessor wins, then an explicit string, then the palette. */
41
+ export function pointFill(def, color, datum, index) {
42
+ if (typeof color === 'function') {
43
+ try {
44
+ const resolved = color(datum, index);
45
+ if (isPaint(resolved)) {
46
+ return resolved;
47
+ }
48
+ } catch {
49
+ // A consumer accessor can throw on unexpected data; fall through to the
50
+ // palette/token fallback rather than crashing the whole chart render.
51
+ }
52
+ } else if (isPaint(color)) {
53
+ return color;
54
+ }
55
+ return fallbackFill(def);
56
+ }
@@ -0,0 +1,19 @@
1
+ import type { SeriesDef } from '../types';
2
+ declare const CURVES: {
3
+ readonly linear: import("d3-shape").CurveFactory;
4
+ readonly monotone: import("d3-shape").CurveFactory;
5
+ readonly natural: import("d3-shape").CurveFactory;
6
+ readonly step: import("d3-shape").CurveFactory;
7
+ };
8
+ export interface AreaOptions {
9
+ color?: string;
10
+ opacity?: number;
11
+ curve?: keyof typeof CURVES;
12
+ stack?: string;
13
+ gradient?: boolean;
14
+ stroke?: boolean;
15
+ label?: string;
16
+ }
17
+ export declare function area(dataKey: string, options?: AreaOptions): SeriesDef;
18
+ export {};
19
+ //# sourceMappingURL=area.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"area.d.ts","sourceRoot":"","sources":["../../src/marks/area.tsx"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAC,SAAS,EAAgB,MAAM,UAAU,CAAC;AAIvD,QAAA,MAAM,MAAM;;;;;CAKF,CAAC;AAEX,MAAM,WAAW,WAAW;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,OAAO,MAAM,CAAC;IAC5B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AASD,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,WAAgB,GAAG,SAAS,CA6F1E"}
@@ -0,0 +1,121 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file marks/area.tsx
5
+ * @output Area series — fill under line with stacking + gradient support
6
+ *
7
+ * Missing / non-finite values (including gaps in a stacked baseline) become
8
+ * holes via d3's `.defined()` rather than collapsing to zero. The gradient id is
9
+ * salted with the series' collision-free `_uid` so overlapping areas over the
10
+ * same dataKey never share (and steal) each other's fill.
11
+ */
12
+
13
+ import { area as d3Area, curveLinear, curveMonotoneX, curveNatural, curveStep } from 'd3-shape';
14
+ import { line as d3Line } from 'd3-shape';
15
+ import { seriesFill } from "../markColor.js";
16
+ import { xPixel } from "../utils.js";
17
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
18
+ const CURVES = {
19
+ linear: curveLinear,
20
+ monotone: curveMonotoneX,
21
+ natural: curveNatural,
22
+ step: curveStep
23
+ };
24
+ /** Both edges of an area point must be finite for the span to be drawable. */
25
+ function isFinitePoint(p) {
26
+ return Number.isFinite(p.px) && Number.isFinite(p.py) && Number.isFinite(p.py0);
27
+ }
28
+ export function area(dataKey, options = {}) {
29
+ const color = options.color;
30
+ const opacity = typeof options.opacity === 'number' && Number.isFinite(options.opacity) ? Math.min(1, Math.max(0, options.opacity)) : 0.3;
31
+ const curve = options.curve ?? 'monotone';
32
+ const gradient = options.gradient ?? false;
33
+ const stroke = options.stroke ?? true;
34
+ const seriesDef = {
35
+ type: 'area',
36
+ key: dataKey,
37
+ dataKeys: [dataKey],
38
+ color,
39
+ label: options.label ?? dataKey,
40
+ layout: {
41
+ stack: options.stack,
42
+ includeZero: true
43
+ },
44
+ resolve(ctx, stackOffsets) {
45
+ const {
46
+ data,
47
+ xKey,
48
+ xScale,
49
+ yScale
50
+ } = ctx;
51
+ const points = [];
52
+ for (let i = 0; i < data.length; i++) {
53
+ const d = data[i];
54
+ let py, py0;
55
+ if (stackOffsets) {
56
+ const offset = stackOffsets[i];
57
+ py = offset ? yScale(offset.y1) : NaN;
58
+ py0 = offset ? yScale(offset.y0) : NaN;
59
+ } else {
60
+ const raw = d[dataKey];
61
+ const v = typeof raw === 'number' && Number.isFinite(raw) ? raw : NaN;
62
+ py = yScale(v);
63
+ py0 = yScale(0);
64
+ }
65
+ points.push({
66
+ px: xPixel(d, xKey, xScale),
67
+ py,
68
+ py0,
69
+ dataIndex: i
70
+ });
71
+ }
72
+ return points;
73
+ },
74
+ render(resolved) {
75
+ if (resolved.length === 0) {
76
+ return null;
77
+ }
78
+ const curveFactory = CURVES[curve] ?? curveMonotoneX;
79
+ const areaGen = d3Area().defined(isFinitePoint).x(d => d.px).y0(d => d.py0).y1(d => d.py).curve(curveFactory);
80
+ const pathD = areaGen(resolved) ?? '';
81
+ const lineGen = d3Line().defined(isFinitePoint).x(d => d.px).y(d => d.py).curve(curveFactory);
82
+ const strokeD = lineGen(resolved) ?? '';
83
+
84
+ // Unique per series instance so two areas over the same dataKey don't
85
+ // share a gradient id (which would make one steal the other's fill).
86
+ const gradientId = `area-grad-${(seriesDef._uid ?? dataKey).replace(/[^a-zA-Z0-9_-]/g, '-')}`;
87
+ const fillColor = seriesFill(seriesDef, color);
88
+ return /*#__PURE__*/_jsxs("g", {
89
+ children: [gradient && /*#__PURE__*/_jsx("defs", {
90
+ children: /*#__PURE__*/_jsxs("linearGradient", {
91
+ id: gradientId,
92
+ x1: "0",
93
+ y1: "0",
94
+ x2: "0",
95
+ y2: "1",
96
+ children: [/*#__PURE__*/_jsx("stop", {
97
+ offset: "0%",
98
+ stopColor: fillColor,
99
+ stopOpacity: opacity
100
+ }), /*#__PURE__*/_jsx("stop", {
101
+ offset: "100%",
102
+ stopColor: fillColor,
103
+ stopOpacity: 0
104
+ })]
105
+ })
106
+ }), /*#__PURE__*/_jsx("path", {
107
+ d: pathD,
108
+ fill: gradient ? `url(#${gradientId})` : fillColor,
109
+ fillOpacity: gradient ? 1 : opacity,
110
+ stroke: "none"
111
+ }), stroke && /*#__PURE__*/_jsx("path", {
112
+ d: strokeD,
113
+ fill: "none",
114
+ stroke: fillColor,
115
+ strokeWidth: 2
116
+ })]
117
+ });
118
+ }
119
+ };
120
+ return seriesDef;
121
+ }
@@ -0,0 +1,9 @@
1
+ import { type SeriesDef } from '../types';
2
+ export interface BandOptions {
3
+ upper: string;
4
+ lower: string;
5
+ color?: string;
6
+ opacity?: number;
7
+ }
8
+ export declare function band(options: BandOptions): SeriesDef;
9
+ //# sourceMappingURL=band.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"band.d.ts","sourceRoot":"","sources":["../../src/marks/band.tsx"],"names":[],"mappings":"AAUA,OAAO,EAEL,KAAK,SAAS,EAEf,MAAM,UAAU,CAAC;AAGlB,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,CA6DpD"}