@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.
- package/CHANGELOG.md +73 -0
- package/LICENSE +24 -0
- package/README.md +118 -0
- package/dist/Chart.d.ts +62 -0
- package/dist/Chart.d.ts.map +1 -0
- package/dist/Chart.js +401 -0
- package/dist/ChartAxis.d.ts +37 -0
- package/dist/ChartAxis.d.ts.map +1 -0
- package/dist/ChartAxis.js +233 -0
- package/dist/ChartContext.d.ts +4 -0
- package/dist/ChartContext.d.ts.map +1 -0
- package/dist/ChartContext.js +15 -0
- package/dist/ChartGrid.d.ts +26 -0
- package/dist/ChartGrid.d.ts.map +1 -0
- package/dist/ChartGrid.js +118 -0
- package/dist/ChartLegend.d.ts +14 -0
- package/dist/ChartLegend.d.ts.map +1 -0
- package/dist/ChartLegend.js +62 -0
- package/dist/ChartSwatch.d.ts +36 -0
- package/dist/ChartSwatch.d.ts.map +1 -0
- package/dist/ChartSwatch.js +85 -0
- package/dist/ChartTooltip.d.ts +46 -0
- package/dist/ChartTooltip.d.ts.map +1 -0
- package/dist/ChartTooltip.js +330 -0
- package/dist/charts.css +33 -0
- package/dist/dataTable.d.ts +56 -0
- package/dist/dataTable.d.ts.map +1 -0
- package/dist/dataTable.js +88 -0
- package/dist/formatters.d.ts +37 -0
- package/dist/formatters.d.ts.map +1 -0
- package/dist/formatters.js +156 -0
- package/dist/getChartColors.d.ts +67 -0
- package/dist/getChartColors.d.ts.map +1 -0
- package/dist/getChartColors.js +191 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/layout.d.ts +40 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +322 -0
- package/dist/legend.d.ts +17 -0
- package/dist/legend.d.ts.map +1 -0
- package/dist/legend.js +40 -0
- package/dist/markColor.d.ts +20 -0
- package/dist/markColor.d.ts.map +1 -0
- package/dist/markColor.js +56 -0
- package/dist/marks/area.d.ts +19 -0
- package/dist/marks/area.d.ts.map +1 -0
- package/dist/marks/area.js +121 -0
- package/dist/marks/band.d.ts +9 -0
- package/dist/marks/band.d.ts.map +1 -0
- package/dist/marks/band.js +64 -0
- package/dist/marks/bar.d.ts +21 -0
- package/dist/marks/bar.d.ts.map +1 -0
- package/dist/marks/bar.js +161 -0
- package/dist/marks/candlestick.d.ts +16 -0
- package/dist/marks/candlestick.d.ts.map +1 -0
- package/dist/marks/candlestick.js +110 -0
- package/dist/marks/dot.d.ts +19 -0
- package/dist/marks/dot.d.ts.map +1 -0
- package/dist/marks/dot.js +98 -0
- package/dist/marks/dotGL.d.ts +16 -0
- package/dist/marks/dotGL.d.ts.map +1 -0
- package/dist/marks/dotGL.js +231 -0
- package/dist/marks/dotGLInteractive.d.ts +35 -0
- package/dist/marks/dotGLInteractive.d.ts.map +1 -0
- package/dist/marks/dotGLInteractive.js +584 -0
- package/dist/marks/errorBar.d.ts +29 -0
- package/dist/marks/errorBar.d.ts.map +1 -0
- package/dist/marks/errorBar.js +95 -0
- package/dist/marks/heatmapGL.d.ts +27 -0
- package/dist/marks/heatmapGL.d.ts.map +1 -0
- package/dist/marks/heatmapGL.js +335 -0
- package/dist/marks/index.d.ts +13 -0
- package/dist/marks/index.d.ts.map +1 -0
- package/dist/marks/index.js +14 -0
- package/dist/marks/line.d.ts +18 -0
- package/dist/marks/line.d.ts.map +1 -0
- package/dist/marks/line.js +83 -0
- package/dist/marks/referenceLine.d.ts +37 -0
- package/dist/marks/referenceLine.d.ts.map +1 -0
- package/dist/marks/referenceLine.js +204 -0
- package/dist/marks/streamGL.d.ts +41 -0
- package/dist/marks/streamGL.d.ts.map +1 -0
- package/dist/marks/streamGL.js +317 -0
- package/dist/tooltip.d.ts +38 -0
- package/dist/tooltip.d.ts.map +1 -0
- package/dist/tooltip.js +57 -0
- package/dist/types.d.ts +193 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +70 -0
- package/dist/useChartColors.d.ts +16 -0
- package/dist/useChartColors.d.ts.map +1 -0
- package/dist/useChartColors.js +37 -0
- package/dist/utils.d.ts +15 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +31 -0
- package/dist/webgl.d.ts +77 -0
- package/dist/webgl.d.ts.map +1 -0
- package/dist/webgl.js +235 -0
- package/package.json +72 -4
- package/src/Chart.doc.mjs +174 -0
- package/src/Chart.test.tsx +273 -0
- package/src/Chart.tsx +535 -0
- package/src/ChartAxis.tsx +289 -0
- package/src/ChartContext.ts +18 -0
- package/src/ChartGrid.tsx +136 -0
- package/src/ChartLegend.tsx +77 -0
- package/src/ChartSwatch.doc.mjs +17 -0
- package/src/ChartSwatch.tsx +89 -0
- package/src/ChartTooltip.tsx +452 -0
- package/src/charts.css.d.ts +3 -0
- package/src/dataTable.test.ts +86 -0
- package/src/dataTable.ts +124 -0
- package/src/formatters.ts +167 -0
- package/src/getChartColors.test.ts +114 -0
- package/src/getChartColors.ts +282 -0
- package/src/index.ts +88 -0
- package/src/layout.ts +372 -0
- package/src/legend.ts +44 -0
- package/src/markColor.ts +70 -0
- package/src/marks/area.tsx +142 -0
- package/src/marks/band.tsx +86 -0
- package/src/marks/bar.tsx +210 -0
- package/src/marks/candlestick.tsx +128 -0
- package/src/marks/dot.tsx +119 -0
- package/src/marks/dotGL.tsx +274 -0
- package/src/marks/dotGLInteractive.tsx +660 -0
- package/src/marks/errorBar.tsx +119 -0
- package/src/marks/heatmapGL.tsx +392 -0
- package/src/marks/index.ts +17 -0
- package/src/marks/line.tsx +118 -0
- package/src/marks/referenceLine.tsx +252 -0
- package/src/marks/streamGL.tsx +381 -0
- package/src/tooltip.ts +73 -0
- package/src/types.ts +212 -0
- package/src/useChartColors.ts +41 -0
- package/src/utils.ts +38 -0
- package/src/webgl.ts +262 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file marks/band.tsx
|
|
5
|
+
* @output Band (confidence interval) series — area between upper/lower bounds.
|
|
6
|
+
* Non-numeric/NaN bounds break the area into gaps instead of collapsing to a
|
|
7
|
+
* false zero baseline.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { area as d3Area, curveMonotoneX } from 'd3-shape';
|
|
11
|
+
import { DEFAULT_SERIES_COLOR } from "../types.js";
|
|
12
|
+
import { xPixel } from "../utils.js";
|
|
13
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
14
|
+
export function band(options) {
|
|
15
|
+
const color = options.color ?? DEFAULT_SERIES_COLOR;
|
|
16
|
+
const opacity = options.opacity ?? 0.15;
|
|
17
|
+
return {
|
|
18
|
+
type: 'band',
|
|
19
|
+
key: `band-${options.upper}-${options.lower}`,
|
|
20
|
+
dataKeys: [options.upper, options.lower],
|
|
21
|
+
layout: {},
|
|
22
|
+
resolve(ctx) {
|
|
23
|
+
const {
|
|
24
|
+
data,
|
|
25
|
+
xKey,
|
|
26
|
+
xScale,
|
|
27
|
+
yScale
|
|
28
|
+
} = ctx;
|
|
29
|
+
const points = [];
|
|
30
|
+
for (let i = 0; i < data.length; i++) {
|
|
31
|
+
const d = data[i];
|
|
32
|
+
// NaN for missing/non-numeric bounds so render() can gap the area
|
|
33
|
+
// rather than draw a spike down to the axis.
|
|
34
|
+
const upperVal = d[options.upper];
|
|
35
|
+
const lowerVal = d[options.lower];
|
|
36
|
+
const upper = typeof upperVal === 'number' && Number.isFinite(upperVal) ? upperVal : NaN;
|
|
37
|
+
const lower = typeof lowerVal === 'number' && Number.isFinite(lowerVal) ? lowerVal : NaN;
|
|
38
|
+
points.push({
|
|
39
|
+
px: xPixel(d, xKey, xScale),
|
|
40
|
+
py: yScale(upper),
|
|
41
|
+
py0: yScale(lower),
|
|
42
|
+
dataIndex: i
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return points;
|
|
46
|
+
},
|
|
47
|
+
render(resolved) {
|
|
48
|
+
if (resolved.length === 0) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const areaGen = d3Area().defined(d => Number.isFinite(d.px) && Number.isFinite(d.py) && Number.isFinite(d.py0)).x(d => d.px).y0(d => d.py0).y1(d => d.py).curve(curveMonotoneX);
|
|
52
|
+
const pathD = areaGen(resolved) ?? '';
|
|
53
|
+
if (!pathD) {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
return /*#__PURE__*/_jsx("path", {
|
|
57
|
+
d: pathD,
|
|
58
|
+
fill: color,
|
|
59
|
+
fillOpacity: opacity,
|
|
60
|
+
stroke: "none"
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file marks/bar.tsx
|
|
3
|
+
* @output Bar series — self-contained resolve + render
|
|
4
|
+
* @position Standalone mark; chart root calls resolve() then render()
|
|
5
|
+
*
|
|
6
|
+
* Robust to missing/NaN values (bars with no finite height are skipped rather
|
|
7
|
+
* than drawn at the baseline) and to degenerate bandwidths (grouped bar widths
|
|
8
|
+
* are clamped to a non-negative value). Negative values grow downward from zero.
|
|
9
|
+
*/
|
|
10
|
+
import type { SeriesDef, ColorAccessor } from '../types';
|
|
11
|
+
export type { ColorAccessor };
|
|
12
|
+
export interface BarOptions {
|
|
13
|
+
color?: ColorAccessor;
|
|
14
|
+
opacity?: number;
|
|
15
|
+
radius?: number;
|
|
16
|
+
stack?: string;
|
|
17
|
+
group?: string;
|
|
18
|
+
label?: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function bar(dataKey: string, options?: BarOptions): SeriesDef;
|
|
21
|
+
//# sourceMappingURL=bar.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bar.d.ts","sourceRoot":"","sources":["../../src/marks/bar.tsx"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAiB,aAAa,EAAC,MAAM,UAAU,CAAC;AAItE,YAAY,EAAC,aAAa,EAAC,CAAC;AAE5B,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAgFD,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,SAAS,CAwGxE"}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file marks/bar.tsx
|
|
5
|
+
* @output Bar series — self-contained resolve + render
|
|
6
|
+
* @position Standalone mark; chart root calls resolve() then render()
|
|
7
|
+
*
|
|
8
|
+
* Robust to missing/NaN values (bars with no finite height are skipped rather
|
|
9
|
+
* than drawn at the baseline) and to degenerate bandwidths (grouped bar widths
|
|
10
|
+
* are clamped to a non-negative value). Negative values grow downward from zero.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { pointFill } from "../markColor.js";
|
|
14
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
15
|
+
/** Clamp a user opacity into [0,1], falling back when it isn't a finite number. */
|
|
16
|
+
function clampOpacity(value, fallback) {
|
|
17
|
+
const n = typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
18
|
+
return Math.min(1, Math.max(0, n));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Coerce a user length (radius/width) to a finite, non-negative number. */
|
|
22
|
+
function nonNegative(value, fallback) {
|
|
23
|
+
return typeof value === 'number' && Number.isFinite(value) ? Math.max(0, value) : fallback;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** True when every coordinate is finite — a NaN/Infinity would corrupt the path. */
|
|
27
|
+
function rectIsFinite(x, y, w, h) {
|
|
28
|
+
return Number.isFinite(x) && Number.isFinite(y) && Number.isFinite(w) && Number.isFinite(h);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Build an SVG path for a rectangle with only the top corners rounded.
|
|
33
|
+
* When `r` is 0, produces a simple rect path with no curves. Returns an empty
|
|
34
|
+
* path (draws nothing) if any coordinate is non-finite.
|
|
35
|
+
*/
|
|
36
|
+
function roundedTopRect(x, y, w, h, r) {
|
|
37
|
+
if (!rectIsFinite(x, y, w, h)) {
|
|
38
|
+
return '';
|
|
39
|
+
}
|
|
40
|
+
// Clamp radius to half the smaller dimension
|
|
41
|
+
const cr = Math.min(r, w / 2, h / 2);
|
|
42
|
+
if (!(cr > 0)) {
|
|
43
|
+
return `M${x},${y + h}V${y}H${x + w}V${y + h}Z`;
|
|
44
|
+
}
|
|
45
|
+
return `M${x},${y + h}` + `V${y + cr}Q${x},${y} ${x + cr},${y}` + `H${x + w - cr}Q${x + w},${y} ${x + w},${y + cr}` + `V${y + h}Z`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Build an SVG path for a rectangle with only the bottom corners rounded.
|
|
50
|
+
* Returns an empty path if any coordinate is non-finite.
|
|
51
|
+
*/
|
|
52
|
+
function roundedBottomRect(x, y, w, h, r) {
|
|
53
|
+
if (!rectIsFinite(x, y, w, h)) {
|
|
54
|
+
return '';
|
|
55
|
+
}
|
|
56
|
+
const cr = Math.min(r, w / 2, h / 2);
|
|
57
|
+
if (!(cr > 0)) {
|
|
58
|
+
return `M${x},${y}V${y + h}H${x + w}V${y}Z`;
|
|
59
|
+
}
|
|
60
|
+
return `M${x},${y}` + `V${y + h - cr}Q${x},${y + h} ${x + cr},${y + h}` + `H${x + w - cr}Q${x + w},${y + h} ${x + w},${y + h - cr}` + `V${y}Z`;
|
|
61
|
+
}
|
|
62
|
+
export function bar(dataKey, options = {}) {
|
|
63
|
+
const color = options.color;
|
|
64
|
+
const opacity = clampOpacity(options.opacity, 1);
|
|
65
|
+
const radius = nonNegative(options.radius, 4);
|
|
66
|
+
let barWidth = 0;
|
|
67
|
+
let barOffset = 0;
|
|
68
|
+
const seriesDef = {
|
|
69
|
+
type: 'bar',
|
|
70
|
+
key: dataKey,
|
|
71
|
+
dataKeys: [dataKey],
|
|
72
|
+
color: typeof color === 'string' ? color : undefined,
|
|
73
|
+
label: options.label ?? dataKey,
|
|
74
|
+
layout: {
|
|
75
|
+
stack: options.stack,
|
|
76
|
+
group: options.group,
|
|
77
|
+
includeZero: true
|
|
78
|
+
},
|
|
79
|
+
resolve(ctx, stackOffsets, groupInfo) {
|
|
80
|
+
const {
|
|
81
|
+
data,
|
|
82
|
+
xKey,
|
|
83
|
+
xScale,
|
|
84
|
+
yScale
|
|
85
|
+
} = ctx;
|
|
86
|
+
if (!('bandwidth' in xScale)) {
|
|
87
|
+
return [];
|
|
88
|
+
}
|
|
89
|
+
const bandScale = xScale;
|
|
90
|
+
const bw = bandScale.bandwidth();
|
|
91
|
+
if (groupInfo && groupInfo.count > 0) {
|
|
92
|
+
const gutter = bw * 0.05;
|
|
93
|
+
const totalGutters = gutter * (groupInfo.count - 1);
|
|
94
|
+
// Clamp: with many groups in a narrow band, gutters can exceed the band
|
|
95
|
+
// and drive the raw width negative — a negative width mirrors the path.
|
|
96
|
+
barWidth = Math.max(0, (bw - totalGutters) / groupInfo.count);
|
|
97
|
+
barOffset = groupInfo.index * (barWidth + gutter);
|
|
98
|
+
} else {
|
|
99
|
+
barWidth = bw;
|
|
100
|
+
barOffset = 0;
|
|
101
|
+
}
|
|
102
|
+
const points = [];
|
|
103
|
+
for (let i = 0; i < data.length; i++) {
|
|
104
|
+
const d = data[i];
|
|
105
|
+
// Unknown categories aren't in the band domain → NaN px so render skips
|
|
106
|
+
// them rather than stacking phantom bars at the origin.
|
|
107
|
+
const xBase = bandScale(String(d[xKey]));
|
|
108
|
+
const px = xBase == null ? NaN : xBase + barOffset + barWidth / 2;
|
|
109
|
+
let py;
|
|
110
|
+
let py0;
|
|
111
|
+
if (stackOffsets) {
|
|
112
|
+
const offset = stackOffsets[i];
|
|
113
|
+
py = offset ? yScale(offset.y1) : NaN;
|
|
114
|
+
py0 = offset ? yScale(offset.y0) : NaN;
|
|
115
|
+
} else {
|
|
116
|
+
const raw = d[dataKey];
|
|
117
|
+
const v = typeof raw === 'number' && Number.isFinite(raw) ? raw : NaN;
|
|
118
|
+
py = yScale(v);
|
|
119
|
+
py0 = yScale(0);
|
|
120
|
+
}
|
|
121
|
+
points.push({
|
|
122
|
+
px,
|
|
123
|
+
py,
|
|
124
|
+
py0,
|
|
125
|
+
dataIndex: i
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
return points;
|
|
129
|
+
},
|
|
130
|
+
render(resolved, ctx) {
|
|
131
|
+
const {
|
|
132
|
+
data
|
|
133
|
+
} = ctx;
|
|
134
|
+
const isTop = seriesDef._isTopOfStack !== false;
|
|
135
|
+
const r = isTop ? radius : 0;
|
|
136
|
+
if (!(barWidth > 0)) {
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
return /*#__PURE__*/_jsx("g", {
|
|
140
|
+
opacity: opacity,
|
|
141
|
+
children: resolved.map(p => {
|
|
142
|
+
const d = data[p.dataIndex];
|
|
143
|
+
if (!d || !Number.isFinite(p.px) || !Number.isFinite(p.py) || !Number.isFinite(p.py0)) {
|
|
144
|
+
return null;
|
|
145
|
+
}
|
|
146
|
+
const fill = pointFill(seriesDef, color, d, p.dataIndex);
|
|
147
|
+
const x = p.px - barWidth / 2;
|
|
148
|
+
const y = Math.min(p.py, p.py0);
|
|
149
|
+
const h = Math.max(0, Math.abs(p.py0 - p.py));
|
|
150
|
+
const growsUp = p.py <= p.py0;
|
|
151
|
+
const d_attr = growsUp ? roundedTopRect(x, y, barWidth, h, r) : roundedBottomRect(x, y, barWidth, h, r);
|
|
152
|
+
return /*#__PURE__*/_jsx("path", {
|
|
153
|
+
d: d_attr,
|
|
154
|
+
fill: fill
|
|
155
|
+
}, p.dataIndex);
|
|
156
|
+
})
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
return seriesDef;
|
|
161
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file marks/candlestick.tsx
|
|
3
|
+
* @output Candlestick (OHLC) series. Renders on both band and linear/time x
|
|
4
|
+
* scales; candles missing any OHLC field are skipped rather than drawn to 0.
|
|
5
|
+
*/
|
|
6
|
+
import type { SeriesDef } from '../types';
|
|
7
|
+
export interface CandlestickOptions {
|
|
8
|
+
open: string;
|
|
9
|
+
high: string;
|
|
10
|
+
low: string;
|
|
11
|
+
close: string;
|
|
12
|
+
upColor?: string;
|
|
13
|
+
downColor?: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function candlestick(options: CandlestickOptions): SeriesDef;
|
|
16
|
+
//# sourceMappingURL=candlestick.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"candlestick.d.ts","sourceRoot":"","sources":["../../src/marks/candlestick.tsx"],"names":[],"mappings":"AAEA;;;;GAIG;AAEH,OAAO,KAAK,EAAC,SAAS,EAA+B,MAAM,UAAU,CAAC;AAGtE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAgCD,wBAAgB,WAAW,CAAC,OAAO,EAAE,kBAAkB,GAAG,SAAS,CA6ElE"}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file marks/candlestick.tsx
|
|
5
|
+
* @output Candlestick (OHLC) series. Renders on both band and linear/time x
|
|
6
|
+
* scales; candles missing any OHLC field are skipped rather than drawn to 0.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { xPixel, isBandScale } from "../utils.js";
|
|
10
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
11
|
+
/** Read a datum field as a finite number, or NaN when missing/non-numeric. */
|
|
12
|
+
function readNumber(d, key) {
|
|
13
|
+
const v = d[key];
|
|
14
|
+
return typeof v === 'number' && Number.isFinite(v) ? v : NaN;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Pixel width of one candle body. On a band scale this is the bandwidth; on a
|
|
19
|
+
* linear/time scale it's derived from the smallest gap between adjacent points
|
|
20
|
+
* (falling back to a modest width when there's only one point).
|
|
21
|
+
*/
|
|
22
|
+
function candleSlot(resolved, ctx) {
|
|
23
|
+
const {
|
|
24
|
+
xScale,
|
|
25
|
+
width
|
|
26
|
+
} = ctx;
|
|
27
|
+
if (isBandScale(xScale)) {
|
|
28
|
+
return xScale.bandwidth();
|
|
29
|
+
}
|
|
30
|
+
const xs = resolved.map(p => p.px).filter(x => Number.isFinite(x)).sort((a, b) => a - b);
|
|
31
|
+
let minGap = Infinity;
|
|
32
|
+
for (let i = 1; i < xs.length; i++) {
|
|
33
|
+
const gap = xs[i] - xs[i - 1];
|
|
34
|
+
if (gap > 0 && gap < minGap) {
|
|
35
|
+
minGap = gap;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return Number.isFinite(minGap) ? minGap : Math.min(width, 40);
|
|
39
|
+
}
|
|
40
|
+
export function candlestick(options) {
|
|
41
|
+
// Emitted core semantic tokens (data-viz --color-data-* tokens aren't CSS vars).
|
|
42
|
+
const upColor = options.upColor ?? 'var(--color-success)';
|
|
43
|
+
const downColor = options.downColor ?? 'var(--color-error)';
|
|
44
|
+
return {
|
|
45
|
+
type: 'candlestick',
|
|
46
|
+
key: `ohlc-${options.close}`,
|
|
47
|
+
dataKeys: [options.open, options.high, options.low, options.close],
|
|
48
|
+
layout: {},
|
|
49
|
+
resolve(ctx) {
|
|
50
|
+
const {
|
|
51
|
+
data,
|
|
52
|
+
xKey,
|
|
53
|
+
xScale,
|
|
54
|
+
yScale
|
|
55
|
+
} = ctx;
|
|
56
|
+
const points = [];
|
|
57
|
+
for (let i = 0; i < data.length; i++) {
|
|
58
|
+
const d = data[i];
|
|
59
|
+
const close = readNumber(d, options.close);
|
|
60
|
+
points.push({
|
|
61
|
+
px: xPixel(d, xKey, xScale),
|
|
62
|
+
py: yScale(close),
|
|
63
|
+
py0: yScale(0),
|
|
64
|
+
dataIndex: i
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return points;
|
|
68
|
+
},
|
|
69
|
+
render(resolved, ctx) {
|
|
70
|
+
const {
|
|
71
|
+
data,
|
|
72
|
+
yScale
|
|
73
|
+
} = ctx;
|
|
74
|
+
const bodyWidth = Math.max(1, candleSlot(resolved, ctx) * 0.6);
|
|
75
|
+
return /*#__PURE__*/_jsx("g", {
|
|
76
|
+
children: resolved.map(p => {
|
|
77
|
+
const d = data[p.dataIndex];
|
|
78
|
+
const o = readNumber(d, options.open);
|
|
79
|
+
const c = readNumber(d, options.close);
|
|
80
|
+
const h = readNumber(d, options.high);
|
|
81
|
+
const l = readNumber(d, options.low);
|
|
82
|
+
// Drop candles missing any OHLC field (or an unplaceable x) — a gap
|
|
83
|
+
// reads more honestly than a full-height candle anchored at 0.
|
|
84
|
+
if (!Number.isFinite(p.px) || !Number.isFinite(o) || !Number.isFinite(c) || !Number.isFinite(h) || !Number.isFinite(l)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
const col = c >= o ? upColor : downColor;
|
|
88
|
+
const bodyTop = yScale(Math.max(o, c));
|
|
89
|
+
const bodyHeight = Math.max(1, Math.abs(yScale(o) - yScale(c)));
|
|
90
|
+
return /*#__PURE__*/_jsxs("g", {
|
|
91
|
+
children: [/*#__PURE__*/_jsx("line", {
|
|
92
|
+
x1: p.px,
|
|
93
|
+
x2: p.px,
|
|
94
|
+
y1: yScale(h),
|
|
95
|
+
y2: yScale(l),
|
|
96
|
+
stroke: col,
|
|
97
|
+
strokeWidth: 1
|
|
98
|
+
}), /*#__PURE__*/_jsx("rect", {
|
|
99
|
+
x: p.px - bodyWidth / 2,
|
|
100
|
+
y: bodyTop,
|
|
101
|
+
width: bodyWidth,
|
|
102
|
+
height: bodyHeight,
|
|
103
|
+
fill: col
|
|
104
|
+
})]
|
|
105
|
+
}, p.dataIndex);
|
|
106
|
+
})
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file marks/dot.tsx
|
|
3
|
+
* @output Dot/scatter series — self-contained resolve + render
|
|
4
|
+
*
|
|
5
|
+
* Points with missing / non-finite values are skipped rather than plotted at the
|
|
6
|
+
* baseline. Optional `dodge` spreads coincident points horizontally, ignoring
|
|
7
|
+
* non-finite ones so a single missing value can't collapse a whole dodge group.
|
|
8
|
+
*/
|
|
9
|
+
import type { SeriesDef, ColorAccessor } from '../types';
|
|
10
|
+
export type { ColorAccessor };
|
|
11
|
+
export interface DotOptions {
|
|
12
|
+
color?: ColorAccessor;
|
|
13
|
+
radius?: number;
|
|
14
|
+
opacity?: number;
|
|
15
|
+
dodge?: boolean;
|
|
16
|
+
label?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function dot(dataKey: string, options?: DotOptions): SeriesDef;
|
|
19
|
+
//# sourceMappingURL=dot.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dot.d.ts","sourceRoot":"","sources":["../../src/marks/dot.tsx"],"names":[],"mappings":"AAEA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAC,SAAS,EAAiB,aAAa,EAAC,MAAM,UAAU,CAAC;AAItE,YAAY,EAAC,aAAa,EAAC,CAAC;AAE5B,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,GAAG,SAAS,CA6FxE"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file marks/dot.tsx
|
|
5
|
+
* @output Dot/scatter series — self-contained resolve + render
|
|
6
|
+
*
|
|
7
|
+
* Points with missing / non-finite values are skipped rather than plotted at the
|
|
8
|
+
* baseline. Optional `dodge` spreads coincident points horizontally, ignoring
|
|
9
|
+
* non-finite ones so a single missing value can't collapse a whole dodge group.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { pointFill } from "../markColor.js";
|
|
13
|
+
import { xPixel } from "../utils.js";
|
|
14
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
15
|
+
export function dot(dataKey, options = {}) {
|
|
16
|
+
const color = options.color;
|
|
17
|
+
const radius = typeof options.radius === 'number' && Number.isFinite(options.radius) ? Math.max(0, options.radius) : 4;
|
|
18
|
+
const opacity = typeof options.opacity === 'number' && Number.isFinite(options.opacity) ? Math.min(1, Math.max(0, options.opacity)) : 0.8;
|
|
19
|
+
const dodge = options.dodge ?? false;
|
|
20
|
+
const seriesDef = {
|
|
21
|
+
type: 'dot',
|
|
22
|
+
key: dataKey,
|
|
23
|
+
dataKeys: [dataKey],
|
|
24
|
+
color: typeof color === 'string' ? color : undefined,
|
|
25
|
+
label: options.label ?? dataKey,
|
|
26
|
+
layout: {},
|
|
27
|
+
resolve(ctx) {
|
|
28
|
+
const {
|
|
29
|
+
data,
|
|
30
|
+
xKey,
|
|
31
|
+
xScale,
|
|
32
|
+
yScale
|
|
33
|
+
} = ctx;
|
|
34
|
+
const points = [];
|
|
35
|
+
for (let i = 0; i < data.length; i++) {
|
|
36
|
+
const d = data[i];
|
|
37
|
+
const raw = d[dataKey];
|
|
38
|
+
const v = typeof raw === 'number' && Number.isFinite(raw) ? raw : NaN;
|
|
39
|
+
points.push({
|
|
40
|
+
px: xPixel(d, xKey, xScale),
|
|
41
|
+
py: yScale(v),
|
|
42
|
+
py0: yScale(0),
|
|
43
|
+
dataIndex: i
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
if (dodge) {
|
|
47
|
+
// Spread points that land on the same x (e.g. dots over band categories)
|
|
48
|
+
// horizontally so they don't overlap, centered on the original x.
|
|
49
|
+
const groups = new Map();
|
|
50
|
+
for (const p of points) {
|
|
51
|
+
// Only points render() will actually draw (finite px AND py) join a
|
|
52
|
+
// dodge group; a missing y-value must not reserve a slot and knock the
|
|
53
|
+
// visible points off-center.
|
|
54
|
+
if (!Number.isFinite(p.px) || !Number.isFinite(p.py)) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const key = Math.round(p.px);
|
|
58
|
+
const group = groups.get(key) ?? [];
|
|
59
|
+
group.push(p);
|
|
60
|
+
groups.set(key, group);
|
|
61
|
+
}
|
|
62
|
+
const spacing = radius * 2 + 1;
|
|
63
|
+
for (const group of groups.values()) {
|
|
64
|
+
if (group.length < 2) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const mid = (group.length - 1) / 2;
|
|
68
|
+
group.forEach((p, idx) => {
|
|
69
|
+
p.px += (idx - mid) * spacing;
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return points;
|
|
74
|
+
},
|
|
75
|
+
render(resolved, ctx) {
|
|
76
|
+
const {
|
|
77
|
+
data
|
|
78
|
+
} = ctx;
|
|
79
|
+
return /*#__PURE__*/_jsx("g", {
|
|
80
|
+
children: resolved.map(p => {
|
|
81
|
+
const d = data[p.dataIndex];
|
|
82
|
+
if (!d || !Number.isFinite(p.px) || !Number.isFinite(p.py)) {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
const fill = pointFill(seriesDef, color, d, p.dataIndex);
|
|
86
|
+
return /*#__PURE__*/_jsx("circle", {
|
|
87
|
+
cx: p.px,
|
|
88
|
+
cy: p.py,
|
|
89
|
+
r: radius,
|
|
90
|
+
fill: fill,
|
|
91
|
+
opacity: opacity
|
|
92
|
+
}, p.dataIndex);
|
|
93
|
+
})
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return seriesDef;
|
|
98
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { SeriesDef } from '../types';
|
|
2
|
+
export interface DotGLOptions {
|
|
3
|
+
color: string;
|
|
4
|
+
size?: number;
|
|
5
|
+
opacity?: number;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* WebGL scatter plot. Canvas mounted outside SVG for sharp Retina rendering.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```
|
|
12
|
+
* series={[dotGL('value', {color: '#4f46e5'})]}
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function dotGL(dataKey: string, options: DotGLOptions): SeriesDef;
|
|
16
|
+
//# sourceMappingURL=dotGL.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dotGL.d.ts","sourceRoot":"","sources":["../../src/marks/dotGL.tsx"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAC,SAAS,EAAgB,MAAM,UAAU,CAAC;AAcvD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAgMD;;;;;;;GAOG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,GAAG,SAAS,CA2CvE"}
|