@coinbase/cds-mobile-visualization 3.4.0-beta.5 → 3.4.0-beta.7
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 +16 -0
- package/dts/chart/CartesianChart.d.ts +57 -33
- package/dts/chart/CartesianChart.d.ts.map +1 -1
- package/dts/chart/ChartContextBridge.d.ts +28 -0
- package/dts/chart/ChartContextBridge.d.ts.map +1 -0
- package/dts/chart/Path.d.ts +77 -34
- package/dts/chart/Path.d.ts.map +1 -1
- package/dts/chart/PeriodSelector.d.ts +1 -1
- package/dts/chart/PeriodSelector.d.ts.map +1 -1
- package/dts/chart/area/Area.d.ts +42 -27
- package/dts/chart/area/Area.d.ts.map +1 -1
- package/dts/chart/area/AreaChart.d.ts +51 -10
- package/dts/chart/area/AreaChart.d.ts.map +1 -1
- package/dts/chart/area/DottedArea.d.ts +21 -2
- package/dts/chart/area/DottedArea.d.ts.map +1 -1
- package/dts/chart/area/GradientArea.d.ts +19 -13
- package/dts/chart/area/GradientArea.d.ts.map +1 -1
- package/dts/chart/area/SolidArea.d.ts +17 -2
- package/dts/chart/area/SolidArea.d.ts.map +1 -1
- package/dts/chart/axis/Axis.d.ts +68 -78
- package/dts/chart/axis/Axis.d.ts.map +1 -1
- package/dts/chart/axis/DefaultAxisTickLabel.d.ts +8 -0
- package/dts/chart/axis/DefaultAxisTickLabel.d.ts.map +1 -0
- package/dts/chart/axis/XAxis.d.ts +1 -1
- package/dts/chart/axis/XAxis.d.ts.map +1 -1
- package/dts/chart/axis/YAxis.d.ts +2 -2
- package/dts/chart/axis/YAxis.d.ts.map +1 -1
- package/dts/chart/axis/index.d.ts +1 -0
- package/dts/chart/axis/index.d.ts.map +1 -1
- package/dts/chart/bar/Bar.d.ts +16 -13
- package/dts/chart/bar/Bar.d.ts.map +1 -1
- package/dts/chart/bar/BarChart.d.ts +36 -20
- package/dts/chart/bar/BarChart.d.ts.map +1 -1
- package/dts/chart/bar/BarPlot.d.ts +2 -1
- package/dts/chart/bar/BarPlot.d.ts.map +1 -1
- package/dts/chart/bar/BarStack.d.ts +39 -48
- package/dts/chart/bar/BarStack.d.ts.map +1 -1
- package/dts/chart/bar/BarStackGroup.d.ts +1 -0
- package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
- package/dts/chart/bar/DefaultBar.d.ts +1 -1
- package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
- package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
- package/dts/chart/gradient/Gradient.d.ts +25 -0
- package/dts/chart/gradient/Gradient.d.ts.map +1 -0
- package/dts/chart/gradient/index.d.ts +2 -0
- package/dts/chart/gradient/index.d.ts.map +1 -0
- package/dts/chart/index.d.ts +3 -1
- package/dts/chart/index.d.ts.map +1 -1
- package/dts/chart/line/DefaultReferenceLineLabel.d.ts +9 -0
- package/dts/chart/line/DefaultReferenceLineLabel.d.ts.map +1 -0
- package/dts/chart/line/DottedLine.d.ts +13 -5
- package/dts/chart/line/DottedLine.d.ts.map +1 -1
- package/dts/chart/line/Line.d.ts +62 -25
- package/dts/chart/line/Line.d.ts.map +1 -1
- package/dts/chart/line/LineChart.d.ts +43 -9
- package/dts/chart/line/LineChart.d.ts.map +1 -1
- package/dts/chart/line/ReferenceLine.d.ts +68 -20
- package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
- package/dts/chart/line/SolidLine.d.ts +8 -5
- package/dts/chart/line/SolidLine.d.ts.map +1 -1
- package/dts/chart/line/index.d.ts +1 -1
- package/dts/chart/line/index.d.ts.map +1 -1
- package/dts/chart/point/DefaultPointLabel.d.ts +10 -0
- package/dts/chart/point/DefaultPointLabel.d.ts.map +1 -0
- package/dts/chart/point/Point.d.ts +120 -0
- package/dts/chart/point/Point.d.ts.map +1 -0
- package/dts/chart/point/index.d.ts +3 -0
- package/dts/chart/point/index.d.ts.map +1 -0
- package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +8 -0
- package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -0
- package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts +12 -0
- package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts.map +1 -0
- package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +11 -0
- package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -0
- package/dts/chart/scrubber/Scrubber.d.ts +172 -43
- package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
- package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +44 -0
- package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -0
- package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +31 -0
- package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -0
- package/dts/chart/scrubber/ScrubberProvider.d.ts +6 -3
- package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
- package/dts/chart/scrubber/index.d.ts +3 -0
- package/dts/chart/scrubber/index.d.ts.map +1 -1
- package/dts/chart/text/ChartText.d.ts +151 -77
- package/dts/chart/text/ChartText.d.ts.map +1 -1
- package/dts/chart/text/{SmartChartTextGroup.d.ts → ChartTextGroup.d.ts} +9 -3
- package/dts/chart/text/ChartTextGroup.d.ts.map +1 -0
- package/dts/chart/text/index.d.ts +1 -1
- package/dts/chart/text/index.d.ts.map +1 -1
- package/dts/chart/utils/chart.d.ts +34 -7
- package/dts/chart/utils/chart.d.ts.map +1 -1
- package/dts/chart/utils/context.d.ts +28 -7
- package/dts/chart/utils/context.d.ts.map +1 -1
- package/dts/chart/utils/gradient.d.ts +117 -0
- package/dts/chart/utils/gradient.d.ts.map +1 -0
- package/dts/chart/utils/index.d.ts +3 -0
- package/dts/chart/utils/index.d.ts.map +1 -1
- package/dts/chart/utils/path.d.ts +53 -0
- package/dts/chart/utils/path.d.ts.map +1 -1
- package/dts/chart/utils/point.d.ts +60 -1
- package/dts/chart/utils/point.d.ts.map +1 -1
- package/dts/chart/utils/scale.d.ts +91 -0
- package/dts/chart/utils/scale.d.ts.map +1 -1
- package/dts/chart/utils/scrubber.d.ts +39 -0
- package/dts/chart/utils/scrubber.d.ts.map +1 -0
- package/dts/chart/utils/transition.d.ts +140 -0
- package/dts/chart/utils/transition.d.ts.map +1 -0
- package/esm/chart/CartesianChart.js +164 -70
- package/esm/chart/ChartContextBridge.js +148 -0
- package/esm/chart/Path.js +196 -113
- package/esm/chart/PeriodSelector.js +1 -1
- package/esm/chart/__stories__/CartesianChart.stories.js +371 -129
- package/esm/chart/__stories__/Chart.stories.js +2 -4
- package/esm/chart/area/Area.js +25 -35
- package/esm/chart/area/AreaChart.js +17 -12
- package/esm/chart/area/DottedArea.js +61 -109
- package/esm/chart/area/GradientArea.js +35 -91
- package/esm/chart/area/SolidArea.js +22 -8
- package/esm/chart/area/__stories__/AreaChart.stories.js +1 -1
- package/esm/chart/axis/Axis.js +2 -0
- package/esm/chart/axis/DefaultAxisTickLabel.js +11 -0
- package/esm/chart/axis/XAxis.js +62 -56
- package/esm/chart/axis/YAxis.js +58 -52
- package/esm/chart/axis/__stories__/Axis.stories.js +0 -1
- package/esm/chart/axis/index.js +1 -0
- package/esm/chart/bar/Bar.js +3 -1
- package/esm/chart/bar/BarChart.js +15 -37
- package/esm/chart/bar/BarPlot.js +41 -35
- package/esm/chart/bar/BarStack.js +75 -38
- package/esm/chart/bar/BarStackGroup.js +6 -16
- package/esm/chart/bar/DefaultBar.js +26 -48
- package/esm/chart/bar/DefaultBarStack.js +23 -58
- package/esm/chart/bar/__stories__/BarChart.stories.js +463 -77
- package/esm/chart/gradient/Gradient.js +53 -0
- package/esm/chart/gradient/index.js +1 -0
- package/esm/chart/index.js +3 -1
- package/esm/chart/line/DefaultReferenceLineLabel.js +66 -0
- package/esm/chart/line/DottedLine.js +29 -14
- package/esm/chart/line/Line.js +106 -67
- package/esm/chart/line/LineChart.js +20 -14
- package/esm/chart/line/ReferenceLine.js +80 -63
- package/esm/chart/line/SolidLine.js +25 -10
- package/esm/chart/line/__stories__/LineChart.stories.js +2098 -1975
- package/esm/chart/line/__stories__/ReferenceLine.stories.js +83 -28
- package/esm/chart/line/index.js +1 -1
- package/esm/chart/point/DefaultPointLabel.js +39 -0
- package/esm/chart/point/Point.js +188 -0
- package/esm/chart/point/index.js +2 -0
- package/esm/chart/scrubber/DefaultScrubberBeacon.js +179 -0
- package/esm/chart/scrubber/DefaultScrubberBeaconLabel.js +43 -0
- package/esm/chart/scrubber/DefaultScrubberLabel.js +28 -0
- package/esm/chart/scrubber/Scrubber.js +126 -146
- package/esm/chart/scrubber/ScrubberBeaconGroup.js +161 -0
- package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +185 -0
- package/esm/chart/scrubber/ScrubberProvider.js +46 -54
- package/esm/chart/scrubber/index.js +3 -1
- package/esm/chart/text/ChartText.js +242 -174
- package/esm/chart/text/{SmartChartTextGroup.js → ChartTextGroup.js} +6 -5
- package/esm/chart/text/index.js +1 -1
- package/esm/chart/utils/chart.js +44 -3
- package/esm/chart/utils/gradient.js +305 -0
- package/esm/chart/utils/index.js +3 -0
- package/esm/chart/utils/path.js +76 -8
- package/esm/chart/utils/point.js +116 -5
- package/esm/chart/utils/scale.js +230 -1
- package/esm/chart/utils/scrubber.js +139 -0
- package/esm/chart/utils/transition.js +214 -0
- package/package.json +7 -5
- package/dts/chart/Point.d.ts +0 -103
- package/dts/chart/Point.d.ts.map +0 -1
- package/dts/chart/line/GradientLine.d.ts +0 -45
- package/dts/chart/line/GradientLine.d.ts.map +0 -1
- package/dts/chart/scrubber/ScrubberBeacon.d.ts +0 -75
- package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +0 -1
- package/dts/chart/text/SmartChartTextGroup.d.ts.map +0 -1
- package/esm/chart/Point.js +0 -111
- package/esm/chart/line/GradientLine.js +0 -62
- package/esm/chart/scrubber/ScrubberBeacon.js +0 -199
|
@@ -2,7 +2,7 @@ const _excluded = ["onDimensionsChange"];
|
|
|
2
2
|
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
|
|
3
3
|
function _objectWithoutPropertiesLoose(r, e) { if (null == r) return {}; var t = {}; for (var n in r) if ({}.hasOwnProperty.call(r, n)) { if (-1 !== e.indexOf(n)) continue; t[n] = r[n]; } return t; }
|
|
4
4
|
import { memo, useEffect, useMemo, useState } from 'react';
|
|
5
|
-
import {
|
|
5
|
+
import { Group } from '@shopify/react-native-skia';
|
|
6
6
|
import { ChartText } from './ChartText';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -32,12 +32,13 @@ const EPSILON_PX = 0.5;
|
|
|
32
32
|
*
|
|
33
33
|
* The component focuses solely on overlap prevention logic for better separation of concerns.
|
|
34
34
|
*/
|
|
35
|
-
export const
|
|
35
|
+
export const ChartTextGroup = /*#__PURE__*/memo(_ref => {
|
|
36
36
|
let {
|
|
37
37
|
labels,
|
|
38
38
|
minGap = 8,
|
|
39
39
|
prioritizeEndLabels = true,
|
|
40
|
-
chartTextProps
|
|
40
|
+
chartTextProps,
|
|
41
|
+
LabelComponent = ChartText
|
|
41
42
|
} = _ref;
|
|
42
43
|
const [boundingBoxes, setBoundingBoxes] = useState(new Map());
|
|
43
44
|
const _ref2 = chartTextProps != null ? chartTextProps : {},
|
|
@@ -193,11 +194,11 @@ export const SmartChartTextGroup = /*#__PURE__*/memo(_ref => {
|
|
|
193
194
|
}
|
|
194
195
|
return new Set(greedy);
|
|
195
196
|
}, [isReady, boundingBoxes, minGap, prioritizeEndLabels, labelsWithKeys]);
|
|
196
|
-
return /*#__PURE__*/_jsx(
|
|
197
|
+
return /*#__PURE__*/_jsx(Group, {
|
|
197
198
|
children: labelsWithKeys.map(labelData => {
|
|
198
199
|
const hasMeasurement = boundingBoxes.has(labelData.key);
|
|
199
200
|
const isVisible = hasMeasurement && isReady && (visibleKeySet == null ? void 0 : visibleKeySet.has(labelData.key));
|
|
200
|
-
return /*#__PURE__*/_jsx(
|
|
201
|
+
return /*#__PURE__*/_jsx(LabelComponent, _extends({
|
|
201
202
|
opacity: isVisible ? 1 : 0,
|
|
202
203
|
x: labelData.x,
|
|
203
204
|
y: labelData.y
|
package/esm/chart/text/index.js
CHANGED
package/esm/chart/utils/chart.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isSharedValue } from 'react-native-reanimated';
|
|
1
2
|
import { stack as d3Stack, stackOffsetDiverging, stackOrderNone } from 'd3-shape';
|
|
2
3
|
export const defaultStackId = 'DEFAULT_STACK_ID';
|
|
3
4
|
/**
|
|
@@ -19,13 +20,13 @@ export const getChartDomain = (series, min, max) => {
|
|
|
19
20
|
return domain;
|
|
20
21
|
}
|
|
21
22
|
if (series.length > 0) {
|
|
22
|
-
const
|
|
23
|
+
const dataLength = Math.max(...series.map(s => {
|
|
23
24
|
var _s$data;
|
|
24
25
|
return ((_s$data = s.data) == null ? void 0 : _s$data.length) || 0;
|
|
25
26
|
}));
|
|
26
|
-
if (
|
|
27
|
+
if (dataLength > 0) {
|
|
27
28
|
if (domain.min === undefined) domain.min = 0;
|
|
28
|
-
if (domain.max === undefined) domain.max =
|
|
29
|
+
if (domain.max === undefined) domain.max = dataLength - 1;
|
|
29
30
|
}
|
|
30
31
|
}
|
|
31
32
|
return domain;
|
|
@@ -111,6 +112,32 @@ export const getStackedSeriesData = series => {
|
|
|
111
112
|
return stackedDataMap;
|
|
112
113
|
};
|
|
113
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Extracts line data values from series data that may contain tuples.
|
|
117
|
+
* For tuple data [[baseline, value]], extracts the last value.
|
|
118
|
+
* For numeric data [value], returns as-is.
|
|
119
|
+
*
|
|
120
|
+
* @param data - Array of numbers, tuples, or null values
|
|
121
|
+
* @returns Array of numbers or null values
|
|
122
|
+
*/
|
|
123
|
+
export const getLineData = data => {
|
|
124
|
+
if (!data) return [];
|
|
125
|
+
|
|
126
|
+
// Check if this is tuple data by finding first non-null entry
|
|
127
|
+
const firstNonNull = data.find(d => d !== null);
|
|
128
|
+
if (Array.isArray(firstNonNull)) {
|
|
129
|
+
return data.map(d => {
|
|
130
|
+
var _d$at;
|
|
131
|
+
if (d === null) return null;
|
|
132
|
+
if (Array.isArray(d)) return (_d$at = d.at(-1)) != null ? _d$at : null;
|
|
133
|
+
return d;
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Already numeric data
|
|
138
|
+
return data;
|
|
139
|
+
};
|
|
140
|
+
|
|
114
141
|
/**
|
|
115
142
|
* Calculates the range of a chart from series data.
|
|
116
143
|
* Range represents the range of y-values from the data.
|
|
@@ -226,4 +253,18 @@ export const getChartInset = (inset, defaults) => {
|
|
|
226
253
|
bottom: (_inset$bottom = inset == null ? void 0 : inset.bottom) != null ? _inset$bottom : baseDefaults.bottom,
|
|
227
254
|
right: (_inset$right = inset == null ? void 0 : inset.right) != null ? _inset$right : baseDefaults.right
|
|
228
255
|
};
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Unwraps an optionally animated value to get the raw value.
|
|
260
|
+
* @param value - The value to unwrap.
|
|
261
|
+
* @returns The raw value.
|
|
262
|
+
*/
|
|
263
|
+
export const unwrapAnimatedValue = value => {
|
|
264
|
+
'worklet';
|
|
265
|
+
|
|
266
|
+
if (isSharedValue(value)) {
|
|
267
|
+
return value.value;
|
|
268
|
+
}
|
|
269
|
+
return value;
|
|
229
270
|
};
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { Skia } from '@shopify/react-native-skia';
|
|
2
|
+
import { applySerializableScale, isCategoricalScale, isSerializableScale } from './scale';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Defines a color transition point in the gradient
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Defines a gradient.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolves gradient stops, handling both static arrays and function forms.
|
|
14
|
+
* When stops is a function, calls it with the domain bounds.
|
|
15
|
+
*/
|
|
16
|
+
export const getGradientStops = (stops, domain) => {
|
|
17
|
+
if (typeof stops === 'function') {
|
|
18
|
+
return stops(domain);
|
|
19
|
+
}
|
|
20
|
+
return stops;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Processes Gradient to gradient configuration for SVG linearGradient.
|
|
25
|
+
* Colors are smoothly interpolated between stops by the browser.
|
|
26
|
+
* Multiple stops at the same offset create hard color transitions.
|
|
27
|
+
*/
|
|
28
|
+
const processGradientStops = (stops, scale) => {
|
|
29
|
+
if (stops.length === 0) {
|
|
30
|
+
console.warn('Gradient has no stops - falling back to default');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Check if stops are in ascending order
|
|
35
|
+
const isOutOfOrder = stops.some((stop, i) => {
|
|
36
|
+
return i > 0 && stop.offset < stops[i - 1].offset;
|
|
37
|
+
});
|
|
38
|
+
if (isOutOfOrder) {
|
|
39
|
+
console.warn("Gradient: stop offsets must be in ascending order");
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
const [rangeMin, rangeMax] = scale.range();
|
|
43
|
+
const rangeSpan = Math.abs(rangeMax - rangeMin);
|
|
44
|
+
|
|
45
|
+
// Convert data value offsets to normalized positions (0-1) using scale
|
|
46
|
+
const normalizedStops = stops.map(stop => {
|
|
47
|
+
var _stop$opacity;
|
|
48
|
+
const stopPosition = scale(stop.offset);
|
|
49
|
+
const normalized = stopPosition === undefined ? 0 : Math.max(0, Math.min(1, Math.abs(stopPosition - rangeMin) / rangeSpan));
|
|
50
|
+
return {
|
|
51
|
+
offset: normalized,
|
|
52
|
+
// Now 0-1 normalized (not data space)
|
|
53
|
+
color: stop.color,
|
|
54
|
+
opacity: (_stop$opacity = stop.opacity) != null ? _stop$opacity : 1
|
|
55
|
+
};
|
|
56
|
+
}).sort((a, b) => a.offset - b.offset);
|
|
57
|
+
return normalizedStops;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Interpolates between two colors using linear interpolation.
|
|
62
|
+
* Returns an rgba string.
|
|
63
|
+
*/
|
|
64
|
+
const interpolateColor = (color1, color2, t) => {
|
|
65
|
+
'worklet';
|
|
66
|
+
|
|
67
|
+
const c1 = Skia.Color(color1);
|
|
68
|
+
const c2 = Skia.Color(color2);
|
|
69
|
+
const r = Math.round((c1[0] + (c2[0] - c1[0]) * t) * 255);
|
|
70
|
+
const g = Math.round((c1[1] + (c2[1] - c1[1]) * t) * 255);
|
|
71
|
+
const b = Math.round((c1[2] + (c2[2] - c1[2]) * t) * 255);
|
|
72
|
+
const a = c1[3] + (c2[3] - c1[3]) * t;
|
|
73
|
+
return "rgba(" + r + ", " + g + ", " + b + ", " + a + ")";
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Adds an opacity to a color
|
|
78
|
+
* Returns an rgba string.
|
|
79
|
+
*/
|
|
80
|
+
export const getColorWithOpacity = (color1, opacity) => {
|
|
81
|
+
const c = Skia.Color(color1);
|
|
82
|
+
return "rgba(" + c[0] * 255 + ", " + c[1] * 255 + ", " + c[2] * 255 + ", " + opacity + ")";
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Creates a gradient configuration for SVG components.
|
|
87
|
+
* Processes a GradientDefinition into a renderable GradientConfig.
|
|
88
|
+
* Supports both numeric scales (linear, log) and categorical scales (band).
|
|
89
|
+
*
|
|
90
|
+
* @param gradient - GradientDefinition configuration (required)
|
|
91
|
+
* @param xScale - X-axis scale (required)
|
|
92
|
+
* @param yScale - Y-axis scale (required)
|
|
93
|
+
* @returns GradientConfig or null if gradient processing fails
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* const gradientConfig = useMemo(() => {
|
|
97
|
+
* if (!gradient || !xScale || !yScale) return;
|
|
98
|
+
* return getGradientConfig(gradient, xScale, yScale);
|
|
99
|
+
* }, [gradient, xScale, yScale]);
|
|
100
|
+
*
|
|
101
|
+
* if (gradientConfig) {
|
|
102
|
+
* return (
|
|
103
|
+
* <defs>
|
|
104
|
+
* <Gradient
|
|
105
|
+
* config={gradientConfig}
|
|
106
|
+
* direction={gradient.axis === 'x' ? 'horizontal' : 'vertical'}
|
|
107
|
+
* id={gradientId}
|
|
108
|
+
* />
|
|
109
|
+
* </defs>
|
|
110
|
+
* );
|
|
111
|
+
* }
|
|
112
|
+
*/
|
|
113
|
+
export const getGradientConfig = (gradient, xScale, yScale) => {
|
|
114
|
+
if (!gradient) return;
|
|
115
|
+
|
|
116
|
+
// Get the scale based on axis
|
|
117
|
+
const scale = gradient.axis === 'x' ? xScale : yScale;
|
|
118
|
+
if (!scale) return;
|
|
119
|
+
|
|
120
|
+
// Extract domain from scale
|
|
121
|
+
const scaleDomain = scale.domain();
|
|
122
|
+
let domain;
|
|
123
|
+
if (isCategoricalScale(scale)) {
|
|
124
|
+
const domainArray = scaleDomain;
|
|
125
|
+
domain = {
|
|
126
|
+
min: domainArray[0],
|
|
127
|
+
max: domainArray[domainArray.length - 1]
|
|
128
|
+
};
|
|
129
|
+
} else {
|
|
130
|
+
const [min, max] = scaleDomain;
|
|
131
|
+
domain = {
|
|
132
|
+
min,
|
|
133
|
+
max
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const resolvedStops = getGradientStops(gradient.stops, domain);
|
|
137
|
+
return processGradientStops(resolvedStops, scale);
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Evaluates the color at a specific data value based on the gradient stops, ignoring opacity.
|
|
142
|
+
* @param stops - The gradient stops configuration
|
|
143
|
+
* @param dataValue - The data value to evaluate (for band scales, this is the index)
|
|
144
|
+
* @param scale - The scale to use for value mapping (handles log scales correctly)
|
|
145
|
+
* @returns The color string at this data value, or undefined if invalid
|
|
146
|
+
*/
|
|
147
|
+
export const evaluateGradientAtValue = (stops, dataValue, scale) => {
|
|
148
|
+
'worklet';
|
|
149
|
+
|
|
150
|
+
if (stops.length === 0) return;
|
|
151
|
+
|
|
152
|
+
// Determine range based on scale type
|
|
153
|
+
let rangeMin;
|
|
154
|
+
let rangeMax;
|
|
155
|
+
if (isSerializableScale(scale)) {
|
|
156
|
+
// SerializableScale has range as [number, number]
|
|
157
|
+
[rangeMin, rangeMax] = scale.range;
|
|
158
|
+
} else {
|
|
159
|
+
// ChartScaleFunction has range() method
|
|
160
|
+
const scaleRange = scale.range();
|
|
161
|
+
[rangeMin, rangeMax] = Array.isArray(scaleRange) ? scaleRange : [scaleRange, scaleRange]; // fallback for band scales
|
|
162
|
+
}
|
|
163
|
+
const rangeSpan = Math.abs(rangeMax - rangeMin);
|
|
164
|
+
if (rangeSpan === 0) return stops[0].color;
|
|
165
|
+
|
|
166
|
+
// Map dataValue through scale to get position
|
|
167
|
+
let dataPosition;
|
|
168
|
+
if (isSerializableScale(scale)) {
|
|
169
|
+
dataPosition = applySerializableScale(dataValue, scale);
|
|
170
|
+
} else {
|
|
171
|
+
const result = scale(dataValue);
|
|
172
|
+
if (result === undefined) return stops[0].color;
|
|
173
|
+
dataPosition = result;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// Normalize to 0-1 based on range
|
|
177
|
+
const normalizedValue = Math.max(0, Math.min(1, Math.abs(dataPosition - rangeMin) / rangeSpan));
|
|
178
|
+
|
|
179
|
+
// Map stop offsets through scale and normalize to 0-1
|
|
180
|
+
const positions = stops.map(stop => {
|
|
181
|
+
let stopPosition;
|
|
182
|
+
if (isSerializableScale(scale)) {
|
|
183
|
+
stopPosition = applySerializableScale(stop.offset, scale);
|
|
184
|
+
} else {
|
|
185
|
+
const result = scale(stop.offset);
|
|
186
|
+
if (result === undefined) return 0;
|
|
187
|
+
stopPosition = result;
|
|
188
|
+
}
|
|
189
|
+
return Math.max(0, Math.min(1, Math.abs(stopPosition - rangeMin) / rangeSpan));
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
// Find which segment we're in
|
|
193
|
+
if (normalizedValue < positions[0]) {
|
|
194
|
+
return stops[0].color;
|
|
195
|
+
}
|
|
196
|
+
if (normalizedValue >= positions[positions.length - 1]) {
|
|
197
|
+
return stops[stops.length - 1].color;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Check if dataValue matches any stop offset exactly (for hard transitions)
|
|
201
|
+
for (let i = 0; i < stops.length; i++) {
|
|
202
|
+
if (dataValue === stops[i].offset) {
|
|
203
|
+
// Found exact match - check if there are multiple stops at this offset (hard transition)
|
|
204
|
+
// Use the LAST color at this offset for hard transitions
|
|
205
|
+
let lastIndexAtOffset = i;
|
|
206
|
+
while (lastIndexAtOffset + 1 < stops.length && stops[lastIndexAtOffset + 1].offset === stops[i].offset) {
|
|
207
|
+
lastIndexAtOffset++;
|
|
208
|
+
}
|
|
209
|
+
return stops[lastIndexAtOffset].color;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Find the segment to interpolate between
|
|
214
|
+
for (let i = 0; i < positions.length - 1; i++) {
|
|
215
|
+
if (normalizedValue >= positions[i] && normalizedValue <= positions[i + 1]) {
|
|
216
|
+
const segmentStart = positions[i];
|
|
217
|
+
const segmentEnd = positions[i + 1];
|
|
218
|
+
if (segmentEnd === segmentStart) {
|
|
219
|
+
return stops[i].color;
|
|
220
|
+
}
|
|
221
|
+
const t = (normalizedValue - segmentStart) / (segmentEnd - segmentStart);
|
|
222
|
+
return interpolateColor(stops[i].color, stops[i + 1].color, t);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return stops[0].color;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Determines the baseline value for the gradient area by finding the value
|
|
230
|
+
* within the axis bounds that is closest to the target baseline.
|
|
231
|
+
*
|
|
232
|
+
* @param axisBounds - The min and max bounds of the axis
|
|
233
|
+
* @param baseline - The target baseline value (defaults to 0)
|
|
234
|
+
* @returns The value within bounds closest to the baseline
|
|
235
|
+
*/
|
|
236
|
+
export const getBaseline = function (axisBounds, baseline) {
|
|
237
|
+
if (baseline === void 0) {
|
|
238
|
+
baseline = 0;
|
|
239
|
+
}
|
|
240
|
+
const {
|
|
241
|
+
min,
|
|
242
|
+
max
|
|
243
|
+
} = axisBounds;
|
|
244
|
+
|
|
245
|
+
// Normalize to ensure lowerBound <= upperBound
|
|
246
|
+
const lowerBound = Math.min(min, max);
|
|
247
|
+
const upperBound = Math.max(min, max);
|
|
248
|
+
|
|
249
|
+
// If baseline is within the range, use it
|
|
250
|
+
if (lowerBound <= baseline && baseline <= upperBound) return baseline;
|
|
251
|
+
|
|
252
|
+
// Otherwise, return the bound closest to baseline
|
|
253
|
+
return Math.abs(lowerBound - baseline) < Math.abs(upperBound - baseline) ? lowerBound : upperBound;
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Generates a gradient definition for the area chart based on the axis bounds
|
|
258
|
+
* and styling parameters. Ensures gradient stops are in ascending order.
|
|
259
|
+
*
|
|
260
|
+
* @param axisBounds - The min and max bounds of the axis
|
|
261
|
+
* @param baselineValue - The baseline value for the gradient
|
|
262
|
+
* @param fill - The color to use for the gradient
|
|
263
|
+
* @param peakOpacity - Opacity at the peak of the gradient
|
|
264
|
+
* @param baselineOpacity - Opacity at the baseline
|
|
265
|
+
* @returns A gradient definition with y-axis stops in ascending order
|
|
266
|
+
*/
|
|
267
|
+
export const createGradient = (axisBounds, baselineValue, fill, peakOpacity, baselineOpacity) => {
|
|
268
|
+
const {
|
|
269
|
+
min,
|
|
270
|
+
max
|
|
271
|
+
} = axisBounds;
|
|
272
|
+
const lowerBound = Math.min(min, max);
|
|
273
|
+
const upperBound = Math.max(min, max);
|
|
274
|
+
if (lowerBound < baselineValue && baselineValue < upperBound) {
|
|
275
|
+
return {
|
|
276
|
+
axis: 'y',
|
|
277
|
+
stops: [{
|
|
278
|
+
offset: lowerBound,
|
|
279
|
+
color: fill,
|
|
280
|
+
opacity: peakOpacity
|
|
281
|
+
}, {
|
|
282
|
+
offset: baselineValue,
|
|
283
|
+
color: fill,
|
|
284
|
+
opacity: baselineOpacity
|
|
285
|
+
}, {
|
|
286
|
+
offset: upperBound,
|
|
287
|
+
color: fill,
|
|
288
|
+
opacity: peakOpacity
|
|
289
|
+
}]
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
const peakValue = Math.abs(min - baselineValue) > Math.abs(max - baselineValue) ? min : max;
|
|
293
|
+
return {
|
|
294
|
+
axis: 'y',
|
|
295
|
+
stops: [{
|
|
296
|
+
offset: peakValue,
|
|
297
|
+
color: fill,
|
|
298
|
+
opacity: peakOpacity
|
|
299
|
+
}, {
|
|
300
|
+
offset: baselineValue,
|
|
301
|
+
color: fill,
|
|
302
|
+
opacity: baselineOpacity
|
|
303
|
+
}].sort((a, b) => a.offset - b.offset)
|
|
304
|
+
};
|
|
305
|
+
};
|
package/esm/chart/utils/index.js
CHANGED
|
@@ -3,7 +3,10 @@ export * from './axis';
|
|
|
3
3
|
export * from './bar';
|
|
4
4
|
export * from './chart';
|
|
5
5
|
export * from './context';
|
|
6
|
+
export * from './gradient';
|
|
6
7
|
export * from './path';
|
|
7
8
|
export * from './point';
|
|
8
9
|
export * from './scale';
|
|
10
|
+
export * from './scrubber';
|
|
11
|
+
export * from './transition';
|
|
9
12
|
// codegen:end
|
package/esm/chart/utils/path.js
CHANGED
|
@@ -49,10 +49,11 @@ export const getLinePath = _ref => {
|
|
|
49
49
|
var _pathGenerator;
|
|
50
50
|
let {
|
|
51
51
|
data,
|
|
52
|
-
curve = '
|
|
52
|
+
curve = 'bump',
|
|
53
53
|
xScale,
|
|
54
54
|
yScale,
|
|
55
|
-
xData
|
|
55
|
+
xData,
|
|
56
|
+
connectNulls = false
|
|
56
57
|
} = _ref;
|
|
57
58
|
if (data.length === 0) {
|
|
58
59
|
return '';
|
|
@@ -64,9 +65,12 @@ export const getLinePath = _ref => {
|
|
|
64
65
|
yScale,
|
|
65
66
|
xData
|
|
66
67
|
});
|
|
67
|
-
const pathGenerator = d3Line().x(d => d.x).y(d => d.y).curve(curveFunction).defined(d => d !== null); // Only draw lines where point is not null
|
|
68
68
|
|
|
69
|
-
|
|
69
|
+
// When connectNulls is true, filter out null values before rendering
|
|
70
|
+
// When false, use defined() to create gaps in the line
|
|
71
|
+
const filteredPoints = connectNulls ? dataPoints.filter(d => d !== null) : dataPoints;
|
|
72
|
+
const pathGenerator = d3Line().x(d => d.x).y(d => d.y).curve(curveFunction).defined(d => connectNulls || d !== null);
|
|
73
|
+
return (_pathGenerator = pathGenerator(filteredPoints)) != null ? _pathGenerator : '';
|
|
70
74
|
};
|
|
71
75
|
|
|
72
76
|
/**
|
|
@@ -94,10 +98,11 @@ export const getLinePath = _ref => {
|
|
|
94
98
|
export const getAreaPath = _ref2 => {
|
|
95
99
|
let {
|
|
96
100
|
data,
|
|
97
|
-
curve = '
|
|
101
|
+
curve = 'bump',
|
|
98
102
|
xScale,
|
|
99
103
|
yScale,
|
|
100
|
-
xData
|
|
104
|
+
xData,
|
|
105
|
+
connectNulls = false
|
|
101
106
|
} = _ref2;
|
|
102
107
|
if (data.length === 0) {
|
|
103
108
|
return '';
|
|
@@ -158,6 +163,10 @@ export const getAreaPath = _ref2 => {
|
|
|
158
163
|
isValid: true
|
|
159
164
|
};
|
|
160
165
|
});
|
|
166
|
+
|
|
167
|
+
// When connectNulls is true, filter out invalid points before rendering
|
|
168
|
+
// When false, use defined() to create gaps in the area
|
|
169
|
+
const filteredPoints = connectNulls ? dataPoints.filter(d => d.isValid) : dataPoints;
|
|
161
170
|
const areaGenerator = d3Area().x(d => d.x).y0(d => {
|
|
162
171
|
var _d$low;
|
|
163
172
|
return (_d$low = d.low) != null ? _d$low : 0;
|
|
@@ -166,12 +175,26 @@ export const getAreaPath = _ref2 => {
|
|
|
166
175
|
var _d$high;
|
|
167
176
|
return (_d$high = d.high) != null ? _d$high : 0;
|
|
168
177
|
}) // Top boundary (high values), fallback to 0
|
|
169
|
-
.curve(curveFunction).defined(d => d.isValid && d.low != null && d.high != null); // Only draw where both values exist
|
|
178
|
+
.curve(curveFunction).defined(d => connectNulls || d.isValid && d.low != null && d.high != null); // Only draw where both values exist
|
|
170
179
|
|
|
171
|
-
const result = areaGenerator(
|
|
180
|
+
const result = areaGenerator(filteredPoints);
|
|
172
181
|
return result != null ? result : '';
|
|
173
182
|
};
|
|
174
183
|
|
|
184
|
+
/**
|
|
185
|
+
* Converts line coordinates to an SVG path string.
|
|
186
|
+
* Useful for rendering axis lines and tick marks.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* ```typescript
|
|
190
|
+
* const path = lineToPath(0, 0, 100, 100);
|
|
191
|
+
* // Returns: "M 0 0 L 100 100"
|
|
192
|
+
* ```
|
|
193
|
+
*/
|
|
194
|
+
export const lineToPath = (x1, y1, x2, y2) => {
|
|
195
|
+
return "M " + x1 + " " + y1 + " L " + x2 + " " + y2;
|
|
196
|
+
};
|
|
197
|
+
|
|
175
198
|
/**
|
|
176
199
|
* Creates an SVG path string for a rectangle with selective corner rounding.
|
|
177
200
|
* Useful for creating bars in charts with optional rounded corners.
|
|
@@ -203,4 +226,49 @@ export const getBarPath = (x, y, width, height, radius, roundTop, roundBottom) =
|
|
|
203
226
|
path += " A " + topR + " " + topR + " 0 0 1 " + (x + topR) + " " + y;
|
|
204
227
|
path += ' Z';
|
|
205
228
|
return path;
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Generates an SVG path string with circles arranged in a dotted pattern within a bounding area.
|
|
233
|
+
* Creates circles at regular intervals based on the pattern size and dot size parameters.
|
|
234
|
+
*
|
|
235
|
+
* @param bounds - The bounding rectangle to fill with dots
|
|
236
|
+
* @param patternSize - Size of the pattern unit (spacing between dots)
|
|
237
|
+
* @param dotSize - Radius of each dot
|
|
238
|
+
* @returns SVG path string containing all the circles
|
|
239
|
+
*
|
|
240
|
+
* @example
|
|
241
|
+
* ```typescript
|
|
242
|
+
* const dotsPath = getDottedAreaPath(
|
|
243
|
+
* { x: 0, y: 0, width: 100, height: 50 },
|
|
244
|
+
* 8, // 8px spacing
|
|
245
|
+
* 2 // 2px radius dots
|
|
246
|
+
* );
|
|
247
|
+
* ```
|
|
248
|
+
*/
|
|
249
|
+
export const getDottedAreaPath = (bounds, patternSize, dotSize) => {
|
|
250
|
+
if (bounds.width <= 0 || bounds.height <= 0 || patternSize <= 0 || dotSize <= 0) {
|
|
251
|
+
return '';
|
|
252
|
+
}
|
|
253
|
+
let path = '';
|
|
254
|
+
|
|
255
|
+
// Calculate the number of dots that fit in each dimension
|
|
256
|
+
const dotsX = Math.ceil(bounds.width / patternSize);
|
|
257
|
+
const dotsY = Math.ceil(bounds.height / patternSize);
|
|
258
|
+
|
|
259
|
+
// Generate circles in a grid pattern
|
|
260
|
+
for (let row = 0; row < dotsY; row++) {
|
|
261
|
+
for (let col = 0; col < dotsX; col++) {
|
|
262
|
+
const centerX = bounds.x + col * patternSize + patternSize / 2;
|
|
263
|
+
const centerY = bounds.y + row * patternSize + patternSize / 2;
|
|
264
|
+
|
|
265
|
+
// Only draw dots that are within the bounds
|
|
266
|
+
if (centerX >= bounds.x && centerX <= bounds.x + bounds.width && centerY >= bounds.y && centerY <= bounds.y + bounds.height) {
|
|
267
|
+
// Create circle using SVG arc commands
|
|
268
|
+
// M cx,cy-r a r,r 0 1,0 0,2r a r,r 0 1,0 0,-2r
|
|
269
|
+
path += "M " + centerX + "," + (centerY - dotSize) + " a " + dotSize + "," + dotSize + " 0 1,0 0," + dotSize * 2 + " a " + dotSize + "," + dotSize + " 0 1,0 0," + -dotSize * 2 + " ";
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return path.trim();
|
|
206
274
|
};
|