@astryxdesign/lab 0.1.4-canary.37c19d3 → 0.1.4-canary.39ffd21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ChartV2/Chart.d.ts +32 -0
- package/dist/ChartV2/Chart.d.ts.map +1 -0
- package/dist/ChartV2/Chart.js +281 -0
- package/dist/ChartV2/ChartAxis.d.ts +36 -0
- package/dist/ChartV2/ChartAxis.d.ts.map +1 -0
- package/dist/ChartV2/ChartAxis.js +161 -0
- package/dist/ChartV2/ChartGrid.d.ts +20 -0
- package/dist/ChartV2/ChartGrid.d.ts.map +1 -0
- package/dist/ChartV2/ChartGrid.js +76 -0
- package/dist/ChartV2/ChartLegend.d.ts +14 -0
- package/dist/ChartV2/ChartLegend.d.ts.map +1 -0
- package/dist/ChartV2/ChartLegend.js +54 -0
- package/dist/ChartV2/ChartSwatch.d.ts +36 -0
- package/dist/ChartV2/ChartSwatch.d.ts.map +1 -0
- package/dist/ChartV2/ChartSwatch.js +82 -0
- package/dist/ChartV2/ChartTooltip.d.ts +46 -0
- package/dist/ChartV2/ChartTooltip.d.ts.map +1 -0
- package/dist/ChartV2/ChartTooltip.js +267 -0
- package/dist/ChartV2/ChartV2Context.d.ts +4 -0
- package/dist/ChartV2/ChartV2Context.d.ts.map +1 -0
- package/dist/ChartV2/ChartV2Context.js +12 -0
- package/dist/ChartV2/index.d.ts +13 -0
- package/dist/ChartV2/index.d.ts.map +1 -0
- package/dist/ChartV2/index.js +10 -0
- package/dist/ChartV2/layout.d.ts +25 -0
- package/dist/ChartV2/layout.d.ts.map +1 -0
- package/dist/ChartV2/layout.js +195 -0
- package/dist/ChartV2/legend.d.ts +18 -0
- package/dist/ChartV2/legend.d.ts.map +1 -0
- package/dist/ChartV2/legend.js +24 -0
- package/dist/ChartV2/marks/area.d.ts +19 -0
- package/dist/ChartV2/marks/area.d.ts.map +1 -0
- package/dist/ChartV2/marks/area.js +109 -0
- package/dist/ChartV2/marks/band.d.ts +9 -0
- package/dist/ChartV2/marks/band.d.ts.map +1 -0
- package/dist/ChartV2/marks/band.js +59 -0
- package/dist/ChartV2/marks/bar.d.ts +17 -0
- package/dist/ChartV2/marks/bar.d.ts.map +1 -0
- package/dist/ChartV2/marks/bar.js +120 -0
- package/dist/ChartV2/marks/candlestick.d.ts +15 -0
- package/dist/ChartV2/marks/candlestick.d.ts.map +1 -0
- package/dist/ChartV2/marks/candlestick.js +83 -0
- package/dist/ChartV2/marks/dot.d.ts +15 -0
- package/dist/ChartV2/marks/dot.d.ts.map +1 -0
- package/dist/ChartV2/marks/dot.js +65 -0
- package/dist/ChartV2/marks/dotGL.d.ts +16 -0
- package/dist/ChartV2/marks/dotGL.d.ts.map +1 -0
- package/dist/ChartV2/marks/dotGL.js +178 -0
- package/dist/ChartV2/marks/dotGLInteractive.d.ts +30 -0
- package/dist/ChartV2/marks/dotGLInteractive.d.ts.map +1 -0
- package/dist/ChartV2/marks/dotGLInteractive.js +464 -0
- package/dist/ChartV2/marks/errorBar.d.ts +27 -0
- package/dist/ChartV2/marks/errorBar.d.ts.map +1 -0
- package/dist/ChartV2/marks/errorBar.js +87 -0
- package/dist/ChartV2/marks/heatmapGL.d.ts +23 -0
- package/dist/ChartV2/marks/heatmapGL.d.ts.map +1 -0
- package/dist/ChartV2/marks/heatmapGL.js +242 -0
- package/dist/ChartV2/marks/index.d.ts +13 -0
- package/dist/ChartV2/marks/index.d.ts.map +1 -0
- package/dist/ChartV2/marks/index.js +14 -0
- package/dist/ChartV2/marks/line.d.ts +18 -0
- package/dist/ChartV2/marks/line.d.ts.map +1 -0
- package/dist/ChartV2/marks/line.js +76 -0
- package/dist/ChartV2/marks/referenceLine.d.ts +35 -0
- package/dist/ChartV2/marks/referenceLine.d.ts.map +1 -0
- package/dist/ChartV2/marks/referenceLine.js +186 -0
- package/dist/ChartV2/marks/streamGL.d.ts +36 -0
- package/dist/ChartV2/marks/streamGL.d.ts.map +1 -0
- package/dist/ChartV2/marks/streamGL.js +204 -0
- package/dist/ChartV2/tooltip.d.ts +38 -0
- package/dist/ChartV2/tooltip.d.ts.map +1 -0
- package/dist/ChartV2/tooltip.js +54 -0
- package/dist/ChartV2/types.d.ts +115 -0
- package/dist/ChartV2/types.d.ts.map +1 -0
- package/dist/ChartV2/types.js +35 -0
- package/dist/CodeEditor/CodeEditor.d.ts +1 -4
- package/dist/CodeEditor/CodeEditor.d.ts.map +1 -1
- package/dist/CodeEditor/CodeEditor.js +0 -3
- package/dist/Sankey/SankeyChart.d.ts.map +1 -1
- package/dist/Sankey/SankeyChart.js +0 -3
- package/dist/Schedule/ListView.d.ts.map +1 -1
- package/dist/Schedule/ListView.js +4 -7
- package/dist/Schedule/MonthlyView.js +2 -3
- package/dist/Schedule/Schedule.d.ts +1 -3
- package/dist/Schedule/Schedule.d.ts.map +1 -1
- package/dist/Schedule/Schedule.js +4 -9
- package/dist/Schedule/TimeGridView.js +2 -3
- package/dist/Schedule/context.d.ts +0 -2
- package/dist/Schedule/context.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -5
- package/dist/lab.css +15 -6
- package/package.json +2 -2
- package/src/ChartV2/Chart.tsx +348 -0
- package/src/ChartV2/ChartAxis.tsx +209 -0
- package/src/ChartV2/ChartGrid.tsx +99 -0
- package/src/ChartV2/ChartLegend.tsx +69 -0
- package/src/ChartV2/ChartSwatch.doc.mjs +17 -0
- package/src/ChartV2/ChartSwatch.tsx +86 -0
- package/src/ChartV2/ChartTooltip.tsx +382 -0
- package/src/ChartV2/ChartV2.doc.mjs +16 -0
- package/src/ChartV2/ChartV2Context.ts +15 -0
- package/src/ChartV2/index.ts +62 -0
- package/src/ChartV2/layout.ts +223 -0
- package/src/ChartV2/legend.ts +32 -0
- package/src/ChartV2/marks/area.tsx +122 -0
- package/src/ChartV2/marks/band.tsx +70 -0
- package/src/ChartV2/marks/bar.tsx +159 -0
- package/src/ChartV2/marks/candlestick.tsx +106 -0
- package/src/ChartV2/marks/dot.tsx +64 -0
- package/src/ChartV2/marks/dotGL.tsx +202 -0
- package/src/ChartV2/marks/dotGLInteractive.tsx +546 -0
- package/src/ChartV2/marks/errorBar.tsx +89 -0
- package/src/ChartV2/marks/heatmapGL.tsx +299 -0
- package/src/ChartV2/marks/index.ts +14 -0
- package/src/ChartV2/marks/line.tsx +94 -0
- package/src/ChartV2/marks/referenceLine.tsx +224 -0
- package/src/ChartV2/marks/streamGL.tsx +251 -0
- package/src/ChartV2/tooltip.ts +70 -0
- package/src/ChartV2/types.ts +129 -0
- package/src/CodeEditor/CodeEditor.tsx +0 -5
- package/src/Sankey/SankeyChart.tsx +0 -3
- package/src/Schedule/ListView.tsx +2 -5
- package/src/Schedule/MonthlyView.tsx +2 -2
- package/src/Schedule/Schedule.test.tsx +0 -39
- package/src/Schedule/Schedule.tsx +0 -8
- package/src/Schedule/TimeGridView.tsx +2 -2
- package/src/Schedule/context.tsx +0 -2
- package/src/index.ts +46 -5
- package/dist/InfoTip/InfoTip.d.ts +0 -58
- package/dist/InfoTip/InfoTip.d.ts.map +0 -1
- package/dist/InfoTip/InfoTip.js +0 -91
- package/dist/InfoTip/index.d.ts +0 -6
- package/dist/InfoTip/index.d.ts.map +0 -1
- package/dist/InfoTip/index.js +0 -8
- package/src/CodeEditor/CodeEditor.test.tsx +0 -21
- package/src/InfoTip/InfoTip.doc.mjs +0 -97
- package/src/InfoTip/InfoTip.test.tsx +0 -169
- package/src/InfoTip/InfoTip.tsx +0 -155
- package/src/InfoTip/index.ts +0 -10
- package/src/Sankey/SankeyChart.test.tsx +0 -77
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file ChartAxis.tsx (v2)
|
|
5
|
+
* @output Renders an axis (top, right, bottom, left) using the chart's scales
|
|
6
|
+
* @position Child of Chart v2; reads scales from chart context
|
|
7
|
+
*
|
|
8
|
+
* The axis edge line and tick marks are independently toggleable so callers
|
|
9
|
+
* can compose: line-only, line + ticks, or line + ticks + grid (paired with
|
|
10
|
+
* `<ChartGrid>`). To stay consistent with most charts, the bottom axis
|
|
11
|
+
* draws its edge line by default and other positions don't.
|
|
12
|
+
*
|
|
13
|
+
* Ticks use CSS transitions for smooth sliding during streaming updates.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {useMemo} from 'react';
|
|
17
|
+
import * as stylex from '@stylexjs/stylex';
|
|
18
|
+
import {colorVars} from '@astryxdesign/core/theme/tokens.stylex';
|
|
19
|
+
import {useChart} from '../Chart/ChartContext';
|
|
20
|
+
import {isBandScale} from '../Chart/utils';
|
|
21
|
+
import type {ScaleLinear, ScaleTime} from 'd3-scale';
|
|
22
|
+
|
|
23
|
+
export interface ChartAxisProps {
|
|
24
|
+
/** Which edge to render the axis on */
|
|
25
|
+
position: 'top' | 'right' | 'bottom' | 'left';
|
|
26
|
+
/** Number of ticks (approximate — d3 decides final count) */
|
|
27
|
+
tickCount?: number;
|
|
28
|
+
/** Maximum number of tick labels to show. Labels are evenly skipped when exceeded. */
|
|
29
|
+
maxTicks?: number;
|
|
30
|
+
/** Custom tick formatter */
|
|
31
|
+
tickFormat?: (value: unknown) => string;
|
|
32
|
+
/** Truncate labels to this many characters (appends "\u2026"). */
|
|
33
|
+
truncate?: number;
|
|
34
|
+
/** Enable smooth transitions for streaming (default: true) */
|
|
35
|
+
animated?: boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Show the axis edge line. Defaults to `true` for `position="bottom"`,
|
|
38
|
+
* `false` otherwise. Forced to `true` when `showTicks` is enabled.
|
|
39
|
+
* Pair with `<ChartGrid vertical />` and pass `showAxisLine` on the
|
|
40
|
+
* corresponding side axis to keep the visual edge grounded.
|
|
41
|
+
*/
|
|
42
|
+
showAxisLine?: boolean;
|
|
43
|
+
/** Show perpendicular tick marks at each tick label. Default: `false`. */
|
|
44
|
+
showTicks?: boolean;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const styles = stylex.create({
|
|
48
|
+
axisLine: {
|
|
49
|
+
stroke: colorVars['--color-text-primary'],
|
|
50
|
+
strokeWidth: 1,
|
|
51
|
+
},
|
|
52
|
+
tickMark: {
|
|
53
|
+
stroke: colorVars['--color-text-primary'],
|
|
54
|
+
strokeWidth: 1,
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const TICK_SIZE = 6;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Chart axis. Always draws tick labels; the axis edge line and tick marks
|
|
62
|
+
* are independently toggleable. Pair with `<ChartGrid>` for grid lines.
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```
|
|
66
|
+
* <ChartAxis position="bottom" /> // edge line + labels
|
|
67
|
+
* <ChartAxis position="left" /> // labels only (no edge line)
|
|
68
|
+
* <ChartAxis position="left" showAxisLine showTicks />
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
export function ChartAxis({
|
|
72
|
+
position,
|
|
73
|
+
tickCount = 5,
|
|
74
|
+
maxTicks,
|
|
75
|
+
tickFormat,
|
|
76
|
+
truncate,
|
|
77
|
+
animated = true,
|
|
78
|
+
showAxisLine = position === 'bottom',
|
|
79
|
+
showTicks = false,
|
|
80
|
+
}: ChartAxisProps) {
|
|
81
|
+
// Tick marks need an axis line to anchor against; force it on when ticks
|
|
82
|
+
// are enabled so the two visuals stay coherent.
|
|
83
|
+
const renderAxisLine = showAxisLine || showTicks;
|
|
84
|
+
const {width, height, xScale, yScale} = useChart();
|
|
85
|
+
|
|
86
|
+
const isHorizontal = position === 'top' || position === 'bottom';
|
|
87
|
+
const scale = isHorizontal ? xScale : yScale;
|
|
88
|
+
|
|
89
|
+
const ticks = useMemo(() => {
|
|
90
|
+
let allTicks: {value: unknown; offset: number}[];
|
|
91
|
+
if (isBandScale(scale)) {
|
|
92
|
+
allTicks = scale.domain().map(d => ({
|
|
93
|
+
value: d,
|
|
94
|
+
offset: (scale(d) ?? 0) + scale.bandwidth() / 2,
|
|
95
|
+
}));
|
|
96
|
+
} else {
|
|
97
|
+
const linearScale = scale as
|
|
98
|
+
| ScaleLinear<number, number>
|
|
99
|
+
| ScaleTime<number, number>;
|
|
100
|
+
allTicks = linearScale.ticks(tickCount).map(d => ({
|
|
101
|
+
value: d,
|
|
102
|
+
offset: linearScale(d as number & Date),
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Auto-skip: show every Nth label if maxTicks is exceeded
|
|
107
|
+
if (maxTicks && allTicks.length > maxTicks) {
|
|
108
|
+
const step = Math.ceil(allTicks.length / maxTicks);
|
|
109
|
+
allTicks = allTicks.filter((_, i) => i % step === 0);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return allTicks;
|
|
113
|
+
}, [scale, tickCount, maxTicks]);
|
|
114
|
+
|
|
115
|
+
const transform =
|
|
116
|
+
position === 'bottom'
|
|
117
|
+
? `translate(0,${height})`
|
|
118
|
+
: position === 'right'
|
|
119
|
+
? `translate(${width},0)`
|
|
120
|
+
: undefined;
|
|
121
|
+
|
|
122
|
+
// For the bottom axis, draw the axis line at y=0 when the domain spans
|
|
123
|
+
// negative values (so the axis line still represents zero, not the chart edge).
|
|
124
|
+
const axisLineY = (() => {
|
|
125
|
+
if (position !== 'bottom') {
|
|
126
|
+
return 0;
|
|
127
|
+
}
|
|
128
|
+
const domain = yScale.domain();
|
|
129
|
+
if (domain[0] < 0 && domain[1] > 0) {
|
|
130
|
+
return yScale(0) - height;
|
|
131
|
+
}
|
|
132
|
+
return 0;
|
|
133
|
+
})();
|
|
134
|
+
|
|
135
|
+
const baseFormat = tickFormat ?? String;
|
|
136
|
+
const format = truncate
|
|
137
|
+
? (value: unknown) => {
|
|
138
|
+
const str = baseFormat(value);
|
|
139
|
+
return str.length > truncate ? str.slice(0, truncate) + '\u2026' : str;
|
|
140
|
+
}
|
|
141
|
+
: baseFormat;
|
|
142
|
+
|
|
143
|
+
const tickTransition = animated
|
|
144
|
+
? 'transform 150ms linear, opacity 150ms ease'
|
|
145
|
+
: undefined;
|
|
146
|
+
|
|
147
|
+
return (
|
|
148
|
+
<g transform={transform}>
|
|
149
|
+
{renderAxisLine && (
|
|
150
|
+
<line
|
|
151
|
+
x1={0}
|
|
152
|
+
x2={isHorizontal ? width : 0}
|
|
153
|
+
y1={isHorizontal ? axisLineY : 0}
|
|
154
|
+
y2={isHorizontal ? axisLineY : height}
|
|
155
|
+
{...stylex.props(styles.axisLine)}
|
|
156
|
+
/>
|
|
157
|
+
)}
|
|
158
|
+
{ticks.map(({value, offset}) => {
|
|
159
|
+
const label = format(value);
|
|
160
|
+
const isVisible = isHorizontal
|
|
161
|
+
? offset >= -10 && offset <= width + 10
|
|
162
|
+
: offset >= -10 && offset <= height + 10;
|
|
163
|
+
|
|
164
|
+
if (isHorizontal) {
|
|
165
|
+
const y = position === 'bottom' ? TICK_SIZE : -TICK_SIZE;
|
|
166
|
+
return (
|
|
167
|
+
<g
|
|
168
|
+
key={label}
|
|
169
|
+
style={{
|
|
170
|
+
transform: `translateX(${offset}px)`,
|
|
171
|
+
transition: tickTransition,
|
|
172
|
+
opacity: isVisible ? 1 : 0,
|
|
173
|
+
}}>
|
|
174
|
+
{showTicks && <line y2={y} {...stylex.props(styles.tickMark)} />}
|
|
175
|
+
<text
|
|
176
|
+
y={position === 'bottom' ? TICK_SIZE + 12 : -(TICK_SIZE + 4)}
|
|
177
|
+
textAnchor="middle"
|
|
178
|
+
fill="var(--color-text-secondary)"
|
|
179
|
+
fontSize={12}>
|
|
180
|
+
{label}
|
|
181
|
+
</text>
|
|
182
|
+
</g>
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
// Vertical axis
|
|
186
|
+
const x = position === 'left' ? -TICK_SIZE : TICK_SIZE;
|
|
187
|
+
return (
|
|
188
|
+
<g
|
|
189
|
+
key={label}
|
|
190
|
+
style={{
|
|
191
|
+
transform: `translateY(${offset}px)`,
|
|
192
|
+
transition: tickTransition,
|
|
193
|
+
opacity: isVisible ? 1 : 0,
|
|
194
|
+
}}>
|
|
195
|
+
{showTicks && <line x2={x} {...stylex.props(styles.tickMark)} />}
|
|
196
|
+
<text
|
|
197
|
+
x={position === 'left' ? -(TICK_SIZE + 4) : TICK_SIZE + 4}
|
|
198
|
+
dy="0.32em"
|
|
199
|
+
textAnchor={position === 'left' ? 'end' : 'start'}
|
|
200
|
+
fill="var(--color-text-secondary)"
|
|
201
|
+
fontSize={12}>
|
|
202
|
+
{label}
|
|
203
|
+
</text>
|
|
204
|
+
</g>
|
|
205
|
+
);
|
|
206
|
+
})}
|
|
207
|
+
</g>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file ChartGrid.tsx (v2)
|
|
5
|
+
* @output Grid lines behind chart marks
|
|
6
|
+
* @position Child of Chart v2; reads scales from chart context
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import {useMemo} from 'react';
|
|
10
|
+
import * as stylex from '@stylexjs/stylex';
|
|
11
|
+
import {colorVars} from '@astryxdesign/core/theme/tokens.stylex';
|
|
12
|
+
import {useChart} from '../Chart/ChartContext';
|
|
13
|
+
import {isBandScale} from '../Chart/utils';
|
|
14
|
+
import type {ScaleLinear, ScaleTime} from 'd3-scale';
|
|
15
|
+
|
|
16
|
+
export interface ChartGridProps {
|
|
17
|
+
/** Show horizontal grid lines. Default: `true`. */
|
|
18
|
+
horizontal?: boolean;
|
|
19
|
+
/** Show vertical grid lines. Default: `false`. */
|
|
20
|
+
vertical?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const styles = stylex.create({
|
|
24
|
+
gridLine: {
|
|
25
|
+
stroke: colorVars['--color-border-emphasized'],
|
|
26
|
+
strokeWidth: 1,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Grid lines behind chart marks.
|
|
32
|
+
*
|
|
33
|
+
* Pair with `<ChartAxis>` so users always see an axis line beside the
|
|
34
|
+
* grid — the components are designed to be used together.
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```
|
|
38
|
+
* <ChartGrid /> // horizontal grid lines (default)
|
|
39
|
+
* <ChartGrid horizontal vertical />
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function ChartGrid({
|
|
43
|
+
horizontal = true,
|
|
44
|
+
vertical = false,
|
|
45
|
+
}: ChartGridProps) {
|
|
46
|
+
const {width, height, xScale, yScale} = useChart();
|
|
47
|
+
|
|
48
|
+
// Skip the y=0 line when emphasizing it via the axis. Without this we'd
|
|
49
|
+
// double-draw on top of the axis line.
|
|
50
|
+
const hLines = useMemo(() => {
|
|
51
|
+
if (!horizontal) {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
return yScale
|
|
55
|
+
.ticks(5)
|
|
56
|
+
.filter(tick => tick !== 0)
|
|
57
|
+
.map(tick => yScale(tick));
|
|
58
|
+
}, [horizontal, yScale]);
|
|
59
|
+
|
|
60
|
+
const vLines = useMemo(() => {
|
|
61
|
+
if (!vertical) {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
if (isBandScale(xScale)) {
|
|
65
|
+
return xScale
|
|
66
|
+
.domain()
|
|
67
|
+
.map(d => (xScale(d) ?? 0) + xScale.bandwidth() / 2);
|
|
68
|
+
}
|
|
69
|
+
const linear = xScale as
|
|
70
|
+
| ScaleLinear<number, number>
|
|
71
|
+
| ScaleTime<number, number>;
|
|
72
|
+
return linear.ticks(5).map(d => linear(d as number & Date));
|
|
73
|
+
}, [vertical, xScale]);
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<g>
|
|
77
|
+
{hLines.map(y => (
|
|
78
|
+
<line
|
|
79
|
+
key={`h-${y}`}
|
|
80
|
+
x1={0}
|
|
81
|
+
x2={width}
|
|
82
|
+
y1={y}
|
|
83
|
+
y2={y}
|
|
84
|
+
{...stylex.props(styles.gridLine)}
|
|
85
|
+
/>
|
|
86
|
+
))}
|
|
87
|
+
{vLines.map(x => (
|
|
88
|
+
<line
|
|
89
|
+
key={`v-${x}`}
|
|
90
|
+
x1={x}
|
|
91
|
+
x2={x}
|
|
92
|
+
y1={0}
|
|
93
|
+
y2={height}
|
|
94
|
+
{...stylex.props(styles.gridLine)}
|
|
95
|
+
/>
|
|
96
|
+
))}
|
|
97
|
+
</g>
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file ChartLegend.tsx
|
|
5
|
+
* @output Standalone chart legend component
|
|
6
|
+
* @position Can be used inside Chart via the `legend` prop, or independently
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* // Via Chart
|
|
10
|
+
* <Chart legend={{position: 'top', alignment: 'start'}} ... />
|
|
11
|
+
*
|
|
12
|
+
* // Standalone
|
|
13
|
+
* <ChartLegend items={[{label: 'Revenue', color: '#3b82f6'}]} />
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import {Text} from '@astryxdesign/core';
|
|
17
|
+
import {VStack, HStack} from '@astryxdesign/core';
|
|
18
|
+
import {ChartSwatch, swatchVariantForType} from './ChartSwatch';
|
|
19
|
+
import type {LegendItem} from './legend';
|
|
20
|
+
|
|
21
|
+
export type {LegendItem};
|
|
22
|
+
|
|
23
|
+
export type LegendPosition = 'top' | 'bottom' | 'start' | 'end';
|
|
24
|
+
export type LegendAlignment = 'start' | 'center' | 'end';
|
|
25
|
+
|
|
26
|
+
export interface ChartLegendProps {
|
|
27
|
+
/** Legend items to display */
|
|
28
|
+
items?: LegendItem[];
|
|
29
|
+
/** Position of the legend relative to the chart. Default: 'bottom' */
|
|
30
|
+
position?: LegendPosition;
|
|
31
|
+
/** Alignment of the legend within its position. Default: 'start' */
|
|
32
|
+
alignment?: LegendAlignment;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function ChartLegend({
|
|
36
|
+
items = [],
|
|
37
|
+
position = 'bottom',
|
|
38
|
+
alignment = 'start',
|
|
39
|
+
}: ChartLegendProps) {
|
|
40
|
+
if (items.length === 0) {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const isVertical = position === 'start' || position === 'end';
|
|
45
|
+
|
|
46
|
+
const legendItems = items.map(item => (
|
|
47
|
+
<HStack key={item.label} gap={2} vAlign="center">
|
|
48
|
+
<ChartSwatch
|
|
49
|
+
color={item.color}
|
|
50
|
+
variant={swatchVariantForType(item.type)}
|
|
51
|
+
/>
|
|
52
|
+
<Text type="supporting">{item.label}</Text>
|
|
53
|
+
</HStack>
|
|
54
|
+
));
|
|
55
|
+
|
|
56
|
+
if (isVertical) {
|
|
57
|
+
return (
|
|
58
|
+
<VStack gap={2} hAlign={alignment}>
|
|
59
|
+
{legendItems}
|
|
60
|
+
</VStack>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<HStack gap={4} justify={alignment} vAlign="center" wrap="wrap">
|
|
66
|
+
{legendItems}
|
|
67
|
+
</HStack>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('../../core/src/docs-types').ComponentDoc} */
|
|
4
|
+
|
|
5
|
+
export const docs = {
|
|
6
|
+
name: 'ChartSwatch',
|
|
7
|
+
displayName: 'Chart Swatch',
|
|
8
|
+
group: 'ChartV2',
|
|
9
|
+
category: 'Data Visualization',
|
|
10
|
+
isHiddenFromOverview: true,
|
|
11
|
+
|
|
12
|
+
usage: {
|
|
13
|
+
description: '',
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
props: [],
|
|
17
|
+
};
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @file ChartSwatch.tsx
|
|
5
|
+
* @output Color swatch primitive for legends, tooltips, and standalone use
|
|
6
|
+
* @position Composable primitive — used by ChartLegend (and future
|
|
7
|
+
* ChartTooltip), or independently anywhere a series indicator
|
|
8
|
+
* is needed.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```
|
|
12
|
+
* <ChartSwatch color="#3b82f6" variant="square" />
|
|
13
|
+
* <ChartSwatch color="#f59e0b" variant="line" />
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import {memo} from 'react';
|
|
18
|
+
import * as stylex from '@stylexjs/stylex';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Visual variant — drives shape.
|
|
22
|
+
* - `'square'` — square chip (bar series)
|
|
23
|
+
* - `'line'` — short stroke (line, dot, area, and other non-bar series)
|
|
24
|
+
*/
|
|
25
|
+
export type ChartSwatchVariant = 'square' | 'line';
|
|
26
|
+
|
|
27
|
+
export interface ChartSwatchProps {
|
|
28
|
+
/** Fill color — typically a series color token */
|
|
29
|
+
color: string;
|
|
30
|
+
/** Visual variant. Default: `'square'`. */
|
|
31
|
+
variant?: ChartSwatchVariant;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const styles = stylex.create({
|
|
35
|
+
base: {
|
|
36
|
+
flexShrink: 0,
|
|
37
|
+
},
|
|
38
|
+
square: {
|
|
39
|
+
width: 8,
|
|
40
|
+
height: 8,
|
|
41
|
+
borderRadius: 2,
|
|
42
|
+
marginInline: 1,
|
|
43
|
+
},
|
|
44
|
+
line: {
|
|
45
|
+
width: 10,
|
|
46
|
+
height: 3,
|
|
47
|
+
borderRadius: 1.5,
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Color swatch primitive.
|
|
53
|
+
*
|
|
54
|
+
* Used by ChartLegend (and ChartTooltip) to render the colored
|
|
55
|
+
* indicator next to a series label. Exported as a primitive so consumers
|
|
56
|
+
* can build their own custom legend/tooltip layouts while keeping visuals
|
|
57
|
+
* consistent.
|
|
58
|
+
*/
|
|
59
|
+
function ChartSwatchImpl({color, variant = 'square'}: ChartSwatchProps) {
|
|
60
|
+
return (
|
|
61
|
+
<div
|
|
62
|
+
aria-hidden
|
|
63
|
+
{...stylex.props(styles.base, styles[variant])}
|
|
64
|
+
style={{backgroundColor: color}}
|
|
65
|
+
/>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Memoized — swatches are static for a given (color, variant) tuple
|
|
71
|
+
* and can render many times in a tooltip/legend with several series.
|
|
72
|
+
*/
|
|
73
|
+
export const ChartSwatch = memo(ChartSwatchImpl);
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Map a SeriesDef.type string to its swatch variant.
|
|
77
|
+
*
|
|
78
|
+
* Bar series get a square chip; everything else (line, dot, area, and any
|
|
79
|
+
* other non-bar mark) gets a short stroke. Centralized so legend, tooltip,
|
|
80
|
+
* and any custom chrome stay consistent.
|
|
81
|
+
*/
|
|
82
|
+
export function swatchVariantForType(
|
|
83
|
+
type: string | undefined,
|
|
84
|
+
): ChartSwatchVariant {
|
|
85
|
+
return type === 'bar' ? 'square' : 'line';
|
|
86
|
+
}
|