@coinbase/cds-web-visualization 3.4.0-beta.1 → 3.4.0-beta.11
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 +53 -0
- package/dts/chart/CartesianChart.d.ts +38 -2
- package/dts/chart/CartesianChart.d.ts.map +1 -1
- package/dts/chart/Path.d.ts +27 -7
- package/dts/chart/Path.d.ts.map +1 -1
- package/dts/chart/PeriodSelector.d.ts +0 -4
- package/dts/chart/PeriodSelector.d.ts.map +1 -1
- package/dts/chart/area/Area.d.ts +54 -24
- package/dts/chart/area/Area.d.ts.map +1 -1
- package/dts/chart/area/AreaChart.d.ts +33 -6
- package/dts/chart/area/AreaChart.d.ts.map +1 -1
- package/dts/chart/area/DottedArea.d.ts +21 -44
- package/dts/chart/area/DottedArea.d.ts.map +1 -1
- package/dts/chart/area/GradientArea.d.ts +21 -12
- package/dts/chart/area/GradientArea.d.ts.map +1 -1
- package/dts/chart/area/SolidArea.d.ts +16 -1
- package/dts/chart/area/SolidArea.d.ts.map +1 -1
- package/dts/chart/axis/Axis.d.ts +109 -63
- 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 +17 -8
- 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 +40 -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.map +1 -1
- package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
- package/dts/chart/gradient/Gradient.d.ts +35 -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 +2 -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 +15 -3
- package/dts/chart/line/DottedLine.d.ts.map +1 -1
- package/dts/chart/line/Line.d.ts +87 -28
- package/dts/chart/line/Line.d.ts.map +1 -1
- package/dts/chart/line/LineChart.d.ts +26 -8
- package/dts/chart/line/LineChart.d.ts.map +1 -1
- package/dts/chart/line/ReferenceLine.d.ts +91 -44
- package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
- package/dts/chart/line/SolidLine.d.ts +14 -3
- 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 +201 -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 +24 -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 +10 -0
- package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -0
- package/dts/chart/scrubber/Scrubber.d.ts +207 -66
- package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
- package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +70 -0
- package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -0
- package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +32 -0
- package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -0
- 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 +46 -43
- 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/axis.d.ts +25 -1
- package/dts/chart/utils/axis.d.ts.map +1 -1
- package/dts/chart/utils/chart.d.ts +27 -7
- package/dts/chart/utils/chart.d.ts.map +1 -1
- package/dts/chart/utils/context.d.ts +6 -0
- package/dts/chart/utils/context.d.ts.map +1 -1
- package/dts/chart/utils/gradient.d.ts +104 -0
- package/dts/chart/utils/gradient.d.ts.map +1 -0
- package/dts/chart/utils/index.d.ts +4 -0
- package/dts/chart/utils/index.d.ts.map +1 -1
- package/dts/chart/utils/interpolate.d.ts +112 -0
- package/dts/chart/utils/interpolate.d.ts.map +1 -0
- package/dts/chart/utils/path.d.ts +24 -1
- package/dts/chart/utils/path.d.ts.map +1 -1
- package/dts/chart/utils/point.d.ts +40 -7
- package/dts/chart/utils/point.d.ts.map +1 -1
- package/dts/chart/utils/scale.d.ts +11 -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 +65 -0
- package/dts/chart/utils/transition.d.ts.map +1 -0
- package/dts/sparkline/sparkline-interactive/SparklineInteractiveMarkerDates.d.ts.map +1 -1
- package/esm/chart/CartesianChart.js +140 -85
- package/esm/chart/Path.js +53 -47
- package/esm/chart/PeriodSelector.js +4 -18
- package/esm/chart/area/Area.js +24 -34
- package/esm/chart/area/AreaChart.js +24 -15
- package/esm/chart/area/DottedArea.js +35 -89
- package/esm/chart/area/GradientArea.js +34 -80
- package/esm/chart/area/SolidArea.js +29 -11
- package/esm/chart/axis/Axis.js +4 -39
- package/esm/chart/axis/DefaultAxisTickLabel.js +15 -0
- package/esm/chart/axis/XAxis.js +184 -63
- package/esm/chart/axis/YAxis.js +190 -57
- package/esm/chart/axis/index.js +1 -0
- package/esm/chart/bar/Bar.js +3 -1
- package/esm/chart/bar/BarChart.js +15 -32
- package/esm/chart/bar/BarPlot.js +3 -2
- package/esm/chart/bar/BarStack.js +65 -23
- package/esm/chart/bar/BarStackGroup.js +7 -17
- package/esm/chart/bar/DefaultBar.js +4 -7
- package/esm/chart/bar/DefaultBarStack.js +5 -7
- package/esm/chart/gradient/Gradient.js +104 -0
- package/esm/chart/gradient/index.js +1 -0
- package/esm/chart/index.js +2 -1
- package/esm/chart/line/DefaultReferenceLineLabel.js +81 -0
- package/esm/chart/line/DottedLine.js +38 -17
- package/esm/chart/line/Line.js +96 -70
- package/esm/chart/line/LineChart.js +18 -6
- package/esm/chart/line/ReferenceLine.js +41 -43
- package/esm/chart/line/SolidLine.js +36 -15
- package/esm/chart/line/index.js +1 -1
- package/esm/chart/{line/GradientLine.js → point/DefaultPointLabel.js} +31 -45
- package/esm/chart/point/Point.css +2 -0
- package/esm/chart/{Point.js → point/Point.js} +66 -57
- package/esm/chart/point/index.js +2 -0
- package/esm/chart/scrubber/DefaultScrubberBeacon.js +155 -0
- package/esm/chart/scrubber/{ScrubberBeaconLabel.js → DefaultScrubberBeaconLabel.js} +23 -10
- package/esm/chart/scrubber/DefaultScrubberLabel.js +30 -0
- package/esm/chart/scrubber/Scrubber.js +98 -392
- package/esm/chart/scrubber/ScrubberBeaconGroup.js +166 -0
- package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +186 -0
- package/esm/chart/scrubber/index.js +3 -1
- package/esm/chart/text/ChartText.js +15 -20
- package/esm/chart/text/{SmartChartTextGroup.js → ChartTextGroup.js} +4 -3
- package/esm/chart/text/index.js +1 -1
- package/esm/chart/utils/axis.js +45 -29
- package/esm/chart/utils/chart.js +29 -3
- package/esm/chart/utils/gradient.js +257 -0
- package/esm/chart/utils/index.js +4 -0
- package/esm/chart/utils/interpolate.js +644 -0
- package/esm/chart/utils/path.js +32 -9
- package/esm/chart/utils/point.js +99 -12
- package/esm/chart/utils/scale.js +13 -2
- package/esm/chart/utils/scrubber.js +132 -0
- package/esm/chart/utils/transition.js +111 -0
- package/esm/sparkline/sparkline-interactive/SparklineInteractiveMarkerDates.js +8 -4
- package/package.json +9 -9
- package/dts/chart/Point.d.ts +0 -153
- package/dts/chart/Point.d.ts.map +0 -1
- package/dts/chart/line/GradientLine.d.ts +0 -42
- package/dts/chart/line/GradientLine.d.ts.map +0 -1
- package/dts/chart/scrubber/ScrubberBeacon.d.ts +0 -93
- package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +0 -1
- package/dts/chart/scrubber/ScrubberBeaconLabel.d.ts +0 -7
- package/dts/chart/scrubber/ScrubberBeaconLabel.d.ts.map +0 -1
- package/dts/chart/text/SmartChartTextGroup.d.ts.map +0 -1
- package/esm/chart/Point.css +0 -2
- package/esm/chart/scrubber/ScrubberBeacon.js +0 -195
package/esm/chart/utils/path.js
CHANGED
|
@@ -40,17 +40,18 @@ export const getPathCurveFunction = function () {
|
|
|
40
40
|
* @example
|
|
41
41
|
* ```typescript
|
|
42
42
|
* const chartScale = getChartScale({ chartRect, domain, range, xScale, yScale });
|
|
43
|
-
* const path = getLinePath({ data: [1, 2, 3], chartScale, curve: '
|
|
43
|
+
* const path = getLinePath({ data: [1, 2, 3], chartScale, curve: 'bump' });
|
|
44
44
|
* ```
|
|
45
45
|
*/
|
|
46
46
|
export const getLinePath = _ref => {
|
|
47
47
|
var _pathGenerator;
|
|
48
48
|
let {
|
|
49
49
|
data,
|
|
50
|
-
curve = '
|
|
50
|
+
curve = 'bump',
|
|
51
51
|
xScale,
|
|
52
52
|
yScale,
|
|
53
|
-
xData
|
|
53
|
+
xData,
|
|
54
|
+
connectNulls
|
|
54
55
|
} = _ref;
|
|
55
56
|
if (data.length === 0) {
|
|
56
57
|
return '';
|
|
@@ -62,9 +63,12 @@ export const getLinePath = _ref => {
|
|
|
62
63
|
yScale,
|
|
63
64
|
xData
|
|
64
65
|
});
|
|
65
|
-
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
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
// When connectNulls is true, filter out null values before rendering
|
|
68
|
+
// When false, use defined() to create gaps in the line
|
|
69
|
+
const filteredPoints = connectNulls ? dataPoints.filter(d => d !== null) : dataPoints;
|
|
70
|
+
const pathGenerator = d3Line().x(d => d.x).y(d => d.y).curve(curveFunction).defined(d => connectNulls || d !== null);
|
|
71
|
+
return (_pathGenerator = pathGenerator(filteredPoints)) !== null && _pathGenerator !== void 0 ? _pathGenerator : '';
|
|
68
72
|
};
|
|
69
73
|
|
|
70
74
|
/**
|
|
@@ -92,10 +96,11 @@ export const getLinePath = _ref => {
|
|
|
92
96
|
export const getAreaPath = _ref2 => {
|
|
93
97
|
let {
|
|
94
98
|
data,
|
|
95
|
-
curve = '
|
|
99
|
+
curve = 'bump',
|
|
96
100
|
xScale,
|
|
97
101
|
yScale,
|
|
98
|
-
xData
|
|
102
|
+
xData,
|
|
103
|
+
connectNulls
|
|
99
104
|
} = _ref2;
|
|
100
105
|
if (data.length === 0) {
|
|
101
106
|
return '';
|
|
@@ -156,6 +161,10 @@ export const getAreaPath = _ref2 => {
|
|
|
156
161
|
isValid: true
|
|
157
162
|
};
|
|
158
163
|
});
|
|
164
|
+
|
|
165
|
+
// When connectNulls is true, filter out invalid points before rendering
|
|
166
|
+
// When false, use defined() to create gaps in the area
|
|
167
|
+
const filteredPoints = connectNulls ? dataPoints.filter(d => d.isValid) : dataPoints;
|
|
159
168
|
const areaGenerator = d3Area().x(d => d.x).y0(d => {
|
|
160
169
|
var _d$low;
|
|
161
170
|
return (_d$low = d.low) !== null && _d$low !== void 0 ? _d$low : 0;
|
|
@@ -164,12 +173,26 @@ export const getAreaPath = _ref2 => {
|
|
|
164
173
|
var _d$high;
|
|
165
174
|
return (_d$high = d.high) !== null && _d$high !== void 0 ? _d$high : 0;
|
|
166
175
|
}) // Top boundary (high values), fallback to 0
|
|
167
|
-
.curve(curveFunction).defined(d => d.isValid && d.low != null && d.high != null); // Only draw where both values exist
|
|
176
|
+
.curve(curveFunction).defined(d => connectNulls || d.isValid && d.low != null && d.high != null); // Only draw where both values exist
|
|
168
177
|
|
|
169
|
-
const result = areaGenerator(
|
|
178
|
+
const result = areaGenerator(filteredPoints);
|
|
170
179
|
return result !== null && result !== void 0 ? result : '';
|
|
171
180
|
};
|
|
172
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Converts line coordinates to an SVG path string.
|
|
184
|
+
* Useful for rendering axis lines and tick marks.
|
|
185
|
+
*
|
|
186
|
+
* @example
|
|
187
|
+
* ```typescript
|
|
188
|
+
* const path = lineToPath(0, 0, 100, 100);
|
|
189
|
+
* // Returns: "M 0 0 L 100 100"
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
export const lineToPath = (x1, y1, x2, y2) => {
|
|
193
|
+
return "M".concat(x1, ",").concat(y1, " L").concat(x2, ",").concat(y2);
|
|
194
|
+
};
|
|
195
|
+
|
|
173
196
|
/**
|
|
174
197
|
* Creates an SVG path string for a rectangle with selective corner rounding.
|
|
175
198
|
* Useful for creating bars in charts with optional rounded corners.
|
package/esm/chart/utils/point.js
CHANGED
|
@@ -1,20 +1,46 @@
|
|
|
1
1
|
import { isCategoricalScale, isLogScale, isNumericScale } from './scale';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Position a label should be placed relative to the point
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* 'top' would have the label be located above the point itself,
|
|
8
|
+
* and thus the vertical alignment of that text would be bottom.
|
|
9
|
+
*/
|
|
10
|
+
|
|
3
11
|
/**
|
|
4
12
|
* Get a point from a data value and a scale.
|
|
5
|
-
*
|
|
6
|
-
* @
|
|
7
|
-
* @param
|
|
8
|
-
* @param
|
|
9
|
-
* @returns
|
|
13
|
+
*
|
|
14
|
+
* @param dataValue - The data value to convert to a pixel position.
|
|
15
|
+
* @param scale - The scale function.
|
|
16
|
+
* @param anchor (@default 'middle') - For band scales, where to anchor the point within the band.
|
|
17
|
+
* @returns The pixel value (@default 0 if data value is not defined in scale).
|
|
10
18
|
*/
|
|
11
|
-
export const getPointOnScale = (dataValue, scale)
|
|
12
|
-
var
|
|
19
|
+
export const getPointOnScale = function (dataValue, scale) {
|
|
20
|
+
var _scale;
|
|
21
|
+
let anchor = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'middle';
|
|
13
22
|
if (isCategoricalScale(scale)) {
|
|
14
|
-
var
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
23
|
+
var _bandScale$bandwidth, _bandScale$bandwidth2, _bandScale$step, _bandScale$step2;
|
|
24
|
+
const bandScale = scale;
|
|
25
|
+
const bandStart = bandScale(dataValue);
|
|
26
|
+
if (bandStart === undefined) return 0;
|
|
27
|
+
const bandwidth = (_bandScale$bandwidth = (_bandScale$bandwidth2 = bandScale.bandwidth) === null || _bandScale$bandwidth2 === void 0 ? void 0 : _bandScale$bandwidth2.call(bandScale)) !== null && _bandScale$bandwidth !== void 0 ? _bandScale$bandwidth : 0;
|
|
28
|
+
const step = (_bandScale$step = (_bandScale$step2 = bandScale.step) === null || _bandScale$step2 === void 0 ? void 0 : _bandScale$step2.call(bandScale)) !== null && _bandScale$step !== void 0 ? _bandScale$step : bandwidth;
|
|
29
|
+
const paddingOffset = (step - bandwidth) / 2;
|
|
30
|
+
const stepStart = bandStart - paddingOffset;
|
|
31
|
+
switch (anchor) {
|
|
32
|
+
case 'stepStart':
|
|
33
|
+
return stepStart;
|
|
34
|
+
case 'bandStart':
|
|
35
|
+
return bandStart;
|
|
36
|
+
case 'bandEnd':
|
|
37
|
+
return bandStart + bandwidth;
|
|
38
|
+
case 'stepEnd':
|
|
39
|
+
return stepStart + step;
|
|
40
|
+
case 'middle':
|
|
41
|
+
default:
|
|
42
|
+
return bandStart + bandwidth / 2;
|
|
43
|
+
}
|
|
18
44
|
}
|
|
19
45
|
|
|
20
46
|
// For log scales, ensure the value is positive
|
|
@@ -22,7 +48,7 @@ export const getPointOnScale = (dataValue, scale) => {
|
|
|
22
48
|
if (isLogScale(scale) && dataValue <= 0) {
|
|
23
49
|
adjustedValue = 0.001; // Use a small positive value for log scales
|
|
24
50
|
}
|
|
25
|
-
return (
|
|
51
|
+
return (_scale = scale(adjustedValue)) !== null && _scale !== void 0 ? _scale : 0;
|
|
26
52
|
};
|
|
27
53
|
|
|
28
54
|
/**
|
|
@@ -115,4 +141,65 @@ export const projectPoints = _ref2 => {
|
|
|
115
141
|
yScale
|
|
116
142
|
});
|
|
117
143
|
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Determines text alignment based on label position.
|
|
148
|
+
* For example, a 'top' position needs the text aligned to the 'bottom' so it appears above the point.
|
|
149
|
+
*/
|
|
150
|
+
export const getAlignmentFromPosition = position => {
|
|
151
|
+
let horizontalAlignment = 'center';
|
|
152
|
+
let verticalAlignment = 'middle';
|
|
153
|
+
switch (position) {
|
|
154
|
+
case 'top':
|
|
155
|
+
verticalAlignment = 'bottom';
|
|
156
|
+
break;
|
|
157
|
+
case 'bottom':
|
|
158
|
+
verticalAlignment = 'top';
|
|
159
|
+
break;
|
|
160
|
+
case 'left':
|
|
161
|
+
horizontalAlignment = 'right';
|
|
162
|
+
break;
|
|
163
|
+
case 'right':
|
|
164
|
+
horizontalAlignment = 'left';
|
|
165
|
+
break;
|
|
166
|
+
case 'center':
|
|
167
|
+
default:
|
|
168
|
+
horizontalAlignment = 'center';
|
|
169
|
+
verticalAlignment = 'middle';
|
|
170
|
+
break;
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
horizontalAlignment,
|
|
174
|
+
verticalAlignment
|
|
175
|
+
};
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Calculates the final label coordinates by applying offset based on position.
|
|
180
|
+
*/
|
|
181
|
+
export const getLabelCoordinates = (x, y, position, offset) => {
|
|
182
|
+
let dx = 0;
|
|
183
|
+
let dy = 0;
|
|
184
|
+
switch (position) {
|
|
185
|
+
case 'top':
|
|
186
|
+
dy = -offset;
|
|
187
|
+
break;
|
|
188
|
+
case 'bottom':
|
|
189
|
+
dy = offset;
|
|
190
|
+
break;
|
|
191
|
+
case 'left':
|
|
192
|
+
dx = -offset;
|
|
193
|
+
break;
|
|
194
|
+
case 'right':
|
|
195
|
+
dx = offset;
|
|
196
|
+
break;
|
|
197
|
+
case 'center':
|
|
198
|
+
default:
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
return {
|
|
202
|
+
x: x + dx,
|
|
203
|
+
y: y + dy
|
|
204
|
+
};
|
|
118
205
|
};
|
package/esm/chart/utils/scale.js
CHANGED
|
@@ -43,6 +43,17 @@ export const getCategoricalScale = _ref2 => {
|
|
|
43
43
|
const domainArray = Array.from({
|
|
44
44
|
length: domain.max - domain.min + 1
|
|
45
45
|
}, (_, i) => i);
|
|
46
|
-
const scale = scaleBand().domain(domainArray).range([range.min, range.max]).padding(padding);
|
|
46
|
+
const scale = scaleBand().domain(domainArray).range([range.min, range.max]).paddingInner(padding).paddingOuter(padding / 2);
|
|
47
47
|
return scale;
|
|
48
|
-
};
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Anchor position for points on a scale. Currently used only for band scales.
|
|
52
|
+
*
|
|
53
|
+
* For band scales, this determines where within the band to position a point:
|
|
54
|
+
* - `'stepStart'` - At the start of the step
|
|
55
|
+
* - `'bandStart'` - At the start of the band
|
|
56
|
+
* - `'middle'` - At the center of the band
|
|
57
|
+
* - `'bandEnd'` - At the end of the band
|
|
58
|
+
* - `'stepEnd'` - At the end of the step
|
|
59
|
+
*/
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Determines which side (left/right) to place scrubber labels based on available space.
|
|
3
|
+
* Prefers right side, switches to left when labels would overflow.
|
|
4
|
+
*/
|
|
5
|
+
export const getLabelPosition = function (beaconX, maxLabelWidth, drawingArea) {
|
|
6
|
+
let xOffset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 16;
|
|
7
|
+
if (drawingArea.width <= 0 || drawingArea.height <= 0) {
|
|
8
|
+
return 'right';
|
|
9
|
+
}
|
|
10
|
+
const availableRightSpace = drawingArea.x + drawingArea.width - beaconX;
|
|
11
|
+
const requiredSpace = maxLabelWidth + xOffset;
|
|
12
|
+
return requiredSpace <= availableRightSpace ? 'right' : 'left';
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Calculates Y positions for all labels avoiding overlaps while maintaining order.
|
|
16
|
+
*/
|
|
17
|
+
export const calculateLabelYPositions = (dimensions, drawingArea, labelHeight, minGap) => {
|
|
18
|
+
if (dimensions.length === 0) {
|
|
19
|
+
return new Map();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Sort by preferred Y values and create working labels
|
|
23
|
+
const sortedLabels = [...dimensions].sort((a, b) => a.preferredY - b.preferredY).map(dim => ({
|
|
24
|
+
seriesId: dim.seriesId,
|
|
25
|
+
preferredY: dim.preferredY,
|
|
26
|
+
finalY: dim.preferredY
|
|
27
|
+
}));
|
|
28
|
+
|
|
29
|
+
// Initial bounds fitting
|
|
30
|
+
const minY = drawingArea.y + labelHeight / 2;
|
|
31
|
+
const maxY = drawingArea.y + drawingArea.height - labelHeight / 2;
|
|
32
|
+
const requiredDistance = labelHeight + minGap;
|
|
33
|
+
for (const label of sortedLabels) {
|
|
34
|
+
// Clamp each label to the drawing area
|
|
35
|
+
label.finalY = Math.max(minY, Math.min(maxY, label.preferredY));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// First pass: push down any overlapping labels
|
|
39
|
+
for (let i = 1; i < sortedLabels.length; i++) {
|
|
40
|
+
const prev = sortedLabels[i - 1];
|
|
41
|
+
const current = sortedLabels[i];
|
|
42
|
+
const minAllowedY = prev.finalY + requiredDistance;
|
|
43
|
+
if (current.finalY < minAllowedY) {
|
|
44
|
+
current.finalY = minAllowedY;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Find collision groups - groups of labels that are tightly packed (gap < minGap between them)
|
|
49
|
+
const collisionGroups = [];
|
|
50
|
+
let currentGroup = [sortedLabels[0]];
|
|
51
|
+
for (let i = 1; i < sortedLabels.length; i++) {
|
|
52
|
+
const prev = sortedLabels[i - 1];
|
|
53
|
+
const current = sortedLabels[i];
|
|
54
|
+
const gap = current.finalY - prev.finalY - labelHeight;
|
|
55
|
+
if (gap < minGap + 0.01) {
|
|
56
|
+
// Labels are touching or very close - part of same collision group
|
|
57
|
+
currentGroup.push(current);
|
|
58
|
+
} else {
|
|
59
|
+
// Gap is large enough - start new group
|
|
60
|
+
collisionGroups.push(currentGroup);
|
|
61
|
+
currentGroup = [current];
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
collisionGroups.push(currentGroup);
|
|
65
|
+
|
|
66
|
+
// Process each collision group - optimize positioning to minimize displacement
|
|
67
|
+
for (const group of collisionGroups) {
|
|
68
|
+
if (group.length === 1) {
|
|
69
|
+
// Single label, already at best position
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const groupLastLabel = group[group.length - 1];
|
|
73
|
+
const groupFirstLabel = group[0];
|
|
74
|
+
const groupOverflow = groupLastLabel.finalY + labelHeight / 2 - (drawingArea.y + drawingArea.height);
|
|
75
|
+
|
|
76
|
+
// Calculate the ideal center point for this group
|
|
77
|
+
const groupPreferredCenter = group.reduce((sum, label) => sum + label.preferredY, 0) / group.length;
|
|
78
|
+
const groupTotalNeeded = group.length * labelHeight + (group.length - 1) * minGap;
|
|
79
|
+
if (groupOverflow <= 0) {
|
|
80
|
+
// Group fits, but let's center it better if possible
|
|
81
|
+
// Calculate how much we can shift up/down to center around preferred positions
|
|
82
|
+
const currentCenter = (groupFirstLabel.finalY + groupLastLabel.finalY) / 2;
|
|
83
|
+
const desiredShift = groupPreferredCenter - currentCenter;
|
|
84
|
+
|
|
85
|
+
// Calculate max shift in each direction
|
|
86
|
+
const maxShiftUp = groupFirstLabel.finalY - minY;
|
|
87
|
+
const maxShiftDown = maxY - groupLastLabel.finalY;
|
|
88
|
+
|
|
89
|
+
// Apply the shift, constrained by boundaries
|
|
90
|
+
const actualShift = Math.max(-maxShiftUp, Math.min(maxShiftDown, desiredShift));
|
|
91
|
+
if (Math.abs(actualShift) > 0.01) {
|
|
92
|
+
for (const label of group) {
|
|
93
|
+
label.finalY += actualShift;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
} else {
|
|
97
|
+
// Group overflows - need to adjust
|
|
98
|
+
const groupStartY = groupFirstLabel.finalY - labelHeight / 2;
|
|
99
|
+
const availableSpace = drawingArea.y + drawingArea.height - groupStartY;
|
|
100
|
+
const maxShiftUp = groupFirstLabel.finalY - minY;
|
|
101
|
+
if (maxShiftUp >= groupOverflow) {
|
|
102
|
+
// Can shift entire group up to fit
|
|
103
|
+
for (const label of group) {
|
|
104
|
+
label.finalY -= groupOverflow;
|
|
105
|
+
}
|
|
106
|
+
} else if (groupTotalNeeded <= availableSpace) {
|
|
107
|
+
// Can't shift enough, but there's room - redistribute with proper spacing
|
|
108
|
+
let currentY = Math.max(minY, groupFirstLabel.finalY - maxShiftUp);
|
|
109
|
+
const gap = (availableSpace - group.length * labelHeight) / Math.max(1, group.length - 1);
|
|
110
|
+
for (const label of group) {
|
|
111
|
+
label.finalY = currentY;
|
|
112
|
+
currentY += labelHeight + gap;
|
|
113
|
+
}
|
|
114
|
+
} else {
|
|
115
|
+
// Not enough space even with compression - compress gaps and fit to bottom
|
|
116
|
+
const compressedGap = Math.max(1, (availableSpace - group.length * labelHeight) / Math.max(1, group.length - 1));
|
|
117
|
+
// Position so last label is at maxY
|
|
118
|
+
let currentY = maxY - (group.length - 1) * (labelHeight + compressedGap);
|
|
119
|
+
currentY = Math.max(minY, currentY);
|
|
120
|
+
for (const label of group) {
|
|
121
|
+
label.finalY = currentY;
|
|
122
|
+
currentY += labelHeight + compressedGap;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const result = new Map();
|
|
128
|
+
for (const label of sortedLabels) {
|
|
129
|
+
result.set(label.seriesId, label.finalY);
|
|
130
|
+
}
|
|
131
|
+
return result;
|
|
132
|
+
};
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
|
|
2
|
+
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
|
|
3
|
+
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
|
|
4
|
+
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
|
|
5
|
+
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
|
|
6
|
+
import { useEffect, useRef } from 'react';
|
|
7
|
+
import { interpolatePath } from 'd3-interpolate-path';
|
|
8
|
+
import { animate, useMotionValue, useTransform } from 'framer-motion';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Default transition configuration used across all chart components.
|
|
12
|
+
*/
|
|
13
|
+
export const defaultTransition = {
|
|
14
|
+
type: 'spring',
|
|
15
|
+
stiffness: 900,
|
|
16
|
+
damping: 120,
|
|
17
|
+
mass: 4
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Duration in seconds for accessory elements to fade in.
|
|
22
|
+
*/
|
|
23
|
+
export const accessoryFadeTransitionDuration = 0.15;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Delay in seconds before accessory elements fade in.
|
|
27
|
+
*/
|
|
28
|
+
export const accessoryFadeTransitionDelay = 0.35;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Hook for path animation state and transitions.
|
|
32
|
+
*
|
|
33
|
+
* @param currentPath - Current target path to animate to
|
|
34
|
+
* @param initialPath - Initial path for enter animation. When provided, the first animation will go from initialPath to currentPath.
|
|
35
|
+
* @param transition - Transition configuration
|
|
36
|
+
* @returns MotionValue containing the current interpolated path string
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* // Simple path transition
|
|
40
|
+
* const animatedPath = usePathTransition({
|
|
41
|
+
* currentPath: d ?? '',
|
|
42
|
+
* transition: {
|
|
43
|
+
* type: 'spring',
|
|
44
|
+
* stiffness: 300,
|
|
45
|
+
* damping: 20
|
|
46
|
+
* }
|
|
47
|
+
* });
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* // Time based animation
|
|
51
|
+
* const animatedPath = usePathTransition({
|
|
52
|
+
* currentPath: targetPath,
|
|
53
|
+
* initialPath: baselinePath,
|
|
54
|
+
* transition: {
|
|
55
|
+
* type: 'tween',
|
|
56
|
+
* duration: 0.3,
|
|
57
|
+
* ease: 'easeInOut'
|
|
58
|
+
* }
|
|
59
|
+
* });
|
|
60
|
+
*/
|
|
61
|
+
export const usePathTransition = _ref => {
|
|
62
|
+
let {
|
|
63
|
+
currentPath,
|
|
64
|
+
initialPath,
|
|
65
|
+
transition = defaultTransition
|
|
66
|
+
} = _ref;
|
|
67
|
+
const isInitialRender = useRef(true);
|
|
68
|
+
const previousPathRef = useRef(initialPath !== null && initialPath !== void 0 ? initialPath : currentPath);
|
|
69
|
+
const targetPathRef = useRef(currentPath);
|
|
70
|
+
const animationRef = useRef(null);
|
|
71
|
+
const progress = useMotionValue(0);
|
|
72
|
+
|
|
73
|
+
// Derive the interpolated path from progress using useTransform
|
|
74
|
+
const interpolatedPath = useTransform(progress, latest => {
|
|
75
|
+
const pathInterpolator = interpolatePath(previousPathRef.current, targetPathRef.current);
|
|
76
|
+
return pathInterpolator(latest);
|
|
77
|
+
});
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
// Only proceed if the target path has actually changed
|
|
80
|
+
if (targetPathRef.current !== currentPath) {
|
|
81
|
+
// Cancel any ongoing animation before starting a new one
|
|
82
|
+
const wasAnimating = !!animationRef.current;
|
|
83
|
+
if (animationRef.current) {
|
|
84
|
+
animationRef.current.cancel();
|
|
85
|
+
animationRef.current = null;
|
|
86
|
+
}
|
|
87
|
+
const currentInterpolatedPath = interpolatedPath.get();
|
|
88
|
+
|
|
89
|
+
// If we were animating and the interpolated path is different from both start and end,
|
|
90
|
+
// use it as the starting point for the next animation (smooth interruption)
|
|
91
|
+
const isInterpolatedPosition = currentInterpolatedPath !== previousPathRef.current && currentInterpolatedPath !== currentPath;
|
|
92
|
+
if (wasAnimating && isInterpolatedPosition) {
|
|
93
|
+
previousPathRef.current = currentInterpolatedPath;
|
|
94
|
+
}
|
|
95
|
+
targetPathRef.current = currentPath;
|
|
96
|
+
progress.set(0);
|
|
97
|
+
animationRef.current = animate(progress, 1, _objectSpread(_objectSpread({}, transition), {}, {
|
|
98
|
+
onComplete: () => {
|
|
99
|
+
previousPathRef.current = currentPath;
|
|
100
|
+
}
|
|
101
|
+
}));
|
|
102
|
+
isInitialRender.current = false;
|
|
103
|
+
}
|
|
104
|
+
return () => {
|
|
105
|
+
if (animationRef.current) {
|
|
106
|
+
animationRef.current.cancel();
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}, [currentPath, transition, progress, interpolatedPath]);
|
|
110
|
+
return interpolatedPath;
|
|
111
|
+
};
|
|
@@ -13,14 +13,15 @@ const noPointerEvents = {
|
|
|
13
13
|
};
|
|
14
14
|
const SparklineInteractiveMarkerDate = /*#__PURE__*/memo(_ref => {
|
|
15
15
|
let {
|
|
16
|
-
getFormattedDate
|
|
16
|
+
getFormattedDate,
|
|
17
|
+
containerOffsetLeft
|
|
17
18
|
} = _ref;
|
|
18
19
|
const [xPos, setXPos] = useState(0);
|
|
19
20
|
const setupRef = useCallback(ref => {
|
|
20
21
|
if (ref) {
|
|
21
|
-
setXPos(ref.offsetLeft + ref.offsetWidth / 2);
|
|
22
|
+
setXPos(ref.offsetLeft + ref.offsetWidth / 2 - containerOffsetLeft);
|
|
22
23
|
}
|
|
23
|
-
}, []);
|
|
24
|
+
}, [containerOffsetLeft]);
|
|
24
25
|
const dateStr = getFormattedDate(xPos);
|
|
25
26
|
|
|
26
27
|
// take up space while loading so when it finishes loading there is no jump
|
|
@@ -48,6 +49,7 @@ function SparklineInteractiveMarkerDatesWithGeneric(_ref2) {
|
|
|
48
49
|
timePeriodGutter = 2
|
|
49
50
|
} = _ref2;
|
|
50
51
|
const [numberOfLabels, setNumberOfLabels] = useState(0);
|
|
52
|
+
const [containerOffsetLeft, setContainerOffsetLeft] = useState(0);
|
|
51
53
|
const getFormattedDate = useDateLookup({
|
|
52
54
|
getMarker,
|
|
53
55
|
formatDate,
|
|
@@ -57,15 +59,17 @@ function SparklineInteractiveMarkerDatesWithGeneric(_ref2) {
|
|
|
57
59
|
if (ref) {
|
|
58
60
|
const numberOfLabelsFromWidth = Math.floor(ref.offsetWidth / labelWidth);
|
|
59
61
|
setNumberOfLabels(Math.max(numberOfLabelsFromWidth, 4));
|
|
62
|
+
setContainerOffsetLeft(ref.offsetLeft);
|
|
60
63
|
}
|
|
61
64
|
}, []);
|
|
62
65
|
const markers = useMemo(() => {
|
|
63
66
|
return times(numberOfLabels).map((_, i) => {
|
|
64
67
|
return /*#__PURE__*/_jsx(SparklineInteractiveMarkerDate, {
|
|
68
|
+
containerOffsetLeft: containerOffsetLeft,
|
|
65
69
|
getFormattedDate: getFormattedDate
|
|
66
70
|
}, i);
|
|
67
71
|
});
|
|
68
|
-
}, [getFormattedDate, numberOfLabels]);
|
|
72
|
+
}, [containerOffsetLeft, getFormattedDate, numberOfLabels]);
|
|
69
73
|
return /*#__PURE__*/_jsx(HStack, {
|
|
70
74
|
ref: setupRef,
|
|
71
75
|
className: fadeInCss,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coinbase/cds-web-visualization",
|
|
3
|
-
"version": "3.4.0-beta.
|
|
3
|
+
"version": "3.4.0-beta.11",
|
|
4
4
|
"description": "Coinbase Design System - Web Sparkline",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
"CHANGELOG"
|
|
39
39
|
],
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@coinbase/cds-common": "^8.
|
|
42
|
-
"@coinbase/cds-lottie-files": "^3.3.
|
|
43
|
-
"@coinbase/cds-utils": "^2.3.
|
|
44
|
-
"@coinbase/cds-web": "^8.
|
|
41
|
+
"@coinbase/cds-common": "^8.36.2",
|
|
42
|
+
"@coinbase/cds-lottie-files": "^3.3.4",
|
|
43
|
+
"@coinbase/cds-utils": "^2.3.5",
|
|
44
|
+
"@coinbase/cds-web": "^8.36.2",
|
|
45
45
|
"react": "^18.3.1",
|
|
46
46
|
"react-dom": "^18.3.1"
|
|
47
47
|
},
|
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
"@babel/preset-env": "^7.28.0",
|
|
59
59
|
"@babel/preset-react": "^7.27.1",
|
|
60
60
|
"@babel/preset-typescript": "^7.27.1",
|
|
61
|
-
"@coinbase/cds-common": "^8.
|
|
62
|
-
"@coinbase/cds-lottie-files": "^3.3.
|
|
63
|
-
"@coinbase/cds-utils": "^2.3.
|
|
64
|
-
"@coinbase/cds-web": "^8.
|
|
61
|
+
"@coinbase/cds-common": "^8.36.2",
|
|
62
|
+
"@coinbase/cds-lottie-files": "^3.3.4",
|
|
63
|
+
"@coinbase/cds-utils": "^2.3.5",
|
|
64
|
+
"@coinbase/cds-web": "^8.36.2",
|
|
65
65
|
"@figma/code-connect": "^1.3.4",
|
|
66
66
|
"@linaria/core": "^3.0.0-beta.22",
|
|
67
67
|
"@types/react": "^18.3.12",
|