minerva-design 1.1.0 → 1.2.0

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.
Files changed (249) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/charts/canvas.d.cts +7 -0
  3. package/dist/charts/canvas.d.ts +7 -0
  4. package/dist/charts/core.cjs +1 -1
  5. package/dist/charts/core.d.cts +5 -1
  6. package/dist/charts/core.d.ts +5 -1
  7. package/dist/charts/core.js +4 -1
  8. package/dist/charts/engine/canvas.cjs +2 -0
  9. package/dist/charts/engine/canvas.cjs.map +1 -0
  10. package/dist/charts/engine/canvas.d.cts +85 -0
  11. package/dist/charts/engine/canvas.d.ts +85 -0
  12. package/dist/charts/engine/canvas.js +165 -0
  13. package/dist/charts/engine/canvas.js.map +1 -0
  14. package/dist/charts/engine/chart.cjs +2 -2
  15. package/dist/charts/engine/chart.cjs.map +1 -1
  16. package/dist/charts/engine/chart.js +382 -440
  17. package/dist/charts/engine/chart.js.map +1 -1
  18. package/dist/charts/engine/color.cjs +1 -1
  19. package/dist/charts/engine/color.cjs.map +1 -1
  20. package/dist/charts/engine/color.d.cts +10 -0
  21. package/dist/charts/engine/color.d.ts +10 -0
  22. package/dist/charts/engine/color.js +75 -3
  23. package/dist/charts/engine/color.js.map +1 -1
  24. package/dist/charts/engine/controller.cjs +3 -0
  25. package/dist/charts/engine/controller.cjs.map +1 -0
  26. package/dist/charts/engine/controller.d.cts +182 -0
  27. package/dist/charts/engine/controller.d.ts +182 -0
  28. package/dist/charts/engine/controller.js +528 -0
  29. package/dist/charts/engine/controller.js.map +1 -0
  30. package/dist/charts/engine/model.cjs +1 -1
  31. package/dist/charts/engine/model.cjs.map +1 -1
  32. package/dist/charts/engine/model.d.cts +2 -2
  33. package/dist/charts/engine/model.d.ts +2 -2
  34. package/dist/charts/engine/model.js +72 -72
  35. package/dist/charts/engine/model.js.map +1 -1
  36. package/dist/charts/engine/overlay.cjs +1 -1
  37. package/dist/charts/engine/overlay.cjs.map +1 -1
  38. package/dist/charts/engine/overlay.d.cts +17 -1
  39. package/dist/charts/engine/overlay.d.ts +17 -1
  40. package/dist/charts/engine/overlay.js +63 -49
  41. package/dist/charts/engine/overlay.js.map +1 -1
  42. package/dist/charts/engine/presets.cjs +3 -0
  43. package/dist/charts/engine/presets.cjs.map +1 -0
  44. package/dist/charts/engine/presets.d.cts +103 -0
  45. package/dist/charts/engine/presets.d.ts +103 -0
  46. package/dist/charts/engine/presets.js +153 -0
  47. package/dist/charts/engine/presets.js.map +1 -0
  48. package/dist/charts/engine/renderers/canvas2d.cjs +1 -1
  49. package/dist/charts/engine/renderers/canvas2d.cjs.map +1 -1
  50. package/dist/charts/engine/renderers/canvas2d.d.cts +17 -1
  51. package/dist/charts/engine/renderers/canvas2d.d.ts +17 -1
  52. package/dist/charts/engine/renderers/canvas2d.js +88 -51
  53. package/dist/charts/engine/renderers/canvas2d.js.map +1 -1
  54. package/dist/charts/engine/scene.cjs +2 -0
  55. package/dist/charts/engine/scene.cjs.map +1 -0
  56. package/dist/charts/engine/scene.d.cts +89 -0
  57. package/dist/charts/engine/scene.d.ts +89 -0
  58. package/dist/charts/engine/scene.js +241 -0
  59. package/dist/charts/engine/scene.js.map +1 -0
  60. package/dist/charts/engine/theme.cjs +1 -1
  61. package/dist/charts/engine/theme.cjs.map +1 -1
  62. package/dist/charts/engine/theme.d.cts +25 -1
  63. package/dist/charts/engine/theme.d.ts +25 -1
  64. package/dist/charts/engine/theme.js +42 -26
  65. package/dist/charts/engine/theme.js.map +1 -1
  66. package/dist/charts/engine/types.d.cts +69 -2
  67. package/dist/charts/engine/types.d.ts +69 -2
  68. package/dist/charts/index.cjs +1 -1
  69. package/dist/charts/index.d.cts +1 -1
  70. package/dist/charts/index.d.ts +1 -1
  71. package/dist/charts/index.js +5 -2
  72. package/dist/charts/react/Chart.cjs +1 -1
  73. package/dist/charts/react/Chart.cjs.map +1 -1
  74. package/dist/charts/react/Chart.d.cts +11 -4
  75. package/dist/charts/react/Chart.d.ts +11 -4
  76. package/dist/charts/react/Chart.js +81 -39
  77. package/dist/charts/react/Chart.js.map +1 -1
  78. package/dist/charts/react/charts.cjs +1 -1
  79. package/dist/charts/react/charts.cjs.map +1 -1
  80. package/dist/charts/react/charts.d.cts +1 -1
  81. package/dist/charts/react/charts.d.ts +1 -1
  82. package/dist/charts/react/charts.js +99 -145
  83. package/dist/charts/react/charts.js.map +1 -1
  84. package/dist/charts/react/types.d.cts +29 -61
  85. package/dist/charts/react/types.d.ts +29 -61
  86. package/dist/native/charts/src/canvas.cjs +1 -0
  87. package/dist/native/charts/src/canvas.js +6 -0
  88. package/dist/native/charts/src/engine/axis.cjs +2 -0
  89. package/dist/native/charts/src/engine/axis.cjs.map +1 -0
  90. package/dist/native/charts/src/engine/axis.js +38 -0
  91. package/dist/native/charts/src/engine/axis.js.map +1 -0
  92. package/dist/native/charts/src/engine/canvas.cjs +1 -0
  93. package/dist/native/charts/src/engine/canvas.js +1 -0
  94. package/dist/native/charts/src/engine/color.cjs +2 -0
  95. package/dist/native/charts/src/engine/color.cjs.map +1 -0
  96. package/dist/native/charts/src/engine/color.js +130 -0
  97. package/dist/native/charts/src/engine/color.js.map +1 -0
  98. package/dist/native/charts/src/engine/controller.cjs +3 -0
  99. package/dist/native/charts/src/engine/controller.cjs.map +1 -0
  100. package/dist/native/charts/src/engine/controller.js +528 -0
  101. package/dist/native/charts/src/engine/controller.js.map +1 -0
  102. package/dist/native/charts/src/engine/data.cjs +2 -0
  103. package/dist/native/charts/src/engine/data.cjs.map +1 -0
  104. package/dist/native/charts/src/engine/data.js +104 -0
  105. package/dist/native/charts/src/engine/data.js.map +1 -0
  106. package/dist/native/charts/src/engine/decimate.cjs +2 -0
  107. package/dist/native/charts/src/engine/decimate.cjs.map +1 -0
  108. package/dist/native/charts/src/engine/decimate.js +28 -0
  109. package/dist/native/charts/src/engine/decimate.js.map +1 -0
  110. package/dist/native/charts/src/engine/format.cjs +2 -0
  111. package/dist/native/charts/src/engine/format.cjs.map +1 -0
  112. package/dist/native/charts/src/engine/format.js +34 -0
  113. package/dist/native/charts/src/engine/format.js.map +1 -0
  114. package/dist/native/charts/src/engine/layout.cjs +2 -0
  115. package/dist/native/charts/src/engine/layout.cjs.map +1 -0
  116. package/dist/native/charts/src/engine/layout.js +26 -0
  117. package/dist/native/charts/src/engine/layout.js.map +1 -0
  118. package/dist/native/charts/src/engine/model.cjs +2 -0
  119. package/dist/native/charts/src/engine/model.cjs.map +1 -0
  120. package/dist/native/charts/src/engine/model.js +175 -0
  121. package/dist/native/charts/src/engine/model.js.map +1 -0
  122. package/dist/native/charts/src/engine/overlay.cjs +2 -0
  123. package/dist/native/charts/src/engine/overlay.cjs.map +1 -0
  124. package/dist/native/charts/src/engine/overlay.js +49 -0
  125. package/dist/native/charts/src/engine/overlay.js.map +1 -0
  126. package/dist/native/charts/src/engine/presets.cjs +3 -0
  127. package/dist/native/charts/src/engine/presets.cjs.map +1 -0
  128. package/dist/native/charts/src/engine/presets.js +147 -0
  129. package/dist/native/charts/src/engine/presets.js.map +1 -0
  130. package/dist/native/charts/src/engine/scale.cjs +2 -0
  131. package/dist/native/charts/src/engine/scale.cjs.map +1 -0
  132. package/dist/native/charts/src/engine/scale.js +37 -0
  133. package/dist/native/charts/src/engine/scale.js.map +1 -0
  134. package/dist/native/charts/src/engine/scene.cjs +2 -0
  135. package/dist/native/charts/src/engine/scene.cjs.map +1 -0
  136. package/dist/native/charts/src/engine/scene.js +241 -0
  137. package/dist/native/charts/src/engine/scene.js.map +1 -0
  138. package/dist/native/charts/src/engine/spatial.cjs +2 -0
  139. package/dist/native/charts/src/engine/spatial.cjs.map +1 -0
  140. package/dist/native/charts/src/engine/spatial.js +53 -0
  141. package/dist/native/charts/src/engine/spatial.js.map +1 -0
  142. package/dist/native/charts/src/engine/theme.cjs +2 -0
  143. package/dist/native/charts/src/engine/theme.cjs.map +1 -0
  144. package/dist/native/charts/src/engine/theme.js +62 -0
  145. package/dist/native/charts/src/engine/theme.js.map +1 -0
  146. package/dist/native/charts/src/engine/time.cjs +2 -0
  147. package/dist/native/charts/src/engine/time.cjs.map +1 -0
  148. package/dist/native/charts/src/engine/time.js +189 -0
  149. package/dist/native/charts/src/engine/time.js.map +1 -0
  150. package/dist/native/charts/src/engine/viewport.cjs +2 -0
  151. package/dist/native/charts/src/engine/viewport.cjs.map +1 -0
  152. package/dist/native/charts/src/engine/viewport.js +12 -0
  153. package/dist/native/charts/src/engine/viewport.js.map +1 -0
  154. package/dist/native/components/Chart/ChartBase.cjs +2 -0
  155. package/dist/native/components/Chart/ChartBase.cjs.map +1 -0
  156. package/dist/native/components/Chart/ChartBase.d.cts +29 -0
  157. package/dist/native/components/Chart/ChartBase.d.ts +29 -0
  158. package/dist/native/components/Chart/ChartBase.js +287 -0
  159. package/dist/native/components/Chart/ChartBase.js.map +1 -0
  160. package/dist/native/components/Chart/charts.cjs +2 -0
  161. package/dist/native/components/Chart/charts.cjs.map +1 -0
  162. package/dist/native/components/Chart/charts.d.cts +19 -0
  163. package/dist/native/components/Chart/charts.d.ts +19 -0
  164. package/dist/native/components/Chart/charts.js +173 -0
  165. package/dist/native/components/Chart/charts.js.map +1 -0
  166. package/dist/native/components/Chart/index.cjs +1 -0
  167. package/dist/native/components/Chart/index.d.cts +2 -0
  168. package/dist/native/components/Chart/index.d.ts +2 -0
  169. package/dist/native/components/Chart/index.js +2 -0
  170. package/dist/native/components/Chart/measure.cjs +2 -0
  171. package/dist/native/components/Chart/measure.cjs.map +1 -0
  172. package/dist/native/components/Chart/measure.d.cts +5 -0
  173. package/dist/native/components/Chart/measure.d.ts +5 -0
  174. package/dist/native/components/Chart/measure.js +11 -0
  175. package/dist/native/components/Chart/measure.js.map +1 -0
  176. package/dist/native/components/Chart/scene.cjs +2 -0
  177. package/dist/native/components/Chart/scene.cjs.map +1 -0
  178. package/dist/native/components/Chart/scene.d.cts +41 -0
  179. package/dist/native/components/Chart/scene.d.ts +41 -0
  180. package/dist/native/components/Chart/scene.js +419 -0
  181. package/dist/native/components/Chart/scene.js.map +1 -0
  182. package/dist/native/components/Chart/theme.cjs +2 -0
  183. package/dist/native/components/Chart/theme.cjs.map +1 -0
  184. package/dist/native/components/Chart/theme.d.cts +10 -0
  185. package/dist/native/components/Chart/theme.d.ts +10 -0
  186. package/dist/native/components/Chart/theme.js +10 -0
  187. package/dist/native/components/Chart/theme.js.map +1 -0
  188. package/dist/native/components/Chart/types.d.cts +129 -0
  189. package/dist/native/components/Chart/types.d.ts +129 -0
  190. package/dist/native/index.cjs +1 -1
  191. package/dist/native/index.d.cts +1 -0
  192. package/dist/native/index.d.ts +1 -0
  193. package/dist/native/index.js +6 -4
  194. package/dist/native/manifest.d.cts +7 -0
  195. package/dist/native/manifest.d.ts +7 -0
  196. package/dist/native/source/components/Chart/ChartBase.tsx +545 -0
  197. package/dist/native/source/components/Chart/charts.tsx +291 -0
  198. package/dist/native/source/components/Chart/index.ts +8 -0
  199. package/dist/native/source/components/Chart/measure.ts +50 -0
  200. package/dist/native/source/components/Chart/scene.tsx +987 -0
  201. package/dist/native/source/components/Chart/theme.ts +16 -0
  202. package/dist/native/source/components/Chart/types.ts +194 -0
  203. package/dist/native/source/index.ts +1 -0
  204. package/dist/native/source/manifest.ts +14 -0
  205. package/dist/taro/charts.d.ts +144 -0
  206. package/dist/taro/index.d.ts +1 -0
  207. package/dist/taro/index.js +3064 -618
  208. package/dist/uni/BarChart.vue +75 -0
  209. package/dist/uni/BarChart.vue.d.ts +111 -0
  210. package/dist/uni/Chart.vue +68 -0
  211. package/dist/uni/Chart.vue.d.ts +100 -0
  212. package/dist/uni/LineChart.vue +75 -0
  213. package/dist/uni/LineChart.vue.d.ts +111 -0
  214. package/dist/uni/ProgressIndicator.vue.d.ts +1 -1
  215. package/dist/uni/QuadrantChart.vue +184 -0
  216. package/dist/uni/QuadrantChart.vue.d.ts +173 -0
  217. package/dist/uni/ScatterChart.vue +69 -0
  218. package/dist/uni/ScatterChart.vue.d.ts +100 -0
  219. package/dist/uni/chart-canvas.d.ts +68 -0
  220. package/dist/uni/chart-canvas.js +363 -0
  221. package/dist/uni/chart-types.d.ts +135 -0
  222. package/dist/uni/chart-types.js +84 -0
  223. package/dist/uni/charts-canvas.d.ts +1 -0
  224. package/dist/uni/charts-canvas.js +8 -0
  225. package/dist/uni/index.d.ts +6 -0
  226. package/dist/uni/index.js +6 -0
  227. package/dist/weapp/bar-chart/index.js +1 -0
  228. package/dist/weapp/bar-chart/index.json +1 -0
  229. package/dist/weapp/bar-chart/index.wxml +1 -0
  230. package/dist/weapp/bar-chart/index.wxss +1 -0
  231. package/dist/weapp/chart/index.js +1 -0
  232. package/dist/weapp/chart/index.json +1 -0
  233. package/dist/weapp/chart/index.wxml +1 -0
  234. package/dist/weapp/chart/index.wxss +1 -0
  235. package/dist/weapp/controls.js +4019 -266
  236. package/dist/weapp/line-chart/index.js +1 -0
  237. package/dist/weapp/line-chart/index.json +1 -0
  238. package/dist/weapp/line-chart/index.wxml +1 -0
  239. package/dist/weapp/line-chart/index.wxss +1 -0
  240. package/dist/weapp/quadrant-chart/index.js +1 -0
  241. package/dist/weapp/quadrant-chart/index.json +1 -0
  242. package/dist/weapp/quadrant-chart/index.wxml +1 -0
  243. package/dist/weapp/quadrant-chart/index.wxss +1 -0
  244. package/dist/weapp/scatter-chart/index.js +1 -0
  245. package/dist/weapp/scatter-chart/index.json +1 -0
  246. package/dist/weapp/scatter-chart/index.wxml +1 -0
  247. package/dist/weapp/scatter-chart/index.wxss +1 -0
  248. package/dist/weapp/types.d.ts +295 -0
  249. package/package.json +21 -1
@@ -0,0 +1,545 @@
1
+ // The chart host of React Native: owns a platform-free chart controller
2
+ // (@minerva/charts/canvas: options, model, zoom, gestures, scene in pixels)
3
+ // and draws its scene with views (./scene). Touches go through the
4
+ // responder system; the drawing is `pointerEvents="none"`, so every touch
5
+ // lands on the surface and positions come from page coordinates.
6
+
7
+ import {
8
+ useEffect,
9
+ useImperativeHandle,
10
+ useLayoutEffect,
11
+ useMemo,
12
+ useRef,
13
+ useState,
14
+ useSyncExternalStore,
15
+ type ReactNode,
16
+ type Ref,
17
+ } from "react";
18
+ import {
19
+ StyleSheet,
20
+ View,
21
+ type DimensionValue,
22
+ type GestureResponderEvent,
23
+ type LayoutChangeEvent,
24
+ type StyleProp,
25
+ type ViewStyle,
26
+ } from "react-native";
27
+ import {
28
+ createChartController,
29
+ type ChartController,
30
+ type ChartDragEvent,
31
+ type ChartHoverEvent,
32
+ type ChartOptions,
33
+ type ChartPlotClickEvent,
34
+ type ChartRect,
35
+ type ChartScene,
36
+ type ChartThemeInput,
37
+ type ChartTooltipOptions,
38
+ type ChartView,
39
+ } from "../../../charts/src/canvas.js";
40
+ import { useTheme } from "../../theme/MinervaProvider";
41
+ import { part } from "../../internal/parts";
42
+ import { measureChartText } from "./measure";
43
+ import { drawHover, drawStatic, tooltipCard } from "./scene";
44
+ import { chartThemeInput } from "./theme";
45
+ import type { ChartCommonProps, ChartHandle } from "./types";
46
+
47
+ interface Handlers {
48
+ onHover?: (event: ChartHoverEvent | null) => void;
49
+ onPointClick?: (event: ChartHoverEvent) => void;
50
+ onPlotClick?: (event: ChartPlotClickEvent) => void;
51
+ onZoom?: (view: ChartView) => void;
52
+ onDrag?: (event: ChartDragEvent) => void;
53
+ onDrop?: (event: ChartDragEvent) => void;
54
+ }
55
+
56
+ /** Engine options of the common props */
57
+ export const engineOptions = (
58
+ props: Pick<
59
+ ChartCommonProps,
60
+ | "xAxis"
61
+ | "yAxis"
62
+ | "legend"
63
+ | "tooltip"
64
+ | "crosshair"
65
+ | "annotations"
66
+ | "interaction"
67
+ | "padding"
68
+ | "palette"
69
+ | "ariaLabel"
70
+ > &
71
+ Pick<ChartOptions, "series">,
72
+ ): ChartOptions => ({
73
+ series: props.series,
74
+ xAxis: props.xAxis,
75
+ yAxis: props.yAxis,
76
+ legend: props.legend,
77
+ tooltip: props.tooltip,
78
+ crosshair: props.crosshair,
79
+ annotations: props.annotations,
80
+ interaction: props.interaction,
81
+ padding: props.padding,
82
+ palette: props.palette,
83
+ ariaLabel: props.ariaLabel,
84
+ });
85
+
86
+ type TooltipResult = Exclude<
87
+ ReturnType<NonNullable<ChartTooltipOptions["render"]>>,
88
+ string | null
89
+ >;
90
+ /** Non-string result of `tooltip.render`: the engine lays a tooltip out without drawing text */
91
+ const CUSTOM_TOOLTIP = {} as unknown as TooltipResult;
92
+
93
+ interface TouchPoint {
94
+ identifier?: number | string;
95
+ pageX: number;
96
+ pageY: number;
97
+ locationX?: number;
98
+ locationY?: number;
99
+ }
100
+
101
+ const idOf = (t: TouchPoint, index: number) => {
102
+ const id = Number(t.identifier);
103
+ return Number.isFinite(id) ? id : index;
104
+ };
105
+
106
+ type Frame = { cancel: () => void };
107
+
108
+ const requestFrame = (callback: () => void): Frame => {
109
+ if (typeof requestAnimationFrame === "function") {
110
+ const id = requestAnimationFrame(callback);
111
+ return { cancel: () => cancelAnimationFrame(id) };
112
+ }
113
+ const id = setTimeout(callback, 16);
114
+ return { cancel: () => clearTimeout(id) };
115
+ };
116
+
117
+ /**
118
+ * External store of a chart: the controller, a version bumped when its
119
+ * scene must be drawn again (coalesced per frame), the touches being
120
+ * tracked and the cache of the static layers.
121
+ */
122
+ interface ChartStore {
123
+ controller: ChartController;
124
+ /** Options of the controller (`setOptions` of the controller) */
125
+ setOptions: (options: ChartOptions) => void;
126
+ subscribe: (listener: () => void) => () => void;
127
+ version: () => number;
128
+ /** Draws again now */
129
+ commit: () => void;
130
+ /** Static layers of a scene (cached while model / view / size are the same) */
131
+ layers: (scene: ChartScene, family: string | undefined) => ReactNode;
132
+ /** Last single-finger position (anchor of a custom tooltip) */
133
+ anchor: { x: number; y: number } | null;
134
+ /**
135
+ * First finger (responder grant); returns whether the chart owns the
136
+ * gesture, which keeps native scroll views from taking it (Android)
137
+ */
138
+ touchStart: (event: GestureResponderEvent) => boolean;
139
+ /** Another finger touches the chart */
140
+ touchAdd: (event: GestureResponderEvent) => void;
141
+ /** Moves (drawn on the next frame) */
142
+ touchMove: (event: GestureResponderEvent) => void;
143
+ touchEnd: (event: GestureResponderEvent) => void;
144
+ touchCancel: () => void;
145
+ /** Whether the chart keeps the touches (pinch, point drag, pan when zoomed) */
146
+ holdsGesture: () => boolean;
147
+ }
148
+
149
+ function createChartStore(
150
+ options: ChartOptions,
151
+ theme: ChartThemeInput,
152
+ size: [number, number] | null,
153
+ ): ChartStore {
154
+ let version = 0;
155
+ let frame: Frame | null = null;
156
+ const listeners = new Set<() => void>();
157
+ let cache: { key: unknown[]; node: ReactNode } | null = null;
158
+ let origin = { x: 0, y: 0 };
159
+ const touches = new Map<number, { x: number; y: number }>();
160
+ const dragsPoints = (o: ChartOptions) =>
161
+ typeof o.interaction === "object" && !!o.interaction.dragPoints;
162
+ let draggable = dragsPoints(options);
163
+ /** The current touch sequence started on the plot of a draggable chart */
164
+ let claimed = false;
165
+
166
+ const notify = () => {
167
+ version++;
168
+ listeners.forEach((listener) => listener());
169
+ };
170
+ const cancelFrame = () => {
171
+ frame?.cancel();
172
+ frame = null;
173
+ };
174
+ const schedule = () => {
175
+ if (frame || !listeners.size) return;
176
+ frame = requestFrame(() => {
177
+ frame = null;
178
+ notify();
179
+ });
180
+ };
181
+
182
+ const controller = createChartController({
183
+ options,
184
+ theme,
185
+ measureText: measureChartText,
186
+ onInvalidate: schedule,
187
+ });
188
+ if (size) controller.setSize(size[0], size[1]);
189
+
190
+ const local = (t: TouchPoint) => ({
191
+ x: t.pageX - origin.x,
192
+ y: t.pageY - origin.y,
193
+ });
194
+
195
+ /** Feeds the controller the fingers that appeared or moved */
196
+ const sync = (event: GestureResponderEvent) => {
197
+ const n = event.nativeEvent;
198
+ const list: readonly TouchPoint[] = n.touches?.length
199
+ ? n.touches
200
+ : [n as unknown as TouchPoint];
201
+ list.forEach((t, i) => {
202
+ const id = idOf(t, i);
203
+ const p = local(t);
204
+ const previous = touches.get(id);
205
+ if (!previous) {
206
+ touches.set(id, p);
207
+ controller.pointerDown(p.x, p.y, { id, touch: true });
208
+ } else if (previous.x !== p.x || previous.y !== p.y) {
209
+ touches.set(id, p);
210
+ controller.pointerMove(p.x, p.y, { id, touch: true });
211
+ }
212
+ });
213
+ if (touches.size === 1) store.anchor = [...touches.values()][0];
214
+ };
215
+
216
+ const store: ChartStore = {
217
+ controller,
218
+ setOptions(next) {
219
+ draggable = dragsPoints(next);
220
+ controller.setOptions(next);
221
+ },
222
+ subscribe(listener) {
223
+ listeners.add(listener);
224
+ return () => {
225
+ listeners.delete(listener);
226
+ if (!listeners.size) cancelFrame();
227
+ };
228
+ },
229
+ version: () => version,
230
+ commit() {
231
+ cancelFrame();
232
+ notify();
233
+ },
234
+ layers(scene, family) {
235
+ const { plot } = scene.layout;
236
+ const { view } = scene;
237
+ const key = [
238
+ scene.model,
239
+ scene.annotations,
240
+ scene.theme,
241
+ scene.width,
242
+ scene.height,
243
+ view.x[0],
244
+ view.x[1],
245
+ view.y[0],
246
+ view.y[1],
247
+ plot.x,
248
+ plot.y,
249
+ plot.width,
250
+ plot.height,
251
+ family,
252
+ ];
253
+ if (!cache || cache.key.some((value, i) => value !== key[i]))
254
+ cache = { key, node: drawStatic(scene, family) };
255
+ return cache.node;
256
+ },
257
+ anchor: null,
258
+ touchStart(event) {
259
+ const n = event.nativeEvent as unknown as TouchPoint;
260
+ // the drawing ignores touches: `location` is relative to the surface
261
+ if (!touches.size && n.locationX !== undefined)
262
+ origin = { x: n.pageX - n.locationX, y: n.pageY - (n.locationY ?? 0) };
263
+ sync(event);
264
+ store.commit();
265
+ // draggable points: the drag only starts after the touch slop, too
266
+ // late to keep a scroll view from stealing the touch: claim it now
267
+ const plot = controller.getPlotRect();
268
+ const p = touches.values().next().value;
269
+ claimed =
270
+ draggable &&
271
+ !!plot &&
272
+ !!p &&
273
+ p.x >= plot.x &&
274
+ p.x <= plot.x + plot.width &&
275
+ p.y >= plot.y &&
276
+ p.y <= plot.y + plot.height;
277
+ return store.holdsGesture();
278
+ },
279
+ touchAdd(event) {
280
+ sync(event);
281
+ store.commit();
282
+ },
283
+ touchMove: sync,
284
+ touchEnd(event) {
285
+ const n = event.nativeEvent;
286
+ const active = new Set(
287
+ (n.touches ?? []).map((t, i) => idOf(t as TouchPoint, i)),
288
+ );
289
+ const changed: readonly TouchPoint[] = n.changedTouches?.length
290
+ ? n.changedTouches
291
+ : [n as unknown as TouchPoint];
292
+ for (const [id, last] of [...touches]) {
293
+ if (active.has(id)) continue;
294
+ const touch = changed.find((t, i) => idOf(t, i) === id);
295
+ const p = touch ? local(touch) : last;
296
+ touches.delete(id);
297
+ if (touches.size === 0) store.anchor = p;
298
+ controller.pointerUp(p.x, p.y, { id, touch: true });
299
+ }
300
+ store.commit();
301
+ },
302
+ touchCancel() {
303
+ for (const id of touches.keys()) controller.pointerCancel({ id });
304
+ touches.clear();
305
+ store.commit();
306
+ },
307
+ holdsGesture() {
308
+ if (touches.size > 1 || (claimed && touches.size > 0)) return true;
309
+ const scene = controller.getScene();
310
+ if (!scene) return false;
311
+ if (scene.dragging) return true;
312
+ const { base } = scene.model;
313
+ const view = controller.getView();
314
+ return (
315
+ view.x[0] !== base.x[0] ||
316
+ view.x[1] !== base.x[1] ||
317
+ view.y[0] !== base.y[0] ||
318
+ view.y[1] !== base.y[1]
319
+ );
320
+ },
321
+ };
322
+ return store;
323
+ }
324
+
325
+ /** A `renderTooltip` node, placed next to the finger once measured */
326
+ function CustomTooltip({
327
+ scene,
328
+ anchor,
329
+ children,
330
+ }: {
331
+ scene: ChartScene;
332
+ anchor: { x: number; y: number };
333
+ children: ReactNode;
334
+ }) {
335
+ const [size, setSize] = useState<{ width: number; height: number } | null>(
336
+ null,
337
+ );
338
+ const { width, height } = scene;
339
+ let left = anchor.x + 14;
340
+ let top = anchor.y;
341
+ if (size) {
342
+ if (left + size.width > width - 4) left = anchor.x - 14 - size.width;
343
+ left = Math.max(4, Math.min(left, width - size.width - 4));
344
+ top = Math.max(
345
+ 4,
346
+ Math.min(anchor.y - size.height / 2, height - size.height - 4),
347
+ );
348
+ }
349
+ return (
350
+ <View
351
+ pointerEvents="none"
352
+ {...part("chart", "tooltip", { custom: true })}
353
+ onLayout={(e: LayoutChangeEvent) => {
354
+ const { width: w, height: h } = e.nativeEvent.layout;
355
+ setSize((previous) =>
356
+ previous && previous.width === w && previous.height === h
357
+ ? previous
358
+ : { width: w, height: h },
359
+ );
360
+ }}
361
+ style={[
362
+ tooltipCard(scene),
363
+ {
364
+ // laid out at the origin (full width available), moved by a transform
365
+ left: 0,
366
+ top: 0,
367
+ maxWidth: Math.max(0, width - 8),
368
+ padding: 8,
369
+ opacity: size ? 1 : 0,
370
+ transform: [{ translateX: left }, { translateY: top }],
371
+ },
372
+ ]}
373
+ >
374
+ {children}
375
+ </View>
376
+ );
377
+ }
378
+
379
+ interface ChartBaseProps extends Handlers {
380
+ options: ChartOptions;
381
+ height?: DimensionValue;
382
+ width?: DimensionValue;
383
+ style?: StyleProp<ViewStyle>;
384
+ testID?: string;
385
+ handleRef?: Ref<ChartHandle>;
386
+ /** React content of the tooltip (replaces the engine's content) */
387
+ renderTooltip?: (event: ChartHoverEvent) => ReactNode;
388
+ /** React content placed over the plot area, given the plot rectangle */
389
+ overlay?: (plot: ChartRect) => ReactNode;
390
+ }
391
+
392
+ /** Sizes the chart, forwards options / theme / touches to the controller and draws its scene */
393
+ export function ChartBase({
394
+ options,
395
+ height = 320,
396
+ width = "100%",
397
+ style,
398
+ testID,
399
+ handleRef,
400
+ renderTooltip,
401
+ overlay,
402
+ ...handlers
403
+ }: ChartBaseProps) {
404
+ const { tokens, fonts } = useTheme();
405
+ const family = fonts.sans;
406
+ const theme = useMemo(() => chartThemeInput(tokens), [tokens]);
407
+ const customTooltip = !!renderTooltip && options.tooltip !== false;
408
+ const resolved = useMemo((): ChartOptions => {
409
+ if (!customTooltip) return options;
410
+ const base = typeof options.tooltip === "object" ? options.tooltip : {};
411
+ return { ...options, tooltip: { ...base, render: () => CUSTOM_TOOLTIP } };
412
+ }, [options, customTooltip]);
413
+
414
+ const [store] = useState(() =>
415
+ createChartStore(
416
+ resolved,
417
+ theme,
418
+ typeof width === "number" && typeof height === "number"
419
+ ? [width, height]
420
+ : null,
421
+ ),
422
+ );
423
+ const { controller } = store;
424
+ useSyncExternalStore(store.subscribe, store.version, store.version);
425
+
426
+ const applied = useRef({ options: resolved, theme });
427
+ useLayoutEffect(() => {
428
+ const current = applied.current;
429
+ if (current.theme === theme && current.options === resolved) return;
430
+ if (current.theme !== theme) controller.setTheme(theme);
431
+ if (current.options !== resolved) store.setOptions(resolved);
432
+ applied.current = { options: resolved, theme };
433
+ store.commit();
434
+ }, [controller, store, resolved, theme]);
435
+
436
+ useLayoutEffect(() => {
437
+ if (typeof width !== "number" || typeof height !== "number") return;
438
+ const scene = controller.getScene();
439
+ if (scene && scene.width === width && scene.height === height) return;
440
+ controller.setSize(width, height);
441
+ store.commit();
442
+ }, [controller, store, width, height]);
443
+
444
+ const handlersRef = useRef<Handlers>(handlers);
445
+ useLayoutEffect(() => {
446
+ handlersRef.current = handlers;
447
+ });
448
+ useEffect(() => {
449
+ const h = handlersRef;
450
+ const offs = [
451
+ controller.on("hover", (e) => h.current.onHover?.(e)),
452
+ controller.on("click", (e) => h.current.onPointClick?.(e)),
453
+ controller.on("plotClick", (e) => h.current.onPlotClick?.(e)),
454
+ controller.on("zoom", (v) => h.current.onZoom?.(v)),
455
+ controller.on("drag", (e) => h.current.onDrag?.(e)),
456
+ controller.on("drop", (e) => h.current.onDrop?.(e)),
457
+ ];
458
+ return () => offs.forEach((off) => off());
459
+ }, [controller]);
460
+
461
+ useImperativeHandle(
462
+ handleRef,
463
+ () => ({
464
+ resetZoom: () => controller.resetZoom(),
465
+ getView: () => controller.getView(),
466
+ setView: (view) => controller.setView(view),
467
+ getPlotRect: () => controller.getPlotRect(),
468
+ getController: () => controller,
469
+ }),
470
+ [controller],
471
+ );
472
+
473
+ const onLayout = (e: LayoutChangeEvent) => {
474
+ const { width: w, height: h } = e.nativeEvent.layout;
475
+ const scene = controller.getScene();
476
+ if (scene && scene.width === w && scene.height === h) return;
477
+ controller.setSize(w, h);
478
+ store.commit();
479
+ };
480
+
481
+ const scene = controller.getScene();
482
+ let tooltip: ReactNode = null;
483
+ const event = scene?.hover?.tooltip ? scene.hover.event : null;
484
+ if (scene && event && customTooltip && renderTooltip) {
485
+ const content = renderTooltip(event);
486
+ const anchor = store.anchor ?? scene.hover?.marks[0] ?? null;
487
+ if (
488
+ content !== null &&
489
+ content !== undefined &&
490
+ content !== false &&
491
+ anchor
492
+ )
493
+ tooltip = (
494
+ <CustomTooltip scene={scene} anchor={anchor}>
495
+ {content}
496
+ </CustomTooltip>
497
+ );
498
+ }
499
+
500
+ return (
501
+ <View
502
+ testID={testID}
503
+ onLayout={onLayout}
504
+ style={[{ width, height }, style]}
505
+ {...part("chart", "root")}
506
+ >
507
+ <View
508
+ style={StyleSheet.absoluteFill}
509
+ accessible
510
+ accessibilityRole="image"
511
+ accessibilityLabel={options.ariaLabel}
512
+ onStartShouldSetResponder={() => true}
513
+ onResponderTerminationRequest={() => !store.holdsGesture()}
514
+ onResponderGrant={store.touchStart}
515
+ onResponderStart={store.touchAdd}
516
+ onResponderMove={store.touchMove}
517
+ onResponderEnd={store.touchEnd}
518
+ onResponderRelease={store.touchEnd}
519
+ onResponderTerminate={store.touchCancel}
520
+ {...part("chart", "surface")}
521
+ >
522
+ <View pointerEvents="none" style={StyleSheet.absoluteFill}>
523
+ {scene ? store.layers(scene, family) : null}
524
+ {scene ? drawHover(scene, family, customTooltip) : null}
525
+ </View>
526
+ </View>
527
+ {overlay && scene ? (
528
+ <View
529
+ pointerEvents="box-none"
530
+ style={{
531
+ position: "absolute",
532
+ left: scene.layout.plot.x,
533
+ top: scene.layout.plot.y,
534
+ width: scene.layout.plot.width,
535
+ height: scene.layout.plot.height,
536
+ }}
537
+ {...part("chart", "overlay")}
538
+ >
539
+ {overlay(scene.layout.plot)}
540
+ </View>
541
+ ) : null}
542
+ {tooltip}
543
+ </View>
544
+ );
545
+ }