@astryxdesign/charts 0.6.2-canary.a4bcbeb → 0.6.2-canary.a7f992d
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/ChartAxis.d.ts +1 -1
- package/dist/ChartAxis.d.ts.map +1 -1
- package/dist/ChartAxis.js +16 -6
- package/dist/ChartGrid.d.ts.map +1 -1
- package/dist/ChartGrid.js +1 -0
- package/dist/ChartSwatch.js +3 -3
- package/dist/ChartTooltip.d.ts +2 -2
- package/dist/ChartTooltip.d.ts.map +1 -1
- package/dist/ChartTooltip.js +109 -54
- package/dist/charts.css +0 -3
- package/package.json +3 -3
- package/src/ChartAxis.doc.mjs +162 -0
- package/src/ChartAxis.spec.md +211 -0
- package/src/ChartAxis.test.tsx +80 -2
- package/src/ChartAxis.tsx +15 -8
- package/src/ChartGrid.doc.mjs +114 -0
- package/src/ChartGrid.spec.md +184 -0
- package/src/ChartGrid.test.tsx +20 -1
- package/src/ChartGrid.tsx +1 -0
- package/src/ChartLegend.doc.mjs +146 -0
- package/src/ChartLegend.spec.md +203 -0
- package/src/ChartSwatch.doc.mjs +104 -2
- package/src/ChartSwatch.spec.md +189 -0
- package/src/ChartSwatch.tsx +3 -3
- package/src/ChartTooltip.doc.mjs +167 -0
- package/src/ChartTooltip.spec.md +240 -0
- package/src/ChartTooltip.test.tsx +324 -31
- package/src/ChartTooltip.tsx +122 -65
package/dist/ChartAxis.d.ts
CHANGED
|
@@ -7,7 +7,7 @@ export interface ChartAxisProps {
|
|
|
7
7
|
maxTicks?: number;
|
|
8
8
|
/** Custom tick formatter */
|
|
9
9
|
tickFormat?: (value: unknown) => string;
|
|
10
|
-
/** Truncate labels to this many characters (appends "\u2026"). */
|
|
10
|
+
/** Truncate labels to this many user-perceived characters (appends "\u2026"). */
|
|
11
11
|
truncate?: number;
|
|
12
12
|
/** Enable smooth transitions for streaming (default: true) */
|
|
13
13
|
animated?: boolean;
|
package/dist/ChartAxis.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartAxis.d.ts","sourceRoot":"","sources":["../src/ChartAxis.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ChartAxis.d.ts","sourceRoot":"","sources":["../src/ChartAxis.tsx"],"names":[],"mappings":"AA4BA,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,QAAQ,EAAE,KAAK,GAAG,OAAO,GAAG,QAAQ,GAAG,MAAM,CAAC;IAC9C,6DAA6D;IAC7D,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,sFAAsF;IACtF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4BAA4B;IAC5B,UAAU,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;IACxC,iFAAiF;IACjF,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,0EAA0E;IAC1E,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AAeD;;;;;;;;;;GAUG;AACH,wBAAgB,SAAS,CAAC,EACxB,QAAQ,EACR,SAAa,EACb,QAAQ,EACR,UAAU,EACV,QAAQ,EACR,QAAe,EACf,YAAoC,EACpC,SAAiB,GAClB,EAAE,cAAc,+BA4LhB"}
|
package/dist/ChartAxis.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file ChartAxis.tsx (v2)
|
|
5
|
+
* @input Chart context scales, axis presentation props, and grapheme-safe text utilities
|
|
5
6
|
* @output Renders an axis (top, right, bottom, left) using the chart's scales
|
|
6
7
|
* @position Child of Chart v2; reads scales from chart context
|
|
7
8
|
*
|
|
@@ -19,6 +20,7 @@
|
|
|
19
20
|
|
|
20
21
|
import { useCallback, useMemo } from 'react';
|
|
21
22
|
import * as stylex from '@stylexjs/stylex';
|
|
23
|
+
import { characterCount, truncateCharacters } from '@astryxdesign/core/utils';
|
|
22
24
|
import '@astryxdesign/core/theme/tokens.stylex';
|
|
23
25
|
import { colorVars } from '@astryxdesign/core/theme/tokens.stylex';
|
|
24
26
|
import { useChart } from "./ChartContext.js";
|
|
@@ -85,20 +87,28 @@ export function ChartAxis({
|
|
|
85
87
|
}, [tickFormat, scale, safeTickCount]);
|
|
86
88
|
const format = useCallback(value => {
|
|
87
89
|
const str = (tickFormat ?? autoFormat ?? String)(value);
|
|
88
|
-
|
|
90
|
+
if (!truncate) {
|
|
91
|
+
return str;
|
|
92
|
+
}
|
|
93
|
+
const shortened = truncateCharacters(str, truncate, '');
|
|
94
|
+
return shortened === str ? str : shortened + '\u2026';
|
|
89
95
|
}, [tickFormat, autoFormat, truncate]);
|
|
90
96
|
const ticks = useMemo(() => {
|
|
97
|
+
// Format each generated tick once so density and rendering share the same
|
|
98
|
+
// grapheme-safe label instead of repeating consumer formatting/segmentation.
|
|
91
99
|
let allTicks;
|
|
92
100
|
if (isBandScale(scale)) {
|
|
93
101
|
allTicks = scale.domain().map(d => ({
|
|
94
102
|
value: d,
|
|
95
|
-
offset: (scale(d) ?? 0) + scale.bandwidth() / 2
|
|
103
|
+
offset: (scale(d) ?? 0) + scale.bandwidth() / 2,
|
|
104
|
+
label: format(d)
|
|
96
105
|
}));
|
|
97
106
|
} else {
|
|
98
107
|
const linearScale = scale;
|
|
99
108
|
allTicks = linearScale.ticks(safeTickCount).map(d => ({
|
|
100
109
|
value: d,
|
|
101
|
-
offset: linearScale(d)
|
|
110
|
+
offset: linearScale(d),
|
|
111
|
+
label: format(d)
|
|
102
112
|
}));
|
|
103
113
|
}
|
|
104
114
|
|
|
@@ -111,7 +121,7 @@ export function ChartAxis({
|
|
|
111
121
|
let cap = maxTicks;
|
|
112
122
|
if (cap == null && allTicks.length > 1) {
|
|
113
123
|
if (isHorizontal && width > 0) {
|
|
114
|
-
const widestChars = allTicks.reduce((m,
|
|
124
|
+
const widestChars = allTicks.reduce((m, tick) => Math.max(m, characterCount(tick.label)), 1);
|
|
115
125
|
const approxLabelPx = widestChars * 7 + 16;
|
|
116
126
|
cap = Math.max(1, Math.floor(width / approxLabelPx));
|
|
117
127
|
} else if (!isHorizontal && height > 0) {
|
|
@@ -157,9 +167,9 @@ export function ChartAxis({
|
|
|
157
167
|
}
|
|
158
168
|
}), ticks.map(({
|
|
159
169
|
value,
|
|
160
|
-
offset
|
|
170
|
+
offset,
|
|
171
|
+
label
|
|
161
172
|
}) => {
|
|
162
|
-
const label = format(value);
|
|
163
173
|
// Key off the raw tick value, not the formatted label: distinct ticks
|
|
164
174
|
// can format (or truncate) to the same string and would collide as keys.
|
|
165
175
|
const key = String(value);
|
package/dist/ChartGrid.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartGrid.d.ts","sourceRoot":"","sources":["../src/ChartGrid.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ChartGrid.d.ts","sourceRoot":"","sources":["../src/ChartGrid.tsx"],"names":[],"mappings":"AAgBA,MAAM,WAAW,cAAc;IAC7B,mDAAmD;IACnD,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,kDAAkD;IAClD,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AASD;;;;;;;;;;;GAWG;AACH,wBAAgB,SAAS,CAAC,EACxB,UAAiB,EACjB,QAAgB,EAChB,SAAa,GACd,EAAE,cAAc,+BAmEhB"}
|
package/dist/ChartGrid.js
CHANGED
package/dist/ChartSwatch.js
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @file ChartSwatch.tsx
|
|
5
|
+
* @input Caller-owned CSS color and optional square or line shape
|
|
5
6
|
* @output Color swatch primitive for legends, tooltips, and standalone use
|
|
6
|
-
* @position Composable primitive — used by ChartLegend
|
|
7
|
-
*
|
|
8
|
-
* is needed.
|
|
7
|
+
* @position Composable primitive — used by ChartLegend and ChartTooltip, or
|
|
8
|
+
* independently anywhere a series indicator is needed.
|
|
9
9
|
*
|
|
10
10
|
* @example
|
|
11
11
|
* ```
|
package/dist/ChartTooltip.d.ts
CHANGED
|
@@ -39,8 +39,8 @@ export interface ChartTooltipProps {
|
|
|
39
39
|
* Composable grouped tooltip for Chart.
|
|
40
40
|
*
|
|
41
41
|
* Subscribes to the chart's pointer event stream and only re-renders when the
|
|
42
|
-
* hovered data index changes (not on every pointer move). Card
|
|
43
|
-
*
|
|
42
|
+
* hovered data index changes (not on every pointer move). Card coordinates retain
|
|
43
|
+
* their previous state when the resolved point and browser geometry are unchanged.
|
|
44
44
|
*/
|
|
45
45
|
export declare function ChartTooltip({ series, render, hoverIndicator, showHoverDots, placement, }: ChartTooltipProps): import("react").JSX.Element;
|
|
46
46
|
//# sourceMappingURL=ChartTooltip.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ChartTooltip.d.ts","sourceRoot":"","sources":["../src/ChartTooltip.tsx"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"ChartTooltip.d.ts","sourceRoot":"","sources":["../src/ChartTooltip.tsx"],"names":[],"mappings":"AA8BA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAYf,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,WAAW,CAAC;AACnB,OAAO,KAAK,EAAC,SAAS,EAAoB,MAAM,SAAS,CAAC;AAG1D,MAAM,MAAM,qBAAqB,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,KAAK,CAAC;AAEtE,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,MAAM,CAAC,EAAE,SAAS,SAAS,EAAE,CAAC;IAC9B;;;OAGG;IACH,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,KAAK,SAAS,CAAC;IAC5E;;;;;;;OAOG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;OAGG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,SAAS,CAAC,EAAE,qBAAqB,CAAC;CACnC;AAwDD;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,EAC3B,MAAW,EACX,MAAM,EACN,cAAqB,EACrB,aAAoB,EACpB,SAAkB,GACnB,EAAE,iBAAiB,+BAqUnB"}
|
package/dist/ChartTooltip.js
CHANGED
|
@@ -17,7 +17,11 @@
|
|
|
17
17
|
*
|
|
18
18
|
* // Composable — drop into the chart's children for full control
|
|
19
19
|
* <Chart>
|
|
20
|
-
* <ChartTooltip
|
|
20
|
+
* <ChartTooltip
|
|
21
|
+
* series={series}
|
|
22
|
+
* hoverIndicator={false}
|
|
23
|
+
* placement="top"
|
|
24
|
+
* />
|
|
21
25
|
* </Chart>
|
|
22
26
|
* ```
|
|
23
27
|
*/
|
|
@@ -29,13 +33,25 @@ import { createPortal } from 'react-dom';
|
|
|
29
33
|
import * as stylex from '@stylexjs/stylex';
|
|
30
34
|
import '@astryxdesign/core/theme/tokens.stylex';
|
|
31
35
|
import { colorVars, radiusVars, shadowVars, spacingVars } from '@astryxdesign/core/theme/tokens.stylex';
|
|
32
|
-
import { Text } from '@astryxdesign/core';
|
|
33
|
-
import { VStack, HStack } from '@astryxdesign/core';
|
|
36
|
+
import { HStack, Text, useLayer, VStack } from '@astryxdesign/core';
|
|
34
37
|
import { useChart } from "./ChartContext.js";
|
|
35
38
|
import { ChartSwatch, swatchVariantForType } from "./ChartSwatch.js";
|
|
36
39
|
import { deriveTooltipSeriesValues, shouldRenderHoverDot } from "./tooltip.js";
|
|
37
40
|
import { isBandScale, xPixel } from "./utils.js";
|
|
38
41
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
42
|
+
const styles = {
|
|
43
|
+
card: {
|
|
44
|
+
kWkggS: "x1prclbq",
|
|
45
|
+
kaIpWk: "x1hviunn",
|
|
46
|
+
k8WAf4: "xce4md1",
|
|
47
|
+
kg3NbH: "xrrkdod",
|
|
48
|
+
kGVxlE: "x14hfi27",
|
|
49
|
+
kfzvcC: "x47corl",
|
|
50
|
+
khDVqt: "xuxw1ft",
|
|
51
|
+
$$css: true
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
39
55
|
/** Default tooltip body — used when `render` is not provided. */
|
|
40
56
|
const DefaultTooltipContent = /*#__PURE__*/memo(function DefaultTooltipContent({
|
|
41
57
|
xValue,
|
|
@@ -75,8 +91,8 @@ const DefaultTooltipContent = /*#__PURE__*/memo(function DefaultTooltipContent({
|
|
|
75
91
|
* Composable grouped tooltip for Chart.
|
|
76
92
|
*
|
|
77
93
|
* Subscribes to the chart's pointer event stream and only re-renders when the
|
|
78
|
-
* hovered data index changes (not on every pointer move). Card
|
|
79
|
-
*
|
|
94
|
+
* hovered data index changes (not on every pointer move). Card coordinates retain
|
|
95
|
+
* their previous state when the resolved point and browser geometry are unchanged.
|
|
80
96
|
*/
|
|
81
97
|
export function ChartTooltip({
|
|
82
98
|
series = [],
|
|
@@ -97,20 +113,44 @@ export function ChartTooltip({
|
|
|
97
113
|
height
|
|
98
114
|
} = useChart();
|
|
99
115
|
const [hoveredIndex, setHoveredIndex] = useState(null);
|
|
116
|
+
const [cardPosition, setCardPosition] = useState({
|
|
117
|
+
x: 0,
|
|
118
|
+
y: 0
|
|
119
|
+
});
|
|
120
|
+
const [portalTarget, setPortalTarget] = useState(null);
|
|
100
121
|
const cardRef = useRef(null);
|
|
122
|
+
const lastPointerEventRef = useRef(null);
|
|
123
|
+
const {
|
|
124
|
+
hide: hideLayer,
|
|
125
|
+
isOpen: isLayerOpen,
|
|
126
|
+
render: renderLayer,
|
|
127
|
+
show: showLayer
|
|
128
|
+
} = useLayer({
|
|
129
|
+
mode: 'fixed'
|
|
130
|
+
});
|
|
101
131
|
// Whether the card is currently suppressed (no content, or a custom `render`
|
|
102
|
-
// opted out by returning null).
|
|
103
|
-
//
|
|
104
|
-
//
|
|
105
|
-
|
|
132
|
+
// opted out by returning null). Start closed; once present content opens the
|
|
133
|
+
// Layer, a content-bearing index change keeps it open while React swaps the
|
|
134
|
+
// content. positionCard also reads this so a same-index pointer move cannot
|
|
135
|
+
// re-reveal a suppressed card before the visibility effect confirms content.
|
|
136
|
+
const cardHiddenRef = useRef(true);
|
|
137
|
+
|
|
138
|
+
// The Layer host must remain in the chart's nearest HTML subtree so nested
|
|
139
|
+
// Theme and MediaTheme scopes keep applying. The portal crosses only the SVG
|
|
140
|
+
// boundary; browser top-layer promotion remains Layer's responsibility.
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
const nextTarget = svgRef.current?.parentElement ?? null;
|
|
143
|
+
setPortalTarget(current => current === nextTarget ? current : nextTarget);
|
|
144
|
+
}, [svgRef, width]);
|
|
106
145
|
|
|
107
146
|
// Stable Set of resolved series keys — used by deriveTooltipSeriesValues.
|
|
108
147
|
// Recomputed only when the resolved map identity changes (per layout pass).
|
|
109
148
|
const resolvedKeys = useMemo(() => new Set(resolved.keys()), [resolved]);
|
|
110
149
|
|
|
111
150
|
// ─── Card positioning ──────────────────────────────────────────────────
|
|
112
|
-
//
|
|
113
|
-
//
|
|
151
|
+
// Layer owns top-layer promotion. Pointer moves update coordinates only when
|
|
152
|
+
// the hovered point or browser geometry changes, so moves within one point do
|
|
153
|
+
// not force a React commit.
|
|
114
154
|
const positionCard = useCallback(e => {
|
|
115
155
|
const card = cardRef.current;
|
|
116
156
|
const svg = svgRef.current;
|
|
@@ -118,14 +158,19 @@ export function ChartTooltip({
|
|
|
118
158
|
return;
|
|
119
159
|
}
|
|
120
160
|
if (!svg || !e.nearest || cardHiddenRef.current) {
|
|
121
|
-
|
|
161
|
+
hideLayer();
|
|
162
|
+
return;
|
|
163
|
+
}
|
|
164
|
+
showLayer();
|
|
165
|
+
const layerHost = card.parentElement;
|
|
166
|
+
if (!layerHost) {
|
|
122
167
|
return;
|
|
123
168
|
}
|
|
124
169
|
const svgRect = svg.getBoundingClientRect();
|
|
125
170
|
const screenX = svgRect.left + margin.left + e.nearest.px;
|
|
126
171
|
const screenY = svgRect.top + margin.top;
|
|
127
|
-
const cardWidth =
|
|
128
|
-
const cardHeight =
|
|
172
|
+
const cardWidth = layerHost.offsetWidth;
|
|
173
|
+
const cardHeight = layerHost.offsetHeight;
|
|
129
174
|
const viewportWidth = window.innerWidth;
|
|
130
175
|
const viewportHeight = window.innerHeight;
|
|
131
176
|
const gap = 8;
|
|
@@ -150,27 +195,31 @@ export function ChartTooltip({
|
|
|
150
195
|
}
|
|
151
196
|
}
|
|
152
197
|
|
|
153
|
-
// Clamp
|
|
154
|
-
//
|
|
155
|
-
// placement (pinned placements and the near-edge `auto` flip alike).
|
|
198
|
+
// Clamp the card coordinates to the viewport. Oversized custom content
|
|
199
|
+
// keeps its caller-owned dimensions and may extend past the far edge.
|
|
156
200
|
x = Math.max(gap, Math.min(x, viewportWidth - cardWidth - gap));
|
|
157
201
|
y = Math.max(gap, Math.min(y, viewportHeight - cardHeight - gap));
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
202
|
+
setCardPosition(current => current.x === x && current.y === y ? current : {
|
|
203
|
+
x,
|
|
204
|
+
y
|
|
205
|
+
});
|
|
206
|
+
}, [svgRef, margin, placement, hideLayer, showLayer]);
|
|
162
207
|
|
|
163
208
|
// ─── Subscribe to pointer events ───────────────────────────────────────
|
|
164
209
|
useEffect(() => {
|
|
165
210
|
let currentIndex = null;
|
|
166
211
|
const unsub = onPointer(e => {
|
|
212
|
+
lastPointerEventRef.current = e;
|
|
167
213
|
const newIndex = e.nearest?.dataIndex ?? null;
|
|
168
214
|
if (newIndex !== currentIndex) {
|
|
169
215
|
currentIndex = newIndex;
|
|
170
|
-
//
|
|
171
|
-
//
|
|
172
|
-
//
|
|
173
|
-
|
|
216
|
+
// Keep a closed/suppressed card closed until React resolves content.
|
|
217
|
+
// Do not preemptively close an already-visible card for another index:
|
|
218
|
+
// the visibility effect closes absent content after commit, while
|
|
219
|
+
// content-bearing transitions keep the same Layer continuously open.
|
|
220
|
+
if (newIndex == null) {
|
|
221
|
+
cardHiddenRef.current = true;
|
|
222
|
+
}
|
|
174
223
|
setHoveredIndex(newIndex);
|
|
175
224
|
}
|
|
176
225
|
positionCard(e);
|
|
@@ -269,24 +318,31 @@ export function ChartTooltip({
|
|
|
269
318
|
});
|
|
270
319
|
}, [hoverIndicator, hoveredIndex, series, xScale, data, xKey, width, height, margin.top]);
|
|
271
320
|
|
|
272
|
-
//
|
|
273
|
-
//
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
321
|
+
// Render props are ordinary React content. Resolve them once per commit so a
|
|
322
|
+
// custom renderer is not invoked separately by the visibility synchronizer.
|
|
323
|
+
// Keep it browser-only: the layer has no portal target during SSR.
|
|
324
|
+
const cardContent = typeof document === 'undefined' || hoveredIndex == null || datum == null ? null : render ? render(xValue, seriesValues) : /*#__PURE__*/_jsx(DefaultTooltipContent, {
|
|
325
|
+
xValue: xValue,
|
|
326
|
+
rows: seriesValues
|
|
327
|
+
});
|
|
328
|
+
const isCardHidden = hoveredIndex == null || datum == null || render != null && cardContent == null;
|
|
329
|
+
const hasNativePopover = typeof HTMLElement !== 'undefined' && typeof HTMLElement.prototype.showPopover === 'function';
|
|
330
|
+
const shouldHideLayerHost = isCardHidden || !hasNativePopover && !isLayerOpen;
|
|
331
|
+
|
|
332
|
+
// Synchronize the shared Layer's native Popover state after React commits the
|
|
333
|
+
// new card body. The second positioning pass measures that final body; later
|
|
334
|
+
// moves within the same data point usually resolve to the existing state.
|
|
280
335
|
useEffect(() => {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
336
|
+
cardHiddenRef.current = isCardHidden;
|
|
337
|
+
if (isCardHidden) {
|
|
338
|
+
hideLayer();
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const pointerEvent = lastPointerEventRef.current;
|
|
342
|
+
if (pointerEvent) {
|
|
343
|
+
positionCard(pointerEvent);
|
|
288
344
|
}
|
|
289
|
-
}, [
|
|
345
|
+
}, [isCardHidden, hoveredIndex, xValue, seriesValues, render, hideLayer, positionCard]);
|
|
290
346
|
|
|
291
347
|
// ─── Render ────────────────────────────────────────────────────────────
|
|
292
348
|
if (typeof document === 'undefined') {
|
|
@@ -295,22 +351,21 @@ export function ChartTooltip({
|
|
|
295
351
|
children: [hoverIndicatorElement, dots]
|
|
296
352
|
});
|
|
297
353
|
}
|
|
298
|
-
|
|
299
|
-
// Mirrors the visibility effect's first two conditions: no card when there's
|
|
300
|
-
// no hover or the hovered datum is stale/out-of-range (so `render` and the
|
|
301
|
-
// default body are never called with an undefined x-value + empty rows).
|
|
302
|
-
const cardContent = hoveredIndex == null || datum == null ? null : render ? render(xValue, seriesValues) : /*#__PURE__*/_jsx(DefaultTooltipContent, {
|
|
303
|
-
xValue: xValue,
|
|
304
|
-
rows: seriesValues
|
|
305
|
-
});
|
|
306
354
|
return /*#__PURE__*/_jsxs(_Fragment, {
|
|
307
|
-
children: [hoverIndicatorElement, dots, /*#__PURE__*/createPortal(/*#__PURE__*/_jsx("div", {
|
|
355
|
+
children: [hoverIndicatorElement, dots, portalTarget ? /*#__PURE__*/createPortal(renderLayer(/*#__PURE__*/_jsx("div", {
|
|
308
356
|
ref: cardRef,
|
|
309
357
|
role: "tooltip",
|
|
310
|
-
...{
|
|
311
|
-
className: "xixxii4 x1prclbq x1hviunn xce4md1 xrrkdod x14hfi27 x47corl xuxw1ft x1q2oy4v x1s85apg"
|
|
312
|
-
},
|
|
313
358
|
children: cardContent
|
|
314
|
-
}),
|
|
359
|
+
}), {
|
|
360
|
+
x: cardPosition.x,
|
|
361
|
+
y: cardPosition.y,
|
|
362
|
+
// Native Popover keeps a closed host out of layout itself, so
|
|
363
|
+
// remove our guard before showPopover() measures it. Layer's
|
|
364
|
+
// reduced fallback needs the explicit closed display state.
|
|
365
|
+
style: shouldHideLayerHost ? {
|
|
366
|
+
display: 'none'
|
|
367
|
+
} : undefined,
|
|
368
|
+
xstyle: styles.card
|
|
369
|
+
}), portalTarget) : null]
|
|
315
370
|
});
|
|
316
371
|
}
|
package/dist/charts.css
CHANGED
|
@@ -14,15 +14,12 @@
|
|
|
14
14
|
.x1prclbq:not(#\#):not(#\#){background-color:var(--color-background-popover)}
|
|
15
15
|
.xl8spv7:not(#\#):not(#\#){background-color:var(--x-backgroundColor)}
|
|
16
16
|
.x14hfi27:not(#\#):not(#\#){box-shadow:var(--shadow-med)}
|
|
17
|
-
.x1s85apg:not(#\#):not(#\#){display:none}
|
|
18
17
|
.x2lah0s:not(#\#):not(#\#){flex-shrink:0}
|
|
19
18
|
.x47corl:not(#\#):not(#\#){pointer-events:none}
|
|
20
|
-
.xixxii4:not(#\#):not(#\#){position:fixed}
|
|
21
19
|
.xnpr05h:not(#\#):not(#\#){stroke-width:1}
|
|
22
20
|
.x113i6qz:not(#\#):not(#\#){stroke:var(--color-border-emphasized)}
|
|
23
21
|
.x1lo1t26:not(#\#):not(#\#){stroke:var(--color-text-primary)}
|
|
24
22
|
.xuxw1ft:not(#\#):not(#\#){white-space:nowrap}
|
|
25
|
-
.x1q2oy4v:not(#\#):not(#\#){z-index:9999}
|
|
26
23
|
.xuoj239:not(#\#):not(#\#):not(#\#){height:3px}
|
|
27
24
|
.xdk7pt:not(#\#):not(#\#):not(#\#){height:8px}
|
|
28
25
|
.xwh1t77:not(#\#):not(#\#):not(#\#){margin-bottom:var(--spacing-4)}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@astryxdesign/charts",
|
|
3
|
-
"version": "0.6.2-canary.
|
|
3
|
+
"version": "0.6.2-canary.a7f992d",
|
|
4
4
|
"description": "Astryx charts — a config-model data visualization library (d3-based). Published to npm only under the @canary dist-tag for early testing; never released as a stable (latest) version.",
|
|
5
5
|
"author": "Meta Open Source",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"peerDependencies": {
|
|
49
49
|
"@stylexjs/stylex": ">=0.10.0",
|
|
50
|
-
"@astryxdesign/core": "0.6.2-canary.
|
|
50
|
+
"@astryxdesign/core": "0.6.2-canary.a7f992d",
|
|
51
51
|
"react": ">=19.0.0",
|
|
52
52
|
"react-dom": ">=19.0.0"
|
|
53
53
|
},
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"d3-shape": "^3.2.0"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@astryxdesign/cli": "0.6.2-canary.
|
|
60
|
+
"@astryxdesign/cli": "0.6.2-canary.a7f992d",
|
|
61
61
|
"@babel/cli": "^7.29.7",
|
|
62
62
|
"@babel/core": "^7.29.7",
|
|
63
63
|
"@babel/preset-react": "^7.29.7",
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
// Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
2
|
+
|
|
3
|
+
/** @type {import('@astryxdesign/cli/authoring').ComponentDoc} */
|
|
4
|
+
|
|
5
|
+
export const docs = {
|
|
6
|
+
name: 'ChartAxis',
|
|
7
|
+
displayName: 'Chart Axis',
|
|
8
|
+
group: 'Charts',
|
|
9
|
+
category: 'Data Visualization',
|
|
10
|
+
isHiddenFromOverview: true,
|
|
11
|
+
keywords: ['chart', 'axis', 'ticks', 'labels', 'scale', 'data visualization'],
|
|
12
|
+
|
|
13
|
+
usage: {
|
|
14
|
+
description:
|
|
15
|
+
'ChartAxis renders tick labels and optional edge and tick lines from the scales owned by a parent Chart. Use it in the Chart axes slot for physical top, right, bottom, or left plot edges.',
|
|
16
|
+
bestPractices: [
|
|
17
|
+
{
|
|
18
|
+
guidance: true,
|
|
19
|
+
description:
|
|
20
|
+
'Use ChartAxis inside Chart so its labels, ticks, and edge line share the same scales and plot dimensions as the marks.',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
guidance: true,
|
|
24
|
+
description:
|
|
25
|
+
'Use tickFormat for product-specific units or locale formatting, and maxTicks or truncate when dense category labels need a bounded presentation.',
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
guidance: false,
|
|
29
|
+
description:
|
|
30
|
+
'Use axis labels or a pointer tooltip as the only way to obtain important values. Give Chart an accessible name and preserve an equivalent data view or summary.',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
guidance: false,
|
|
34
|
+
description:
|
|
35
|
+
'Hide the edge line while showing tick marks. showTicks intentionally keeps the line visible so each mark has a grounded edge.',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
accessibility: [
|
|
39
|
+
{
|
|
40
|
+
name: 'Chart alternative',
|
|
41
|
+
category: 'Content',
|
|
42
|
+
criterion: '1.1.1 Non-text Content',
|
|
43
|
+
requirement: 'Required on the parent Chart',
|
|
44
|
+
states: ['All'],
|
|
45
|
+
description:
|
|
46
|
+
'Chart owns the accessible image name and supported small-data table. Tick labels supplement that chart-level alternative rather than replacing it.',
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: 'Label text integrity',
|
|
50
|
+
category: 'Content',
|
|
51
|
+
criterion: 'Unicode grapheme clusters',
|
|
52
|
+
requirement: 'Required when truncate is set',
|
|
53
|
+
states: ['Truncated labels'],
|
|
54
|
+
description:
|
|
55
|
+
'Truncation keeps emoji, joined emoji sequences, flags, and combining marks intact before appending the ellipsis.',
|
|
56
|
+
},
|
|
57
|
+
],
|
|
58
|
+
anatomy: [
|
|
59
|
+
{
|
|
60
|
+
name: 'Axis edge line',
|
|
61
|
+
required: false,
|
|
62
|
+
description:
|
|
63
|
+
'Line along the selected plot edge. It is shown by default only on the bottom edge and whenever tick marks are shown.',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'Tick marks',
|
|
67
|
+
required: false,
|
|
68
|
+
description:
|
|
69
|
+
'Short lines extending outward from each retained tick position.',
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Tick labels',
|
|
73
|
+
required: true,
|
|
74
|
+
description:
|
|
75
|
+
'Formatted scale values or categories positioned outside the selected plot edge.',
|
|
76
|
+
},
|
|
77
|
+
],
|
|
78
|
+
},
|
|
79
|
+
|
|
80
|
+
props: [
|
|
81
|
+
{
|
|
82
|
+
name: 'position',
|
|
83
|
+
type: "'top' | 'right' | 'bottom' | 'left'",
|
|
84
|
+
description: 'Physical plot edge on which to render the axis.',
|
|
85
|
+
required: true,
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
name: 'tickCount',
|
|
89
|
+
type: 'number',
|
|
90
|
+
description:
|
|
91
|
+
'Approximate continuous-scale tick count. d3 chooses the final values.',
|
|
92
|
+
default: '5',
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: 'maxTicks',
|
|
96
|
+
type: 'number',
|
|
97
|
+
description:
|
|
98
|
+
'Maximum displayed label count. Labels are evenly skipped when the generated set is larger.',
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: 'tickFormat',
|
|
102
|
+
type: '(value: unknown) => string',
|
|
103
|
+
description:
|
|
104
|
+
'Formats each retained tick. When omitted, continuous scales use d3 formatting and band scales use their category strings.',
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: 'truncate',
|
|
108
|
+
type: 'number',
|
|
109
|
+
description:
|
|
110
|
+
'Maximum content characters retained before an ellipsis is appended. User-perceived characters remain intact.',
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: 'animated',
|
|
114
|
+
type: 'boolean',
|
|
115
|
+
description:
|
|
116
|
+
'Whether tick position and visibility changes use the built-in transition.',
|
|
117
|
+
default: 'true',
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
name: 'showAxisLine',
|
|
121
|
+
type: 'boolean',
|
|
122
|
+
description:
|
|
123
|
+
'Whether to draw the edge line. Tick marks force the line on so they remain grounded.',
|
|
124
|
+
default: 'true for bottom; false for top, right, and left',
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'showTicks',
|
|
128
|
+
type: 'boolean',
|
|
129
|
+
description: 'Whether to draw an outward mark at each retained tick.',
|
|
130
|
+
default: 'false',
|
|
131
|
+
},
|
|
132
|
+
],
|
|
133
|
+
|
|
134
|
+
examples: [
|
|
135
|
+
{
|
|
136
|
+
label: 'Bottom and left axes',
|
|
137
|
+
code: `import {Chart, ChartAxis, ChartGrid, bar} from '@astryxdesign/charts';
|
|
138
|
+
|
|
139
|
+
<Chart
|
|
140
|
+
data={monthlyRevenue}
|
|
141
|
+
xKey="month"
|
|
142
|
+
series={[bar('revenue')]}
|
|
143
|
+
grid={<ChartGrid horizontal />}
|
|
144
|
+
axes={
|
|
145
|
+
<>
|
|
146
|
+
<ChartAxis position="bottom" />
|
|
147
|
+
<ChartAxis position="left" tickFormat={value => String(value)} />
|
|
148
|
+
</>
|
|
149
|
+
}
|
|
150
|
+
/>;`,
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
label: 'Dense category labels',
|
|
154
|
+
code: `<ChartAxis
|
|
155
|
+
position="bottom"
|
|
156
|
+
maxTicks={8}
|
|
157
|
+
truncate={12}
|
|
158
|
+
showTicks
|
|
159
|
+
/>;`,
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
};
|