@chipmobilesdk/rn-chart 0.1.1 → 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 -74
- package/README.md +337 -314
- package/package.json +46 -49
- package/skills/sdk-rn-chart/SKILL.md +1 -2
- package/src/a11y/summary.ts +170 -177
- package/src/contract/datasource.ts +126 -134
- 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 -139
- package/src/contract/value.ts +121 -125
- package/src/geometry/bar.ts +271 -256
- package/src/geometry/distribution.ts +197 -200
- package/src/geometry/heatmap.ts +189 -195
- package/src/geometry/hitTest.ts +117 -120
- package/src/geometry/layout.ts +162 -121
- package/src/geometry/markers.ts +55 -0
- package/src/geometry/path.ts +193 -199
- package/src/geometry/progress.ts +223 -204
- package/src/geometry/scale.ts +140 -146
- package/src/geometry/ticks.ts +145 -153
- package/src/geometry/timeSeries.ts +168 -158
- package/src/geometry/types.ts +94 -88
- package/src/index.ts +158 -176
- package/src/react/ChartBoundary.tsx +96 -96
- package/src/react/ChartFrame.tsx +583 -620
- package/src/react/ChartLegend.tsx +211 -177
- 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
package/src/react/charts.tsx
CHANGED
|
@@ -1,161 +1,154 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The five chart family components.
|
|
3
|
-
*
|
|
4
|
-
* Each is a thin wrapper over `ChartFrame` plus its own geometry builder, so
|
|
5
|
-
* validation, state handling, theming, selection, accessibility, and failure
|
|
6
|
-
* containment behave identically across every family.
|
|
7
|
-
*
|
|
8
|
-
* All five are wrapped in `React.memo`: value-identical props stop at the memo
|
|
9
|
-
* boundary and never reach geometry (FR-073, SC-017).
|
|
10
|
-
*/
|
|
11
|
-
import React, { useCallback } from 'react';
|
|
12
|
-
|
|
13
|
-
import type { ChartDatasource } from '../contract/datasource';
|
|
14
|
-
import type { BaseChartProps } from '../contract/props';
|
|
15
|
-
import { buildBarGeometry, type BarGrouping } from '../geometry/bar';
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
} from '../geometry/
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
{
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
export const BarChart = React.memo(BarChartImpl) as <M = unknown>(
|
|
156
|
-
props: BarChartProps<M>,
|
|
157
|
-
) => React.ReactElement;
|
|
158
|
-
|
|
159
|
-
export const HeatmapChart = React.memo(HeatmapChartImpl) as <M = unknown>(
|
|
160
|
-
props: HeatmapChartProps<M>,
|
|
161
|
-
) => React.ReactElement;
|
|
1
|
+
/**
|
|
2
|
+
* The five chart family components.
|
|
3
|
+
*
|
|
4
|
+
* Each is a thin wrapper over `ChartFrame` plus its own geometry builder, so
|
|
5
|
+
* validation, state handling, theming, selection, accessibility, and failure
|
|
6
|
+
* containment behave identically across every family.
|
|
7
|
+
*
|
|
8
|
+
* All five are wrapped in `React.memo`: value-identical props stop at the memo
|
|
9
|
+
* boundary and never reach geometry (FR-073, SC-017).
|
|
10
|
+
*/
|
|
11
|
+
import React, { useCallback } from 'react';
|
|
12
|
+
|
|
13
|
+
import type { ChartDatasource } from '../contract/datasource';
|
|
14
|
+
import type { BaseChartProps } from '../contract/props';
|
|
15
|
+
import { buildBarGeometry, type BarGrouping } from '../geometry/bar';
|
|
16
|
+
import { buildDistributionGeometry, type DistributionShape } from '../geometry/distribution';
|
|
17
|
+
import { buildHeatmapGeometry } from '../geometry/heatmap';
|
|
18
|
+
import { buildProgressGeometry } from '../geometry/progress';
|
|
19
|
+
import { buildTimeSeriesGeometry } from '../geometry/timeSeries';
|
|
20
|
+
import type { GeometryInput } from '../geometry/types';
|
|
21
|
+
import { ChartFrame } from './ChartFrame';
|
|
22
|
+
|
|
23
|
+
export interface ProgressChartProps<M = unknown> extends BaseChartProps<M> {
|
|
24
|
+
readonly orientation?: 'horizontal' | 'vertical';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function ProgressChartImpl<M>(props: ProgressChartProps<M>) {
|
|
28
|
+
const build = useCallback(
|
|
29
|
+
(ds: ChartDatasource<M>, input: GeometryInput) =>
|
|
30
|
+
buildProgressGeometry(ds, input, { orientation: props.orientation }),
|
|
31
|
+
[props.orientation],
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<ChartFrame
|
|
36
|
+
{...props}
|
|
37
|
+
family="progress"
|
|
38
|
+
buildGeometry={build}
|
|
39
|
+
showAxes={false}
|
|
40
|
+
defaultLegend={false}
|
|
41
|
+
/>
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface DistributionChartProps<M = unknown> extends BaseChartProps<M> {
|
|
46
|
+
readonly shape?: DistributionShape;
|
|
47
|
+
readonly innerRadiusRatio?: number;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function DistributionChartImpl<M>(props: DistributionChartProps<M>) {
|
|
51
|
+
const build = useCallback(
|
|
52
|
+
(ds: ChartDatasource<M>, input: GeometryInput) =>
|
|
53
|
+
buildDistributionGeometry(ds, input, {
|
|
54
|
+
shape: props.shape,
|
|
55
|
+
innerRadiusRatio: props.innerRadiusRatio,
|
|
56
|
+
}),
|
|
57
|
+
[props.shape, props.innerRadiusRatio],
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<ChartFrame
|
|
62
|
+
{...props}
|
|
63
|
+
family="distribution"
|
|
64
|
+
buildGeometry={build}
|
|
65
|
+
showAxes={false}
|
|
66
|
+
defaultLegend
|
|
67
|
+
/>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface TimeSeriesChartProps<M = unknown> extends BaseChartProps<M> {
|
|
72
|
+
/** A window the app supplies. Users cannot pan or zoom (out of scope). */
|
|
73
|
+
readonly range?: { readonly from: number; readonly to: number };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function TimeSeriesChartImpl<M>(props: TimeSeriesChartProps<M>) {
|
|
77
|
+
const build = useCallback(
|
|
78
|
+
(ds: ChartDatasource<M>, input: GeometryInput) =>
|
|
79
|
+
buildTimeSeriesGeometry(ds, input, { range: props.range }),
|
|
80
|
+
[props.range],
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
return (
|
|
84
|
+
<ChartFrame
|
|
85
|
+
{...props}
|
|
86
|
+
family="timeSeries"
|
|
87
|
+
buildGeometry={build}
|
|
88
|
+
// A user aims at a moment in time, not at a pixel on the line.
|
|
89
|
+
hitTestMode="byX"
|
|
90
|
+
defaultLegend
|
|
91
|
+
/>
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface BarChartProps<M = unknown> extends BaseChartProps<M> {
|
|
96
|
+
readonly grouping?: BarGrouping;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function BarChartImpl<M>(props: BarChartProps<M>) {
|
|
100
|
+
const build = useCallback(
|
|
101
|
+
(ds: ChartDatasource<M>, input: GeometryInput) =>
|
|
102
|
+
buildBarGeometry(ds, input, { grouping: props.grouping }),
|
|
103
|
+
[props.grouping],
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
return <ChartFrame {...props} family="bar" buildGeometry={build} defaultLegend />;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export interface HeatmapChartProps<M = unknown> extends BaseChartProps<M> {
|
|
110
|
+
readonly buckets?: number;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function HeatmapChartImpl<M>(props: HeatmapChartProps<M>) {
|
|
114
|
+
const build = useCallback(
|
|
115
|
+
(ds: ChartDatasource<M>, input: GeometryInput) =>
|
|
116
|
+
buildHeatmapGeometry(ds, input, { buckets: props.buckets }),
|
|
117
|
+
[props.buckets],
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
return (
|
|
121
|
+
<ChartFrame
|
|
122
|
+
{...props}
|
|
123
|
+
family="heatmap"
|
|
124
|
+
buildGeometry={build}
|
|
125
|
+
showAxes={false}
|
|
126
|
+
defaultLegend={false}
|
|
127
|
+
/>
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* `React.memo` erases the generic parameter, so each component is re-cast to a
|
|
133
|
+
* generic function type. This keeps `<BarChart<MyMeta> ... />` working for
|
|
134
|
+
* consumers while still getting the memo boundary.
|
|
135
|
+
*/
|
|
136
|
+
export const ProgressChart = React.memo(ProgressChartImpl) as <M = unknown>(
|
|
137
|
+
props: ProgressChartProps<M>,
|
|
138
|
+
) => React.ReactElement;
|
|
139
|
+
|
|
140
|
+
export const DistributionChart = React.memo(DistributionChartImpl) as <M = unknown>(
|
|
141
|
+
props: DistributionChartProps<M>,
|
|
142
|
+
) => React.ReactElement;
|
|
143
|
+
|
|
144
|
+
export const TimeSeriesChart = React.memo(TimeSeriesChartImpl) as <M = unknown>(
|
|
145
|
+
props: TimeSeriesChartProps<M>,
|
|
146
|
+
) => React.ReactElement;
|
|
147
|
+
|
|
148
|
+
export const BarChart = React.memo(BarChartImpl) as <M = unknown>(
|
|
149
|
+
props: BarChartProps<M>,
|
|
150
|
+
) => React.ReactElement;
|
|
151
|
+
|
|
152
|
+
export const HeatmapChart = React.memo(HeatmapChartImpl) as <M = unknown>(
|
|
153
|
+
props: HeatmapChartProps<M>,
|
|
154
|
+
) => React.ReactElement;
|
|
@@ -1,124 +1,124 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Container measurement and the geometry memo.
|
|
3
|
-
*
|
|
4
|
-
* `ResolvedGeometry` is memoized on datasource identity, plot rectangle, theme
|
|
5
|
-
* identity, and formatter identity — and deliberately **not** on selection, so
|
|
6
|
-
* highlighting a point repaints the highlight without recomputing scales or
|
|
7
|
-
* path strings (FR-073, SC-017).
|
|
8
|
-
*
|
|
9
|
-
* A consequence worth documenting for consumers: rebuilding the datasource
|
|
10
|
-
* object inline on every render defeats this. The README says so, and the demo
|
|
11
|
-
* app models the correct pattern.
|
|
12
|
-
*/
|
|
13
|
-
import { useCallback, useMemo, useState } from 'react';
|
|
14
|
-
import { PixelRatio, type LayoutChangeEvent } from 'react-native';
|
|
15
|
-
|
|
16
|
-
import type { ChartDatasource } from '../contract/datasource';
|
|
17
|
-
import { resolveSeriesColor, type ChartTheme } from '../contract/theme';
|
|
18
|
-
import {
|
|
19
|
-
EMPTY_PLOT,
|
|
20
|
-
isMeasurable,
|
|
21
|
-
resolvePlotRect,
|
|
22
|
-
type ContainerSize,
|
|
23
|
-
type PlotRect,
|
|
24
|
-
} from '../geometry/layout';
|
|
25
|
-
import { effectiveFontSize } from '../geometry/layout';
|
|
26
|
-
import type { GeometryInput } from '../geometry/types';
|
|
27
|
-
|
|
28
|
-
export interface ChartLayout {
|
|
29
|
-
readonly onLayout: (event: LayoutChangeEvent) => void;
|
|
30
|
-
readonly size: ContainerSize;
|
|
31
|
-
readonly measured: boolean;
|
|
32
|
-
readonly plot: PlotRect;
|
|
33
|
-
readonly fontSize: number;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function useChartLayout(
|
|
37
|
-
theme: ChartTheme,
|
|
38
|
-
options: {
|
|
39
|
-
readonly height?: number;
|
|
40
|
-
readonly xLabels?: readonly string[];
|
|
41
|
-
readonly yLabels?: readonly string[];
|
|
42
|
-
} = {},
|
|
43
|
-
): ChartLayout {
|
|
44
|
-
const [size, setSize] = useState<ContainerSize>({ width: 0, height: 0 });
|
|
45
|
-
|
|
46
|
-
const onLayout = useCallback((event: LayoutChangeEvent) => {
|
|
47
|
-
const { width, height } = event.nativeEvent.layout;
|
|
48
|
-
setSize(prev =>
|
|
49
|
-
// Avoid a state churn loop on sub-pixel layout jitter.
|
|
50
|
-
Math.abs(prev.width - width) < 0.5 && Math.abs(prev.height - height) < 0.5
|
|
51
|
-
? prev
|
|
52
|
-
: { width, height },
|
|
53
|
-
);
|
|
54
|
-
}, []);
|
|
55
|
-
|
|
56
|
-
const fontScale = PixelRatio.getFontScale();
|
|
57
|
-
const fontSize = effectiveFontSize(theme.typography.axisLabel.fontSize, fontScale);
|
|
58
|
-
|
|
59
|
-
const measured = isMeasurable(size);
|
|
60
|
-
|
|
61
|
-
const plot = useMemo(() => {
|
|
62
|
-
if (!measured) {
|
|
63
|
-
// Wait for a real measurement rather than computing a broken layout
|
|
64
|
-
// or throwing (FR-078).
|
|
65
|
-
return EMPTY_PLOT;
|
|
66
|
-
}
|
|
67
|
-
return resolvePlotRect({
|
|
68
|
-
container: size,
|
|
69
|
-
xLabels: options.xLabels,
|
|
70
|
-
yLabels: options.yLabels,
|
|
71
|
-
baseFontSize: theme.typography.axisLabel.fontSize,
|
|
72
|
-
fontScale,
|
|
73
|
-
padding: theme.spacing.xs,
|
|
74
|
-
});
|
|
75
|
-
}, [
|
|
76
|
-
measured,
|
|
77
|
-
size,
|
|
78
|
-
options.xLabels,
|
|
79
|
-
options.yLabels,
|
|
80
|
-
theme.typography.axisLabel.fontSize,
|
|
81
|
-
theme.spacing.xs,
|
|
82
|
-
fontScale,
|
|
83
|
-
]);
|
|
84
|
-
|
|
85
|
-
return { onLayout, size, measured, plot, fontSize };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Builds the geometry input, resolving per-series colours once so geometry
|
|
90
|
-
* modules never reach into the theme themselves.
|
|
91
|
-
*/
|
|
92
|
-
export function useGeometryInput<M>(
|
|
93
|
-
datasource: ChartDatasource<M>,
|
|
94
|
-
theme: ChartTheme,
|
|
95
|
-
layout: ChartLayout,
|
|
96
|
-
formatters: {
|
|
97
|
-
readonly formatValue: (value: number) => string;
|
|
98
|
-
readonly formatCategory: (raw: string) => string;
|
|
99
|
-
readonly formatInstant: (at: number) => string;
|
|
100
|
-
},
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
nonValueTextures: theme.nonValue,
|
|
118
|
-
heatmapRamp: theme.heatmapRamp,
|
|
119
|
-
formatValue: formatters.formatValue,
|
|
120
|
-
formatCategory: formatters.formatCategory,
|
|
121
|
-
formatInstant: formatters.formatInstant,
|
|
122
|
-
};
|
|
123
|
-
}, [datasource, theme, layout.plot, layout.fontSize, formatters]);
|
|
124
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Container measurement and the geometry memo.
|
|
3
|
+
*
|
|
4
|
+
* `ResolvedGeometry` is memoized on datasource identity, plot rectangle, theme
|
|
5
|
+
* identity, and formatter identity — and deliberately **not** on selection, so
|
|
6
|
+
* highlighting a point repaints the highlight without recomputing scales or
|
|
7
|
+
* path strings (FR-073, SC-017).
|
|
8
|
+
*
|
|
9
|
+
* A consequence worth documenting for consumers: rebuilding the datasource
|
|
10
|
+
* object inline on every render defeats this. The README says so, and the demo
|
|
11
|
+
* app models the correct pattern.
|
|
12
|
+
*/
|
|
13
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
14
|
+
import { PixelRatio, type LayoutChangeEvent } from 'react-native';
|
|
15
|
+
|
|
16
|
+
import type { ChartDatasource, ChartFamily } from '../contract/datasource';
|
|
17
|
+
import { resolveSegmentColors, resolveSeriesColor, type ChartTheme } from '../contract/theme';
|
|
18
|
+
import {
|
|
19
|
+
EMPTY_PLOT,
|
|
20
|
+
isMeasurable,
|
|
21
|
+
resolvePlotRect,
|
|
22
|
+
type ContainerSize,
|
|
23
|
+
type PlotRect,
|
|
24
|
+
} from '../geometry/layout';
|
|
25
|
+
import { effectiveFontSize } from '../geometry/layout';
|
|
26
|
+
import type { GeometryInput } from '../geometry/types';
|
|
27
|
+
|
|
28
|
+
export interface ChartLayout {
|
|
29
|
+
readonly onLayout: (event: LayoutChangeEvent) => void;
|
|
30
|
+
readonly size: ContainerSize;
|
|
31
|
+
readonly measured: boolean;
|
|
32
|
+
readonly plot: PlotRect;
|
|
33
|
+
readonly fontSize: number;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function useChartLayout(
|
|
37
|
+
theme: ChartTheme,
|
|
38
|
+
options: {
|
|
39
|
+
readonly height?: number;
|
|
40
|
+
readonly xLabels?: readonly string[];
|
|
41
|
+
readonly yLabels?: readonly string[];
|
|
42
|
+
} = {},
|
|
43
|
+
): ChartLayout {
|
|
44
|
+
const [size, setSize] = useState<ContainerSize>({ width: 0, height: 0 });
|
|
45
|
+
|
|
46
|
+
const onLayout = useCallback((event: LayoutChangeEvent) => {
|
|
47
|
+
const { width, height } = event.nativeEvent.layout;
|
|
48
|
+
setSize(prev =>
|
|
49
|
+
// Avoid a state churn loop on sub-pixel layout jitter.
|
|
50
|
+
Math.abs(prev.width - width) < 0.5 && Math.abs(prev.height - height) < 0.5
|
|
51
|
+
? prev
|
|
52
|
+
: { width, height },
|
|
53
|
+
);
|
|
54
|
+
}, []);
|
|
55
|
+
|
|
56
|
+
const fontScale = PixelRatio.getFontScale();
|
|
57
|
+
const fontSize = effectiveFontSize(theme.typography.axisLabel.fontSize, fontScale);
|
|
58
|
+
|
|
59
|
+
const measured = isMeasurable(size);
|
|
60
|
+
|
|
61
|
+
const plot = useMemo(() => {
|
|
62
|
+
if (!measured) {
|
|
63
|
+
// Wait for a real measurement rather than computing a broken layout
|
|
64
|
+
// or throwing (FR-078).
|
|
65
|
+
return EMPTY_PLOT;
|
|
66
|
+
}
|
|
67
|
+
return resolvePlotRect({
|
|
68
|
+
container: size,
|
|
69
|
+
xLabels: options.xLabels,
|
|
70
|
+
yLabels: options.yLabels,
|
|
71
|
+
baseFontSize: theme.typography.axisLabel.fontSize,
|
|
72
|
+
fontScale,
|
|
73
|
+
padding: theme.spacing.xs,
|
|
74
|
+
});
|
|
75
|
+
}, [
|
|
76
|
+
measured,
|
|
77
|
+
size,
|
|
78
|
+
options.xLabels,
|
|
79
|
+
options.yLabels,
|
|
80
|
+
theme.typography.axisLabel.fontSize,
|
|
81
|
+
theme.spacing.xs,
|
|
82
|
+
fontScale,
|
|
83
|
+
]);
|
|
84
|
+
|
|
85
|
+
return { onLayout, size, measured, plot, fontSize };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Builds the geometry input, resolving per-series colours once so geometry
|
|
90
|
+
* modules never reach into the theme themselves.
|
|
91
|
+
*/
|
|
92
|
+
export function useGeometryInput<M>(
|
|
93
|
+
datasource: ChartDatasource<M>,
|
|
94
|
+
theme: ChartTheme,
|
|
95
|
+
layout: ChartLayout,
|
|
96
|
+
formatters: {
|
|
97
|
+
readonly formatValue: (value: number) => string;
|
|
98
|
+
readonly formatCategory: (raw: string) => string;
|
|
99
|
+
readonly formatInstant: (at: number) => string;
|
|
100
|
+
},
|
|
101
|
+
family?: ChartFamily,
|
|
102
|
+
): GeometryInput {
|
|
103
|
+
return useMemo(() => {
|
|
104
|
+
const seriesColors: Record<string, string> = {};
|
|
105
|
+
datasource.series.forEach((series, index) => {
|
|
106
|
+
seriesColors[series.id] = resolveSeriesColor(theme, index, series.color);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
plot: layout.plot,
|
|
111
|
+
fontSize: layout.fontSize,
|
|
112
|
+
seriesColors,
|
|
113
|
+
// Per point only where segments exist; resolving one per point for
|
|
114
|
+
// every family cost a colour per data point at the time-series budget.
|
|
115
|
+
segmentColors:
|
|
116
|
+
family === 'distribution' ? resolveSegmentColors(datasource, theme) : undefined,
|
|
117
|
+
nonValueTextures: theme.nonValue,
|
|
118
|
+
heatmapRamp: theme.heatmapRamp,
|
|
119
|
+
formatValue: formatters.formatValue,
|
|
120
|
+
formatCategory: formatters.formatCategory,
|
|
121
|
+
formatInstant: formatters.formatInstant,
|
|
122
|
+
};
|
|
123
|
+
}, [datasource, theme, layout.plot, layout.fontSize, formatters, family]);
|
|
124
|
+
}
|