@chipmobilesdk/rn-chart 0.2.0 → 0.2.1
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 +126 -115
- package/README.md +381 -381
- package/package.json +46 -49
- package/skills/sdk-rn-chart/SKILL.md +68 -69
- package/src/a11y/summary.ts +170 -177
- package/src/contract/datasource.ts +126 -136
- package/src/contract/errors.ts +129 -133
- package/src/contract/format.ts +162 -168
- package/src/contract/interaction.ts +70 -71
- package/src/contract/marker.ts +75 -75
- package/src/contract/props.ts +57 -60
- package/src/contract/state.ts +88 -94
- package/src/contract/theme.ts +169 -177
- package/src/contract/value.ts +121 -125
- package/src/geometry/bar.ts +271 -273
- package/src/geometry/distribution.ts +197 -203
- package/src/geometry/heatmap.ts +189 -195
- package/src/geometry/hitTest.ts +117 -120
- package/src/geometry/layout.ts +162 -173
- package/src/geometry/markers.ts +55 -57
- package/src/geometry/path.ts +193 -199
- package/src/geometry/progress.ts +223 -230
- package/src/geometry/scale.ts +140 -146
- package/src/geometry/ticks.ts +145 -153
- package/src/geometry/timeSeries.ts +168 -168
- package/src/geometry/types.ts +94 -94
- package/src/index.ts +158 -176
- package/src/react/ChartBoundary.tsx +96 -96
- package/src/react/ChartFrame.tsx +583 -622
- package/src/react/ChartLegend.tsx +211 -215
- package/src/react/ChartThemeProvider.tsx +44 -48
- package/src/react/charts.tsx +154 -161
- package/src/react/useChartLayout.ts +124 -124
- package/src/react/useChartSelection.ts +152 -170
- package/src/render/Axis.tsx +120 -122
- package/src/render/Marks.tsx +173 -205
- package/src/validation/budgets.ts +103 -110
- package/src/validation/validateDatasource.ts +174 -174
|
@@ -1,170 +1,152 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Selection ownership: uncontrolled by default, controlled when the app
|
|
3
|
-
* supplies a value (FR-054a).
|
|
4
|
-
*
|
|
5
|
-
* Two properties matter and both are load-bearing:
|
|
6
|
-
*
|
|
7
|
-
* - The selection is **resolved against the current datasource on every
|
|
8
|
-
* render** rather than stored resolved. A reference to a point that no
|
|
9
|
-
* longer exists therefore draws nothing, in either mode (FR-054).
|
|
10
|
-
* - Selection state lives outside the geometry memo, so highlighting repaints
|
|
11
|
-
* the highlight without recomputing scales or path strings (FR-073).
|
|
12
|
-
*/
|
|
13
|
-
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
14
|
-
|
|
15
|
-
import type {
|
|
16
|
-
PointInteractionEvent,
|
|
17
|
-
SelectionRef,
|
|
18
|
-
SeriesInteractionEvent,
|
|
19
|
-
} from '../contract/interaction';
|
|
20
|
-
import { sameSelection } from '../contract/interaction';
|
|
21
|
-
import { findRegion, type HitRegion } from '../geometry/hitTest';
|
|
22
|
-
|
|
23
|
-
export interface UseChartSelectionArgs<M> {
|
|
24
|
-
/** Present (including `null`) means the app owns the selection. */
|
|
25
|
-
readonly controlled?: SelectionRef | null;
|
|
26
|
-
readonly onSelectionChange?: (
|
|
27
|
-
next: SelectionRef | null,
|
|
28
|
-
event: PointInteractionEvent<M> | null,
|
|
29
|
-
) => void;
|
|
30
|
-
readonly onSelectionUnresolvable?: (ref: SelectionRef) => void;
|
|
31
|
-
readonly hitRegions: readonly HitRegion[];
|
|
32
|
-
/** Non-ready statuses suppress selection entirely (FR-053). */
|
|
33
|
-
readonly interactionSuppressed: boolean;
|
|
34
|
-
/**
|
|
35
|
-
* False until the container has been measured and geometry exists.
|
|
36
|
-
*
|
|
37
|
-
* Without this, a chart that has not laid out yet has no hit regions, and a
|
|
38
|
-
* perfectly valid app-controlled selection would be reported as
|
|
39
|
-
* unresolvable on first paint — telling the app its state is wrong when it
|
|
40
|
-
* is not.
|
|
41
|
-
*/
|
|
42
|
-
readonly geometryReady: boolean;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export interface ChartSelectionApi<M> {
|
|
46
|
-
/** What to draw. Never a reference the datasource cannot resolve. */
|
|
47
|
-
readonly resolved: HitRegion | undefined;
|
|
48
|
-
readonly selection: SelectionRef | null;
|
|
49
|
-
readonly select: (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
args:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export interface
|
|
114
|
-
|
|
115
|
-
readonly
|
|
116
|
-
readonly
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
prev.includes(seriesId)
|
|
154
|
-
? prev.filter(id => id !== seriesId)
|
|
155
|
-
: [...prev, seriesId],
|
|
156
|
-
);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
args.onVisibilityChange?.({
|
|
160
|
-
type: 'visibilityChange',
|
|
161
|
-
seriesId,
|
|
162
|
-
visible: willBeVisible,
|
|
163
|
-
metadata: metadata as M,
|
|
164
|
-
});
|
|
165
|
-
},
|
|
166
|
-
[args, internal, isControlled],
|
|
167
|
-
);
|
|
168
|
-
|
|
169
|
-
return { hidden, toggle, enabled: args.enabled };
|
|
170
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Selection ownership: uncontrolled by default, controlled when the app
|
|
3
|
+
* supplies a value (FR-054a).
|
|
4
|
+
*
|
|
5
|
+
* Two properties matter and both are load-bearing:
|
|
6
|
+
*
|
|
7
|
+
* - The selection is **resolved against the current datasource on every
|
|
8
|
+
* render** rather than stored resolved. A reference to a point that no
|
|
9
|
+
* longer exists therefore draws nothing, in either mode (FR-054).
|
|
10
|
+
* - Selection state lives outside the geometry memo, so highlighting repaints
|
|
11
|
+
* the highlight without recomputing scales or path strings (FR-073).
|
|
12
|
+
*/
|
|
13
|
+
import { useCallback, useEffect, useRef, useState } from 'react';
|
|
14
|
+
|
|
15
|
+
import type {
|
|
16
|
+
PointInteractionEvent,
|
|
17
|
+
SelectionRef,
|
|
18
|
+
SeriesInteractionEvent,
|
|
19
|
+
} from '../contract/interaction';
|
|
20
|
+
import { sameSelection } from '../contract/interaction';
|
|
21
|
+
import { findRegion, type HitRegion } from '../geometry/hitTest';
|
|
22
|
+
|
|
23
|
+
export interface UseChartSelectionArgs<M> {
|
|
24
|
+
/** Present (including `null`) means the app owns the selection. */
|
|
25
|
+
readonly controlled?: SelectionRef | null;
|
|
26
|
+
readonly onSelectionChange?: (
|
|
27
|
+
next: SelectionRef | null,
|
|
28
|
+
event: PointInteractionEvent<M> | null,
|
|
29
|
+
) => void;
|
|
30
|
+
readonly onSelectionUnresolvable?: (ref: SelectionRef) => void;
|
|
31
|
+
readonly hitRegions: readonly HitRegion[];
|
|
32
|
+
/** Non-ready statuses suppress selection entirely (FR-053). */
|
|
33
|
+
readonly interactionSuppressed: boolean;
|
|
34
|
+
/**
|
|
35
|
+
* False until the container has been measured and geometry exists.
|
|
36
|
+
*
|
|
37
|
+
* Without this, a chart that has not laid out yet has no hit regions, and a
|
|
38
|
+
* perfectly valid app-controlled selection would be reported as
|
|
39
|
+
* unresolvable on first paint — telling the app its state is wrong when it
|
|
40
|
+
* is not.
|
|
41
|
+
*/
|
|
42
|
+
readonly geometryReady: boolean;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface ChartSelectionApi<M> {
|
|
46
|
+
/** What to draw. Never a reference the datasource cannot resolve. */
|
|
47
|
+
readonly resolved: HitRegion | undefined;
|
|
48
|
+
readonly selection: SelectionRef | null;
|
|
49
|
+
readonly select: (ref: SelectionRef | null, event: PointInteractionEvent<M> | null) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function useChartSelection<M>(args: UseChartSelectionArgs<M>): ChartSelectionApi<M> {
|
|
53
|
+
const isControlled = args.controlled !== undefined;
|
|
54
|
+
const [internal, setInternal] = useState<SelectionRef | null>(null);
|
|
55
|
+
|
|
56
|
+
const selection = isControlled ? (args.controlled ?? null) : internal;
|
|
57
|
+
|
|
58
|
+
// Resolve fresh every render. A stale reference resolves to `undefined` and
|
|
59
|
+
// simply is not drawn.
|
|
60
|
+
const resolved =
|
|
61
|
+
selection && !args.interactionSuppressed
|
|
62
|
+
? findRegion(args.hitRegions, selection.seriesId, selection.pointId)
|
|
63
|
+
: undefined;
|
|
64
|
+
|
|
65
|
+
// Tell a controlling app when its selection stopped resolving, rather than
|
|
66
|
+
// silently ignoring it. Uncontrolled selections are cleared instead.
|
|
67
|
+
const reportedRef = useRef<string | null>(null);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!selection || resolved || args.interactionSuppressed || !args.geometryReady) {
|
|
70
|
+
reportedRef.current = null;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const key = `${selection.seriesId}:${selection.pointId ?? ''}`;
|
|
75
|
+
if (reportedRef.current === key) {
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
reportedRef.current = key;
|
|
79
|
+
|
|
80
|
+
if (isControlled) {
|
|
81
|
+
args.onSelectionUnresolvable?.(selection);
|
|
82
|
+
} else {
|
|
83
|
+
setInternal(null);
|
|
84
|
+
}
|
|
85
|
+
}, [selection, resolved, isControlled, args, args.geometryReady]);
|
|
86
|
+
|
|
87
|
+
const select = useCallback(
|
|
88
|
+
(ref: SelectionRef | null, event: PointInteractionEvent<M> | null) => {
|
|
89
|
+
if (args.interactionSuppressed) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (!isControlled) {
|
|
93
|
+
setInternal(prev => (sameSelection(prev, ref) ? null : ref));
|
|
94
|
+
}
|
|
95
|
+
args.onSelectionChange?.(ref, event);
|
|
96
|
+
},
|
|
97
|
+
[args, isControlled],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
return { resolved, selection, select };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface UseSeriesVisibilityArgs<M> {
|
|
104
|
+
/** Present means the app owns visibility. */
|
|
105
|
+
readonly controlled?: readonly string[];
|
|
106
|
+
readonly onVisibilityChange?: (event: SeriesInteractionEvent<M>) => void;
|
|
107
|
+
/** Toggling stays off unless the app opts in (FR-049). */
|
|
108
|
+
readonly enabled: boolean;
|
|
109
|
+
/** Series marked hidden in the datasource itself. */
|
|
110
|
+
readonly initiallyHidden: readonly string[];
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export interface SeriesVisibilityApi {
|
|
114
|
+
readonly hidden: ReadonlySet<string>;
|
|
115
|
+
readonly toggle: (seriesId: string, metadata?: unknown) => void;
|
|
116
|
+
readonly enabled: boolean;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function useSeriesVisibility<M>(args: UseSeriesVisibilityArgs<M>): SeriesVisibilityApi {
|
|
120
|
+
const isControlled = args.controlled !== undefined;
|
|
121
|
+
const [internal, setInternal] = useState<readonly string[]>(args.initiallyHidden);
|
|
122
|
+
|
|
123
|
+
const hiddenList = isControlled ? (args.controlled ?? []) : internal;
|
|
124
|
+
const hidden = new Set(hiddenList);
|
|
125
|
+
|
|
126
|
+
const toggle = useCallback(
|
|
127
|
+
(seriesId: string, metadata?: unknown) => {
|
|
128
|
+
if (!args.enabled) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const currentlyHidden = new Set(isControlled ? (args.controlled ?? []) : internal);
|
|
133
|
+
const willBeVisible = currentlyHidden.has(seriesId);
|
|
134
|
+
|
|
135
|
+
if (!isControlled) {
|
|
136
|
+
setInternal(prev =>
|
|
137
|
+
prev.includes(seriesId) ? prev.filter(id => id !== seriesId) : [...prev, seriesId],
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
args.onVisibilityChange?.({
|
|
142
|
+
type: 'visibilityChange',
|
|
143
|
+
seriesId,
|
|
144
|
+
visible: willBeVisible,
|
|
145
|
+
metadata: metadata as M,
|
|
146
|
+
});
|
|
147
|
+
},
|
|
148
|
+
[args, internal, isControlled],
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return { hidden, toggle, enabled: args.enabled };
|
|
152
|
+
}
|
package/src/render/Axis.tsx
CHANGED
|
@@ -1,122 +1,120 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Axis and grid rendering. Private SVG adapter.
|
|
3
|
-
*
|
|
4
|
-
* Ticks arrive already thinned by `geometry/ticks.ts`, so nothing here decides
|
|
5
|
-
* what fits — this module only draws what the geometry chose.
|
|
6
|
-
*/
|
|
7
|
-
import React from 'react';
|
|
8
|
-
import { G, Line, Text as SvgText } from 'react-native-svg';
|
|
9
|
-
|
|
10
|
-
import type { ChartTheme } from '../contract/theme';
|
|
11
|
-
import type { PlotRect } from '../geometry/layout';
|
|
12
|
-
import { plotBottom, plotRight } from '../geometry/layout';
|
|
13
|
-
import type { Tick } from '../geometry/ticks';
|
|
14
|
-
|
|
15
|
-
export function Grid({
|
|
16
|
-
plot,
|
|
17
|
-
yTicks,
|
|
18
|
-
theme,
|
|
19
|
-
}: {
|
|
20
|
-
readonly plot: PlotRect;
|
|
21
|
-
readonly yTicks: readonly Tick[];
|
|
22
|
-
readonly theme: ChartTheme;
|
|
23
|
-
}) {
|
|
24
|
-
return (
|
|
25
|
-
<G>
|
|
26
|
-
{yTicks.map(tick => (
|
|
27
|
-
<Line
|
|
28
|
-
key={`grid-${tick.value}`}
|
|
29
|
-
x1={plot.x}
|
|
30
|
-
y1={tick.position}
|
|
31
|
-
x2={plotRight(plot)}
|
|
32
|
-
y2={tick.position}
|
|
33
|
-
stroke={theme.grid}
|
|
34
|
-
strokeWidth={1}
|
|
35
|
-
/>
|
|
36
|
-
))}
|
|
37
|
-
</G>
|
|
38
|
-
);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function XAxis({
|
|
42
|
-
plot,
|
|
43
|
-
ticks,
|
|
44
|
-
theme,
|
|
45
|
-
fontSize,
|
|
46
|
-
rotateLabels,
|
|
47
|
-
}: {
|
|
48
|
-
readonly plot: PlotRect;
|
|
49
|
-
readonly ticks: readonly Tick[];
|
|
50
|
-
readonly theme: ChartTheme;
|
|
51
|
-
readonly fontSize: number;
|
|
52
|
-
readonly rotateLabels?: boolean;
|
|
53
|
-
}) {
|
|
54
|
-
const baseline = plotBottom(plot);
|
|
55
|
-
|
|
56
|
-
return (
|
|
57
|
-
<G>
|
|
58
|
-
<Line
|
|
59
|
-
x1={plot.x}
|
|
60
|
-
y1={baseline}
|
|
61
|
-
x2={plotRight(plot)}
|
|
62
|
-
y2={baseline}
|
|
63
|
-
stroke={theme.axis}
|
|
64
|
-
strokeWidth={1}
|
|
65
|
-
/>
|
|
66
|
-
{ticks.map(tick => (
|
|
67
|
-
<SvgText
|
|
68
|
-
key={`x-${tick.position}-${tick.label}`}
|
|
69
|
-
x={tick.position}
|
|
70
|
-
y={baseline + fontSize + 4}
|
|
71
|
-
fill={theme.typography.axisLabel.color}
|
|
72
|
-
fontSize={fontSize}
|
|
73
|
-
textAnchor={rotateLabels ? 'end' : 'middle'}
|
|
74
|
-
transform={
|
|
75
|
-
rotateLabels
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
}
|
|
79
|
-
>
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
readonly
|
|
94
|
-
readonly
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
>
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
);
|
|
122
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Axis and grid rendering. Private SVG adapter.
|
|
3
|
+
*
|
|
4
|
+
* Ticks arrive already thinned by `geometry/ticks.ts`, so nothing here decides
|
|
5
|
+
* what fits — this module only draws what the geometry chose.
|
|
6
|
+
*/
|
|
7
|
+
import React from 'react';
|
|
8
|
+
import { G, Line, Text as SvgText } from 'react-native-svg';
|
|
9
|
+
|
|
10
|
+
import type { ChartTheme } from '../contract/theme';
|
|
11
|
+
import type { PlotRect } from '../geometry/layout';
|
|
12
|
+
import { plotBottom, plotRight } from '../geometry/layout';
|
|
13
|
+
import type { Tick } from '../geometry/ticks';
|
|
14
|
+
|
|
15
|
+
export function Grid({
|
|
16
|
+
plot,
|
|
17
|
+
yTicks,
|
|
18
|
+
theme,
|
|
19
|
+
}: {
|
|
20
|
+
readonly plot: PlotRect;
|
|
21
|
+
readonly yTicks: readonly Tick[];
|
|
22
|
+
readonly theme: ChartTheme;
|
|
23
|
+
}) {
|
|
24
|
+
return (
|
|
25
|
+
<G>
|
|
26
|
+
{yTicks.map(tick => (
|
|
27
|
+
<Line
|
|
28
|
+
key={`grid-${tick.value}`}
|
|
29
|
+
x1={plot.x}
|
|
30
|
+
y1={tick.position}
|
|
31
|
+
x2={plotRight(plot)}
|
|
32
|
+
y2={tick.position}
|
|
33
|
+
stroke={theme.grid}
|
|
34
|
+
strokeWidth={1}
|
|
35
|
+
/>
|
|
36
|
+
))}
|
|
37
|
+
</G>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function XAxis({
|
|
42
|
+
plot,
|
|
43
|
+
ticks,
|
|
44
|
+
theme,
|
|
45
|
+
fontSize,
|
|
46
|
+
rotateLabels,
|
|
47
|
+
}: {
|
|
48
|
+
readonly plot: PlotRect;
|
|
49
|
+
readonly ticks: readonly Tick[];
|
|
50
|
+
readonly theme: ChartTheme;
|
|
51
|
+
readonly fontSize: number;
|
|
52
|
+
readonly rotateLabels?: boolean;
|
|
53
|
+
}) {
|
|
54
|
+
const baseline = plotBottom(plot);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<G>
|
|
58
|
+
<Line
|
|
59
|
+
x1={plot.x}
|
|
60
|
+
y1={baseline}
|
|
61
|
+
x2={plotRight(plot)}
|
|
62
|
+
y2={baseline}
|
|
63
|
+
stroke={theme.axis}
|
|
64
|
+
strokeWidth={1}
|
|
65
|
+
/>
|
|
66
|
+
{ticks.map(tick => (
|
|
67
|
+
<SvgText
|
|
68
|
+
key={`x-${tick.position}-${tick.label}`}
|
|
69
|
+
x={tick.position}
|
|
70
|
+
y={baseline + fontSize + 4}
|
|
71
|
+
fill={theme.typography.axisLabel.color}
|
|
72
|
+
fontSize={fontSize}
|
|
73
|
+
textAnchor={rotateLabels ? 'end' : 'middle'}
|
|
74
|
+
transform={
|
|
75
|
+
rotateLabels ? `rotate(-45, ${tick.position}, ${baseline + fontSize + 4})` : undefined
|
|
76
|
+
}
|
|
77
|
+
>
|
|
78
|
+
{tick.label}
|
|
79
|
+
</SvgText>
|
|
80
|
+
))}
|
|
81
|
+
</G>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function YAxis({
|
|
86
|
+
plot,
|
|
87
|
+
ticks,
|
|
88
|
+
theme,
|
|
89
|
+
fontSize,
|
|
90
|
+
}: {
|
|
91
|
+
readonly plot: PlotRect;
|
|
92
|
+
readonly ticks: readonly Tick[];
|
|
93
|
+
readonly theme: ChartTheme;
|
|
94
|
+
readonly fontSize: number;
|
|
95
|
+
}) {
|
|
96
|
+
return (
|
|
97
|
+
<G>
|
|
98
|
+
<Line
|
|
99
|
+
x1={plot.x}
|
|
100
|
+
y1={plot.y}
|
|
101
|
+
x2={plot.x}
|
|
102
|
+
y2={plotBottom(plot)}
|
|
103
|
+
stroke={theme.axis}
|
|
104
|
+
strokeWidth={1}
|
|
105
|
+
/>
|
|
106
|
+
{ticks.map(tick => (
|
|
107
|
+
<SvgText
|
|
108
|
+
key={`y-${tick.position}-${tick.label}`}
|
|
109
|
+
x={plot.x - 6}
|
|
110
|
+
y={tick.position + fontSize / 3}
|
|
111
|
+
fill={theme.typography.axisLabel.color}
|
|
112
|
+
fontSize={fontSize}
|
|
113
|
+
textAnchor="end"
|
|
114
|
+
>
|
|
115
|
+
{tick.label}
|
|
116
|
+
</SvgText>
|
|
117
|
+
))}
|
|
118
|
+
</G>
|
|
119
|
+
);
|
|
120
|
+
}
|