@mui/x-charts-pro 8.5.1 → 8.5.3
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/BarChartPro/BarChartPro.d.ts +3 -2
- package/CHANGELOG.md +187 -10
- package/ChartDataProviderPro/ChartDataProviderPro.js +1 -1
- package/ChartZoomSlider/internals/ChartAxisZoomSlider.js +1 -1
- package/ChartZoomSlider/internals/ChartAxisZoomSliderActiveTrack.js +22 -32
- package/ChartZoomSlider/internals/ChartAxisZoomSliderTrack.js +1 -8
- package/ChartZoomSlider/internals/zoom-utils.d.ts +3 -1
- package/ChartZoomSlider/internals/zoom-utils.js +19 -8
- package/ChartsToolbarPro/ChartsToolbarPro.d.ts +2 -1
- package/ChartsToolbarPro/Toolbar.types.d.ts +15 -0
- package/ChartsToolbarPro/Toolbar.types.js +5 -0
- package/FunnelChart/FunnelChart.js +4 -11
- package/FunnelChart/FunnelChart.plugins.d.ts +3 -2
- package/FunnelChart/FunnelChart.plugins.js +2 -1
- package/FunnelChart/FunnelPlot.d.ts +0 -5
- package/FunnelChart/FunnelPlot.js +85 -67
- package/FunnelChart/FunnelSection.js +2 -0
- package/FunnelChart/categoryAxis.types.d.ts +2 -1
- package/FunnelChart/curves/bump.d.ts +3 -5
- package/FunnelChart/curves/bump.js +13 -13
- package/FunnelChart/curves/curve.types.d.ts +14 -1
- package/FunnelChart/curves/getFunnelCurve.d.ts +7 -2
- package/FunnelChart/curves/linear.d.ts +4 -4
- package/FunnelChart/curves/linear.js +42 -34
- package/FunnelChart/curves/pyramid.d.ts +3 -3
- package/FunnelChart/curves/pyramid.js +25 -24
- package/FunnelChart/curves/step-pyramid.d.ts +5 -5
- package/FunnelChart/curves/step-pyramid.js +24 -24
- package/FunnelChart/curves/step.d.ts +3 -3
- package/FunnelChart/curves/step.js +14 -26
- package/FunnelChart/funnelAxisPlugin/computeAxisValue.d.ts +25 -0
- package/FunnelChart/funnelAxisPlugin/computeAxisValue.js +124 -0
- package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.d.ts +3 -0
- package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.js +173 -0
- package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.d.ts +27 -0
- package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.js +5 -0
- package/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.d.ts +56 -0
- package/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.js +27 -0
- package/FunnelChart/labelUtils.d.ts +4 -11
- package/FunnelChart/labelUtils.js +43 -44
- package/FunnelChart/useFunnelChartProps.js +1 -3
- package/Heatmap/Heatmap.d.ts +3 -3
- package/LineChartPro/LineChartPro.d.ts +3 -2
- package/PieChartPro/PieChartPro.d.ts +3 -2
- package/RadarChartPro/RadarChartPro.d.ts +17 -2
- package/ScatterChartPro/ScatterChartPro.d.ts +3 -2
- package/esm/BarChartPro/BarChartPro.d.ts +3 -2
- package/esm/ChartDataProviderPro/ChartDataProviderPro.js +1 -1
- package/esm/ChartZoomSlider/internals/ChartAxisZoomSlider.js +1 -1
- package/esm/ChartZoomSlider/internals/ChartAxisZoomSliderActiveTrack.js +23 -33
- package/esm/ChartZoomSlider/internals/ChartAxisZoomSliderTrack.js +1 -8
- package/esm/ChartZoomSlider/internals/zoom-utils.d.ts +3 -1
- package/esm/ChartZoomSlider/internals/zoom-utils.js +19 -9
- package/esm/ChartsToolbarPro/ChartsToolbarPro.d.ts +2 -1
- package/esm/ChartsToolbarPro/Toolbar.types.d.ts +15 -0
- package/esm/ChartsToolbarPro/Toolbar.types.js +1 -0
- package/esm/FunnelChart/FunnelChart.js +4 -11
- package/esm/FunnelChart/FunnelChart.plugins.d.ts +3 -2
- package/esm/FunnelChart/FunnelChart.plugins.js +3 -2
- package/esm/FunnelChart/FunnelPlot.d.ts +0 -5
- package/esm/FunnelChart/FunnelPlot.js +86 -68
- package/esm/FunnelChart/FunnelSection.js +2 -0
- package/esm/FunnelChart/categoryAxis.types.d.ts +2 -1
- package/esm/FunnelChart/curves/bump.d.ts +3 -5
- package/esm/FunnelChart/curves/bump.js +13 -13
- package/esm/FunnelChart/curves/curve.types.d.ts +14 -1
- package/esm/FunnelChart/curves/getFunnelCurve.d.ts +7 -2
- package/esm/FunnelChart/curves/linear.d.ts +4 -4
- package/esm/FunnelChart/curves/linear.js +42 -34
- package/esm/FunnelChart/curves/pyramid.d.ts +3 -3
- package/esm/FunnelChart/curves/pyramid.js +25 -24
- package/esm/FunnelChart/curves/step-pyramid.d.ts +5 -5
- package/esm/FunnelChart/curves/step-pyramid.js +24 -24
- package/esm/FunnelChart/curves/step.d.ts +3 -3
- package/esm/FunnelChart/curves/step.js +14 -26
- package/esm/FunnelChart/funnelAxisPlugin/computeAxisValue.d.ts +25 -0
- package/esm/FunnelChart/funnelAxisPlugin/computeAxisValue.js +114 -0
- package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.d.ts +3 -0
- package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.js +165 -0
- package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.d.ts +27 -0
- package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.js +1 -0
- package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.d.ts +56 -0
- package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.js +20 -0
- package/esm/FunnelChart/labelUtils.d.ts +4 -11
- package/esm/FunnelChart/labelUtils.js +43 -44
- package/esm/FunnelChart/useFunnelChartProps.js +1 -3
- package/esm/Heatmap/Heatmap.d.ts +3 -3
- package/esm/LineChartPro/LineChartPro.d.ts +3 -2
- package/esm/PieChartPro/PieChartPro.d.ts +3 -2
- package/esm/RadarChartPro/RadarChartPro.d.ts +17 -2
- package/esm/ScatterChartPro/ScatterChartPro.d.ts +3 -2
- package/esm/index.js +1 -1
- package/esm/internals/plugins/useChartProZoom/useChartProZoom.selectors.d.ts +16 -595
- package/esm/internals/plugins/useChartProZoom/useChartProZoom.selectors.js +2 -2
- package/index.js +1 -1
- package/internals/plugins/useChartProZoom/useChartProZoom.selectors.d.ts +16 -595
- package/internals/plugins/useChartProZoom/useChartProZoom.selectors.js +2 -2
- package/package.json +6 -6
|
@@ -1,29 +1,31 @@
|
|
|
1
1
|
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
|
|
2
2
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
3
|
-
const _excluded = ["onItemClick"
|
|
3
|
+
const _excluded = ["onItemClick"];
|
|
4
4
|
import * as React from 'react';
|
|
5
5
|
import PropTypes from 'prop-types';
|
|
6
6
|
import { line as d3Line } from '@mui/x-charts-vendor/d3-shape';
|
|
7
|
-
import { cartesianSeriesTypes } from '@mui/x-charts/internals';
|
|
8
|
-
import { useXAxes, useYAxes } from '@mui/x-charts/hooks';
|
|
7
|
+
import { cartesianSeriesTypes, useSelector, useStore, isBandScale } from '@mui/x-charts/internals';
|
|
9
8
|
import { FunnelSection } from "./FunnelSection.js";
|
|
10
9
|
import { alignLabel, positionLabel } from "./labelUtils.js";
|
|
11
10
|
import { useFunnelSeriesContext } from "../hooks/useFunnelSeries.js";
|
|
12
11
|
import { getFunnelCurve } from "./curves/index.js";
|
|
13
12
|
import { FunnelSectionLabel } from "./FunnelSectionLabel.js";
|
|
13
|
+
import { selectorChartXAxis, selectorChartYAxis, selectorFunnelGap } from "./funnelAxisPlugin/useChartFunnelAxisRendering.selectors.js";
|
|
14
14
|
import { createElement as _createElement } from "react";
|
|
15
15
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
16
16
|
cartesianSeriesTypes.addType('funnel');
|
|
17
|
-
const useAggregatedData =
|
|
17
|
+
const useAggregatedData = () => {
|
|
18
18
|
const seriesData = useFunnelSeriesContext();
|
|
19
|
+
const store = useStore();
|
|
19
20
|
const {
|
|
20
|
-
xAxis,
|
|
21
|
-
xAxisIds
|
|
22
|
-
} =
|
|
21
|
+
axis: xAxis,
|
|
22
|
+
axisIds: xAxisIds
|
|
23
|
+
} = useSelector(store, selectorChartXAxis);
|
|
23
24
|
const {
|
|
24
|
-
yAxis,
|
|
25
|
-
yAxisIds
|
|
26
|
-
} =
|
|
25
|
+
axis: yAxis,
|
|
26
|
+
axisIds: yAxisIds
|
|
27
|
+
} = useSelector(store, selectorChartYAxis);
|
|
28
|
+
const gap = useSelector(store, selectorFunnelGap);
|
|
27
29
|
const allData = React.useMemo(() => {
|
|
28
30
|
if (seriesData === undefined) {
|
|
29
31
|
return [];
|
|
@@ -46,22 +48,28 @@ const useAggregatedData = gap => {
|
|
|
46
48
|
const bandWidth = (isXAxisBand || isYAxisBand) && baseScaleConfig.scale?.bandwidth() || 0;
|
|
47
49
|
const xScale = xAxis[xAxisId].scale;
|
|
48
50
|
const yScale = yAxis[yAxisId].scale;
|
|
49
|
-
const xPosition = (value, bandIndex, stackOffset, useBand) => {
|
|
50
|
-
if (
|
|
51
|
-
const position = xScale(
|
|
51
|
+
const xPosition = (value, bandIndex, bandIdentifier, stackOffset, useBand) => {
|
|
52
|
+
if (isBandScale(xScale)) {
|
|
53
|
+
const position = xScale(bandIdentifier);
|
|
52
54
|
return useBand ? position + bandWidth : position;
|
|
53
55
|
}
|
|
54
|
-
|
|
56
|
+
if (isHorizontal) {
|
|
57
|
+
return xScale(value + (stackOffset || 0)) + bandIndex * gap;
|
|
58
|
+
}
|
|
59
|
+
return xScale(value);
|
|
55
60
|
};
|
|
56
|
-
const yPosition = (value, bandIndex, stackOffset, useBand) => {
|
|
57
|
-
if (
|
|
58
|
-
const position = yScale(
|
|
61
|
+
const yPosition = (value, bandIndex, bandIdentifier, stackOffset, useBand) => {
|
|
62
|
+
if (isBandScale(yScale)) {
|
|
63
|
+
const position = yScale(bandIdentifier);
|
|
59
64
|
return useBand ? position + bandWidth : position;
|
|
60
65
|
}
|
|
61
|
-
|
|
66
|
+
if (isHorizontal) {
|
|
67
|
+
return yScale(value);
|
|
68
|
+
}
|
|
69
|
+
return yScale(value + (stackOffset || 0)) + bandIndex * gap;
|
|
62
70
|
};
|
|
63
|
-
const allY = currentSeries.dataPoints.flatMap((d, dataIndex) => d.flatMap(v => yPosition(v.y, baseScaleConfig.data?.[dataIndex], v.stackOffset, v.useBandWidth)));
|
|
64
|
-
const allX = currentSeries.dataPoints.flatMap((d, dataIndex) => d.flatMap(v => xPosition(v.x, baseScaleConfig.data?.[dataIndex], v.stackOffset, v.useBandWidth)));
|
|
71
|
+
const allY = currentSeries.dataPoints.flatMap((d, dataIndex) => d.flatMap(v => yPosition(v.y, dataIndex, baseScaleConfig.data?.[dataIndex], v.stackOffset, v.useBandWidth)));
|
|
72
|
+
const allX = currentSeries.dataPoints.flatMap((d, dataIndex) => d.flatMap(v => xPosition(v.x, dataIndex, baseScaleConfig.data?.[dataIndex], v.stackOffset, v.useBandWidth)));
|
|
65
73
|
const minPoint = {
|
|
66
74
|
x: Math.min(...allX),
|
|
67
75
|
y: Math.min(...allY)
|
|
@@ -89,21 +97,21 @@ const useAggregatedData = gap => {
|
|
|
89
97
|
min: minPoint,
|
|
90
98
|
max: maxPoint
|
|
91
99
|
});
|
|
92
|
-
const
|
|
100
|
+
const bandPoints = curve({}).processPoints(values.map(v => ({
|
|
101
|
+
x: xPosition(v.x, dataIndex, baseScaleConfig.data?.[dataIndex], v.stackOffset, v.useBandWidth),
|
|
102
|
+
y: yPosition(v.y, dataIndex, baseScaleConfig.data?.[dataIndex], v.stackOffset, v.useBandWidth)
|
|
103
|
+
})));
|
|
104
|
+
const line = d3Line().x(v => v.x).y(v => v.y).curve(curve);
|
|
93
105
|
return {
|
|
94
|
-
d: line(
|
|
106
|
+
d: line(bandPoints),
|
|
95
107
|
color,
|
|
96
108
|
id,
|
|
97
109
|
seriesId,
|
|
98
110
|
dataIndex,
|
|
99
111
|
variant: currentSeries.variant,
|
|
100
112
|
label: sectionLabel !== false && _extends({}, positionLabel(_extends({}, sectionLabel, {
|
|
101
|
-
xPosition,
|
|
102
|
-
yPosition,
|
|
103
113
|
isHorizontal,
|
|
104
|
-
values
|
|
105
|
-
dataIndex,
|
|
106
|
-
baseScaleData: baseScaleConfig.data ?? []
|
|
114
|
+
values: bandPoints
|
|
107
115
|
})), alignLabel(sectionLabel ?? {}), {
|
|
108
116
|
value: valueFormatter ? valueFormatter(currentSeries.data[dataIndex], {
|
|
109
117
|
dataIndex
|
|
@@ -112,53 +120,68 @@ const useAggregatedData = gap => {
|
|
|
112
120
|
};
|
|
113
121
|
});
|
|
114
122
|
});
|
|
115
|
-
return result
|
|
123
|
+
return result;
|
|
116
124
|
}, [seriesData, xAxis, xAxisIds, yAxis, yAxisIds, gap]);
|
|
117
125
|
return allData;
|
|
118
126
|
};
|
|
119
127
|
function FunnelPlot(props) {
|
|
120
128
|
const {
|
|
121
|
-
onItemClick
|
|
122
|
-
gap
|
|
129
|
+
onItemClick
|
|
123
130
|
} = props,
|
|
124
131
|
other = _objectWithoutPropertiesLoose(props, _excluded);
|
|
125
|
-
const data = useAggregatedData(
|
|
132
|
+
const data = useAggregatedData();
|
|
126
133
|
return /*#__PURE__*/_jsxs(React.Fragment, {
|
|
127
|
-
children: [data.map(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
key: id,
|
|
138
|
-
dataIndex: dataIndex,
|
|
139
|
-
seriesId: seriesId,
|
|
140
|
-
variant: variant,
|
|
141
|
-
onClick: onItemClick && (event => {
|
|
142
|
-
onItemClick(event, {
|
|
143
|
-
type: 'funnel',
|
|
134
|
+
children: [data.map(series => {
|
|
135
|
+
if (series.length === 0) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return /*#__PURE__*/_jsx("g", {
|
|
139
|
+
"data-series": series[0].seriesId,
|
|
140
|
+
children: series.map(({
|
|
141
|
+
d,
|
|
142
|
+
color,
|
|
143
|
+
id,
|
|
144
144
|
seriesId,
|
|
145
|
-
dataIndex
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
145
|
+
dataIndex,
|
|
146
|
+
variant
|
|
147
|
+
}) => /*#__PURE__*/_createElement(FunnelSection, _extends({}, other, {
|
|
148
|
+
d: d,
|
|
149
|
+
color: color,
|
|
150
|
+
key: id,
|
|
151
|
+
dataIndex: dataIndex,
|
|
152
|
+
seriesId: seriesId,
|
|
153
|
+
variant: variant,
|
|
154
|
+
onClick: onItemClick && (event => {
|
|
155
|
+
onItemClick(event, {
|
|
156
|
+
type: 'funnel',
|
|
157
|
+
seriesId,
|
|
158
|
+
dataIndex
|
|
159
|
+
});
|
|
160
|
+
})
|
|
161
|
+
})))
|
|
162
|
+
}, series[0].seriesId);
|
|
163
|
+
}), data.map(series => {
|
|
164
|
+
if (series.length === 0) {
|
|
155
165
|
return null;
|
|
156
166
|
}
|
|
157
|
-
return /*#__PURE__*/_jsx(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
167
|
+
return /*#__PURE__*/_jsx("g", {
|
|
168
|
+
"data-series": series[0].seriesId,
|
|
169
|
+
children: series.map(({
|
|
170
|
+
id,
|
|
171
|
+
label,
|
|
172
|
+
seriesId,
|
|
173
|
+
dataIndex
|
|
174
|
+
}) => {
|
|
175
|
+
if (!label || !label.value) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return /*#__PURE__*/_jsx(FunnelSectionLabel, _extends({
|
|
179
|
+
label: label,
|
|
180
|
+
dataIndex: dataIndex,
|
|
181
|
+
seriesId: seriesId
|
|
182
|
+
}, other), id);
|
|
183
|
+
})
|
|
184
|
+
}, series[0].seriesId);
|
|
162
185
|
})]
|
|
163
186
|
});
|
|
164
187
|
}
|
|
@@ -167,11 +190,6 @@ process.env.NODE_ENV !== "production" ? FunnelPlot.propTypes = {
|
|
|
167
190
|
// | These PropTypes are generated from the TypeScript type definitions |
|
|
168
191
|
// | To update them edit the TypeScript types and run "pnpm proptypes" |
|
|
169
192
|
// ----------------------------------------------------------------------
|
|
170
|
-
/**
|
|
171
|
-
* The gap, in pixels, between funnel sections.
|
|
172
|
-
* @default 0
|
|
173
|
-
*/
|
|
174
|
-
gap: PropTypes.number,
|
|
175
193
|
/**
|
|
176
194
|
* Callback fired when a funnel item is clicked.
|
|
177
195
|
* @param {React.MouseEvent<SVGElement, MouseEvent>} event The event source of the callback.
|
|
@@ -53,6 +53,8 @@ const FunnelSection = consumeSlots('MuiFunnelSection', 'funnelSection', {
|
|
|
53
53
|
strokeWidth: isOutlined ? 1.5 : 0,
|
|
54
54
|
cursor: onClick ? 'pointer' : 'unset',
|
|
55
55
|
onClick: onClick,
|
|
56
|
+
"data-highlighted": isHighlighted || undefined,
|
|
57
|
+
"data-faded": isFaded || undefined,
|
|
56
58
|
className: clsx(classes?.root, className, isOutlined ? classes?.outlined : classes?.filled, isHighlighted && classes?.highlighted, isFaded && classes?.faded)
|
|
57
59
|
}, other, {
|
|
58
60
|
ref: ref
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { AxisConfig, ScaleName } from '@mui/x-charts/models';
|
|
2
2
|
import { MakeOptional } from '@mui/x-internals/types';
|
|
3
|
-
export type
|
|
3
|
+
export type FunnelScaleName = Exclude<ScaleName, 'point'>;
|
|
4
|
+
export type CategoryAxis<S extends FunnelScaleName = FunnelScaleName> = S extends FunnelScaleName ? {
|
|
4
5
|
/**
|
|
5
6
|
* The categories to be displayed on the axis.
|
|
6
7
|
* The order of the categories is the order in which they are displayed.
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CurveOptions } from "./curve.types.js";
|
|
1
|
+
import { FunnelCurveGenerator, CurveOptions, Point } from "./curve.types.js";
|
|
3
2
|
/**
|
|
4
3
|
* This is a custom "bump" curve generator.
|
|
5
4
|
* It draws smooth curves for the 4 provided points,
|
|
@@ -8,16 +7,14 @@ import { CurveOptions } from "./curve.types.js";
|
|
|
8
7
|
* The implementation is based on the d3-shape bump curve generator.
|
|
9
8
|
* https://github.com/d3/d3-shape/blob/a82254af78f08799c71d7ab25df557c4872a3c51/src/curve/bump.js
|
|
10
9
|
*/
|
|
11
|
-
export declare class Bump implements
|
|
10
|
+
export declare class Bump implements FunnelCurveGenerator {
|
|
12
11
|
private context;
|
|
13
12
|
private isHorizontal;
|
|
14
|
-
private gap;
|
|
15
13
|
private min;
|
|
16
14
|
private max;
|
|
17
15
|
private points;
|
|
18
16
|
constructor(context: CanvasRenderingContext2D, {
|
|
19
17
|
isHorizontal,
|
|
20
|
-
gap,
|
|
21
18
|
min,
|
|
22
19
|
max,
|
|
23
20
|
isIncreasing
|
|
@@ -26,6 +23,7 @@ export declare class Bump implements CurveGenerator {
|
|
|
26
23
|
areaEnd(): void;
|
|
27
24
|
lineStart(): void;
|
|
28
25
|
lineEnd(): void;
|
|
26
|
+
processPoints(points: Point[]): Point[];
|
|
29
27
|
point(x: number, y: number): void;
|
|
30
28
|
private drawPath;
|
|
31
29
|
private drawHorizontalPath;
|
|
@@ -11,14 +11,12 @@
|
|
|
11
11
|
export class Bump {
|
|
12
12
|
constructor(context, {
|
|
13
13
|
isHorizontal,
|
|
14
|
-
gap,
|
|
15
14
|
min,
|
|
16
15
|
max,
|
|
17
16
|
isIncreasing
|
|
18
17
|
}) {
|
|
19
18
|
this.context = void 0;
|
|
20
19
|
this.isHorizontal = false;
|
|
21
|
-
this.gap = 0;
|
|
22
20
|
this.min = {
|
|
23
21
|
x: 0,
|
|
24
22
|
y: 0
|
|
@@ -30,7 +28,6 @@ export class Bump {
|
|
|
30
28
|
this.points = [];
|
|
31
29
|
this.context = context;
|
|
32
30
|
this.isHorizontal = isHorizontal ?? false;
|
|
33
|
-
this.gap = (gap ?? 0) / 2;
|
|
34
31
|
this.min = min ?? {
|
|
35
32
|
x: 0,
|
|
36
33
|
y: 0
|
|
@@ -50,6 +47,9 @@ export class Bump {
|
|
|
50
47
|
areaEnd() {}
|
|
51
48
|
lineStart() {}
|
|
52
49
|
lineEnd() {}
|
|
50
|
+
processPoints(points) {
|
|
51
|
+
return points;
|
|
52
|
+
}
|
|
53
53
|
point(x, y) {
|
|
54
54
|
this.points.push({
|
|
55
55
|
x,
|
|
@@ -73,34 +73,34 @@ export class Bump {
|
|
|
73
73
|
const [p0, p1, p2, p3] = this.points;
|
|
74
74
|
|
|
75
75
|
// 0 is the top-left corner
|
|
76
|
-
this.context.moveTo(p0.x
|
|
77
|
-
this.context.lineTo(p0.x
|
|
76
|
+
this.context.moveTo(p0.x, p0.y);
|
|
77
|
+
this.context.lineTo(p0.x, p0.y);
|
|
78
78
|
|
|
79
79
|
// Bezier curve to point 1
|
|
80
|
-
this.context.bezierCurveTo((p0.x + p1.x) / 2, p0.y, (p0.x + p1.x) / 2, p1.y, p1.x
|
|
80
|
+
this.context.bezierCurveTo((p0.x + p1.x) / 2, p0.y, (p0.x + p1.x) / 2, p1.y, p1.x, p1.y);
|
|
81
81
|
|
|
82
82
|
// Line to point 2
|
|
83
|
-
this.context.lineTo(p2.x
|
|
83
|
+
this.context.lineTo(p2.x, p2.y);
|
|
84
84
|
|
|
85
85
|
// Bezier curve back to point 3
|
|
86
|
-
this.context.bezierCurveTo((p2.x + p3.x) / 2, p2.y, (p2.x + p3.x) / 2, p3.y, p3.x
|
|
86
|
+
this.context.bezierCurveTo((p2.x + p3.x) / 2, p2.y, (p2.x + p3.x) / 2, p3.y, p3.x, p3.y);
|
|
87
87
|
this.context.closePath();
|
|
88
88
|
}
|
|
89
89
|
drawVerticalPath() {
|
|
90
90
|
const [p0, p1, p2, p3] = this.points;
|
|
91
91
|
|
|
92
92
|
// 0 is the top-right corner
|
|
93
|
-
this.context.moveTo(p0.x, p0.y
|
|
94
|
-
this.context.lineTo(p0.x, p0.y
|
|
93
|
+
this.context.moveTo(p0.x, p0.y);
|
|
94
|
+
this.context.lineTo(p0.x, p0.y);
|
|
95
95
|
|
|
96
96
|
// Bezier curve to point 1
|
|
97
|
-
this.context.bezierCurveTo(p0.x, (p0.y + p1.y) / 2, p1.x, (p0.y + p1.y) / 2, p1.x, p1.y
|
|
97
|
+
this.context.bezierCurveTo(p0.x, (p0.y + p1.y) / 2, p1.x, (p0.y + p1.y) / 2, p1.x, p1.y);
|
|
98
98
|
|
|
99
99
|
// Line to point 2
|
|
100
|
-
this.context.lineTo(p2.x, p2.y
|
|
100
|
+
this.context.lineTo(p2.x, p2.y);
|
|
101
101
|
|
|
102
102
|
// Bezier curve back to point 3
|
|
103
|
-
this.context.bezierCurveTo(p2.x, (p2.y + p3.y) / 2, p3.x, (p2.y + p3.y) / 2, p3.x, p3.y
|
|
103
|
+
this.context.bezierCurveTo(p2.x, (p2.y + p3.y) / 2, p3.x, (p2.y + p3.y) / 2, p3.x, p3.y);
|
|
104
104
|
this.context.closePath();
|
|
105
105
|
}
|
|
106
106
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CurveGenerator } from '@mui/x-charts-vendor/d3-shape';
|
|
1
2
|
export type CurveOptions = {
|
|
2
3
|
/**
|
|
3
4
|
* The gap between each segment.
|
|
@@ -44,4 +45,16 @@ export type FunnelPointShape = 'square' | 'sharp';
|
|
|
44
45
|
export type Point = {
|
|
45
46
|
x: number;
|
|
46
47
|
y: number;
|
|
47
|
-
};
|
|
48
|
+
};
|
|
49
|
+
export interface FunnelCurveGenerator extends CurveGenerator {
|
|
50
|
+
/**
|
|
51
|
+
* Processes the points to create a curve based on the provided options.
|
|
52
|
+
* This does not draw the curve but prepares the points for rendering.
|
|
53
|
+
*
|
|
54
|
+
* @param points The points to process.
|
|
55
|
+
* @param options The options for the curve.
|
|
56
|
+
* @returns The processed points.
|
|
57
|
+
*/
|
|
58
|
+
processPoints(points: Point[], xPosition: PositionGetter, yPosition: PositionGetter): Point[];
|
|
59
|
+
}
|
|
60
|
+
export type PositionGetter = (value: number, bandIndex: number, bandIdentifier: string | number, stackOffset?: number, useBand?: boolean) => number;
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { Path } from '@mui/x-charts-vendor/d3-path';
|
|
2
2
|
import { CurveOptions, FunnelCurveType } from "./curve.types.js";
|
|
3
|
-
|
|
3
|
+
import { Step } from "./step.js";
|
|
4
|
+
import { Linear } from "./linear.js";
|
|
5
|
+
import { Bump } from "./bump.js";
|
|
6
|
+
import { Pyramid } from "./pyramid.js";
|
|
7
|
+
import { StepPyramid } from "./step-pyramid.js";
|
|
8
|
+
export declare const getFunnelCurve: (curve: FunnelCurveType | undefined, options: CurveOptions) => (context: CanvasRenderingContext2D | Path) => Step | Linear | Bump | Pyramid | StepPyramid;
|
|
@@ -1,14 +1,13 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CurveOptions } from "./curve.types.js";
|
|
1
|
+
import { FunnelCurveGenerator, CurveOptions, Point } from "./curve.types.js";
|
|
3
2
|
/**
|
|
4
3
|
* This is a custom "linear" curve generator.
|
|
5
4
|
* It draws straight lines for the 4 provided points,
|
|
6
|
-
* with the option to
|
|
5
|
+
* with the option to properly handling the border radius.
|
|
7
6
|
*
|
|
8
7
|
* The implementation is based on the d3-shape linear curve generator.
|
|
9
8
|
* https://github.com/d3/d3-shape/blob/a82254af78f08799c71d7ab25df557c4872a3c51/src/curve/linear.js
|
|
10
9
|
*/
|
|
11
|
-
export declare class Linear implements
|
|
10
|
+
export declare class Linear implements FunnelCurveGenerator {
|
|
12
11
|
private context;
|
|
13
12
|
private position;
|
|
14
13
|
private sections;
|
|
@@ -36,5 +35,6 @@ export declare class Linear implements CurveGenerator {
|
|
|
36
35
|
lineStart(): void;
|
|
37
36
|
lineEnd(): void;
|
|
38
37
|
protected getBorderRadius(): number | number[];
|
|
38
|
+
processPoints(points: Point[]): Point[];
|
|
39
39
|
point(xIn: number, yIn: number): void;
|
|
40
40
|
}
|
|
@@ -6,7 +6,7 @@ import { lerpX, lerpY } from "./utils.js";
|
|
|
6
6
|
/**
|
|
7
7
|
* This is a custom "linear" curve generator.
|
|
8
8
|
* It draws straight lines for the 4 provided points,
|
|
9
|
-
* with the option to
|
|
9
|
+
* with the option to properly handling the border radius.
|
|
10
10
|
*
|
|
11
11
|
* The implementation is based on the d3-shape linear curve generator.
|
|
12
12
|
* https://github.com/d3/d3-shape/blob/a82254af78f08799c71d7ab25df557c4872a3c51/src/curve/linear.js
|
|
@@ -42,7 +42,7 @@ export class Linear {
|
|
|
42
42
|
this.pointShape = 'square';
|
|
43
43
|
this.context = context;
|
|
44
44
|
this.isHorizontal = isHorizontal ?? false;
|
|
45
|
-
this.gap =
|
|
45
|
+
this.gap = gap ?? 0;
|
|
46
46
|
this.position = position ?? 0;
|
|
47
47
|
this.sections = sections ?? 1;
|
|
48
48
|
this.borderRadius = borderRadius ?? 0;
|
|
@@ -92,7 +92,7 @@ export class Linear {
|
|
|
92
92
|
}
|
|
93
93
|
// Is smallest section and shaped like a triangle
|
|
94
94
|
if (this.position === this.sections - 1 && this.pointShape === 'sharp') {
|
|
95
|
-
return [this.borderRadius];
|
|
95
|
+
return [0, 0, this.borderRadius];
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
// Is smallest section
|
|
@@ -102,32 +102,22 @@ export class Linear {
|
|
|
102
102
|
}
|
|
103
103
|
return 0;
|
|
104
104
|
}
|
|
105
|
-
|
|
106
|
-
this.points.push({
|
|
107
|
-
x: xIn,
|
|
108
|
-
y: yIn
|
|
109
|
-
});
|
|
110
|
-
if (this.points.length < 4) {
|
|
111
|
-
return;
|
|
112
|
-
}
|
|
113
|
-
|
|
105
|
+
processPoints(points) {
|
|
114
106
|
// Add gaps where they are needed.
|
|
115
|
-
|
|
116
|
-
const slopeStart =
|
|
117
|
-
const slopeEnd =
|
|
107
|
+
const processedPoints = points.map((point, index) => {
|
|
108
|
+
const slopeStart = points.at(index <= 1 ? 0 : 3);
|
|
109
|
+
const slopeEnd = points.at(index <= 1 ? 1 : 2);
|
|
118
110
|
if (this.isHorizontal) {
|
|
119
|
-
const yGetter = lerpY(slopeStart.x - this.gap, slopeStart.y, slopeEnd.x
|
|
120
|
-
const xGap = point.x + (index === 0 || index === 3 ? this.gap : -this.gap);
|
|
111
|
+
const yGetter = lerpY(slopeStart.x - this.gap, slopeStart.y, slopeEnd.x, slopeEnd.y);
|
|
121
112
|
return {
|
|
122
|
-
x:
|
|
123
|
-
y: yGetter(
|
|
113
|
+
x: point.x,
|
|
114
|
+
y: yGetter(point.x)
|
|
124
115
|
};
|
|
125
116
|
}
|
|
126
|
-
const xGetter = lerpX(slopeStart.x, slopeStart.y - this.gap, slopeEnd.x, slopeEnd.y
|
|
127
|
-
const yGap = point.y + (index === 0 || index === 3 ? this.gap : -this.gap);
|
|
117
|
+
const xGetter = lerpX(slopeStart.x, slopeStart.y - this.gap, slopeEnd.x, slopeEnd.y);
|
|
128
118
|
return {
|
|
129
|
-
x: xGetter(
|
|
130
|
-
y:
|
|
119
|
+
x: xGetter(point.y),
|
|
120
|
+
y: point.y
|
|
131
121
|
};
|
|
132
122
|
});
|
|
133
123
|
if (this.pointShape === 'sharp') {
|
|
@@ -135,25 +125,43 @@ export class Linear {
|
|
|
135
125
|
// Else the algorithm will break.
|
|
136
126
|
const isLastSection = this.position === this.sections - 1;
|
|
137
127
|
const isFirstSection = this.position === 0;
|
|
128
|
+
let firstPoint = null;
|
|
129
|
+
let secondPoint = null;
|
|
138
130
|
if (isFirstSection && this.isIncreasing) {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
y: (this.max.y + this.min.y) / 2
|
|
142
|
-
} : {
|
|
143
|
-
x: (this.max.x + this.min.x) / 2,
|
|
144
|
-
y: this.max.y + this.gap
|
|
145
|
-
}, this.points[1], this.points[2]];
|
|
131
|
+
firstPoint = processedPoints[1];
|
|
132
|
+
secondPoint = processedPoints[2];
|
|
146
133
|
}
|
|
147
134
|
if (isLastSection && !this.isIncreasing) {
|
|
148
|
-
|
|
149
|
-
|
|
135
|
+
firstPoint = processedPoints[3];
|
|
136
|
+
secondPoint = processedPoints[0];
|
|
137
|
+
}
|
|
138
|
+
if (firstPoint && secondPoint) {
|
|
139
|
+
return [
|
|
140
|
+
// Sharp point at the start
|
|
141
|
+
this.isHorizontal ? {
|
|
142
|
+
x: this.max.x,
|
|
150
143
|
y: (this.max.y + this.min.y) / 2
|
|
151
144
|
} : {
|
|
152
145
|
x: (this.max.x + this.min.x) / 2,
|
|
153
|
-
y: this.max.y
|
|
154
|
-
},
|
|
146
|
+
y: this.max.y
|
|
147
|
+
},
|
|
148
|
+
// Then other points
|
|
149
|
+
firstPoint, secondPoint];
|
|
155
150
|
}
|
|
156
151
|
}
|
|
152
|
+
return processedPoints;
|
|
153
|
+
}
|
|
154
|
+
point(xIn, yIn) {
|
|
155
|
+
this.points.push({
|
|
156
|
+
x: xIn,
|
|
157
|
+
y: yIn
|
|
158
|
+
});
|
|
159
|
+
const isLastSection = this.position === this.sections - 1;
|
|
160
|
+
const isFirstSection = this.position === 0;
|
|
161
|
+
const isSharpPoint = this.pointShape === 'sharp' && (isLastSection && !this.isIncreasing || isFirstSection && this.isIncreasing);
|
|
162
|
+
if (this.points.length < (isSharpPoint ? 3 : 4)) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
157
165
|
borderRadiusPolygon(this.context, this.points, this.getBorderRadius());
|
|
158
166
|
}
|
|
159
167
|
}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CurveOptions } from "./curve.types.js";
|
|
1
|
+
import { FunnelCurveGenerator, CurveOptions, Point } from "./curve.types.js";
|
|
3
2
|
/**
|
|
4
3
|
* This is a custom "pyramid" curve generator.
|
|
5
4
|
* It draws straight lines for the 4 provided points. The slopes are calculated
|
|
6
5
|
* based on the min and max values of the x and y axes.
|
|
7
6
|
* with the option to add a gap between sections while also properly handling the border radius.
|
|
8
7
|
*/
|
|
9
|
-
export declare class Pyramid implements
|
|
8
|
+
export declare class Pyramid implements FunnelCurveGenerator {
|
|
10
9
|
private context;
|
|
11
10
|
private position;
|
|
12
11
|
private sections;
|
|
@@ -32,5 +31,6 @@ export declare class Pyramid implements CurveGenerator {
|
|
|
32
31
|
lineStart(): void;
|
|
33
32
|
lineEnd(): void;
|
|
34
33
|
protected getBorderRadius(): number | number[];
|
|
34
|
+
processPoints(points: Point[]): Point[];
|
|
35
35
|
point(xIn: number, yIn: number): void;
|
|
36
36
|
}
|
|
@@ -88,17 +88,9 @@ export class Pyramid {
|
|
|
88
88
|
}
|
|
89
89
|
return 0;
|
|
90
90
|
}
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
y: yIn
|
|
95
|
-
});
|
|
96
|
-
if (this.points.length < 4) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// Add gaps where they are needed.
|
|
101
|
-
this.points = this.points.map((point, index) => {
|
|
91
|
+
processPoints(points) {
|
|
92
|
+
// Replace funnel points by pyramids ones.
|
|
93
|
+
const processedPoints = points.map((point, index) => {
|
|
102
94
|
if (this.isHorizontal) {
|
|
103
95
|
const slopeEnd = {
|
|
104
96
|
x: this.max.x,
|
|
@@ -109,10 +101,9 @@ export class Pyramid {
|
|
|
109
101
|
y: this.max.y
|
|
110
102
|
};
|
|
111
103
|
const yGetter = lerpY(slopeStart.x, slopeStart.y, slopeEnd.x, slopeEnd.y);
|
|
112
|
-
const xGap = point.x + (index === 0 || index === 3 ? this.gap : -this.gap);
|
|
113
104
|
return {
|
|
114
|
-
x:
|
|
115
|
-
y: yGetter(
|
|
105
|
+
x: point.x,
|
|
106
|
+
y: yGetter(point.x)
|
|
116
107
|
};
|
|
117
108
|
}
|
|
118
109
|
const slopeEnd = {
|
|
@@ -124,10 +115,9 @@ export class Pyramid {
|
|
|
124
115
|
y: this.min.y
|
|
125
116
|
} : this.min;
|
|
126
117
|
const xGetter = lerpX(slopeStart.x, slopeStart.y, slopeEnd.x, slopeEnd.y);
|
|
127
|
-
const yGap = point.y + (index === 0 || index === 3 ? this.gap : -this.gap);
|
|
128
118
|
return {
|
|
129
|
-
x: xGetter(
|
|
130
|
-
y:
|
|
119
|
+
x: xGetter(point.y),
|
|
120
|
+
y: point.y
|
|
131
121
|
};
|
|
132
122
|
});
|
|
133
123
|
|
|
@@ -135,13 +125,24 @@ export class Pyramid {
|
|
|
135
125
|
// Else the algorithm will break.
|
|
136
126
|
const isLastSection = this.position === this.sections - 1;
|
|
137
127
|
const isFirstSection = this.position === 0;
|
|
138
|
-
if (this.
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
128
|
+
if (isFirstSection && this.isIncreasing) {
|
|
129
|
+
return [processedPoints[0], processedPoints[1], processedPoints[2]];
|
|
130
|
+
}
|
|
131
|
+
if (isLastSection && !this.isIncreasing) {
|
|
132
|
+
return [processedPoints[0], processedPoints[1], processedPoints[3]];
|
|
133
|
+
}
|
|
134
|
+
return processedPoints;
|
|
135
|
+
}
|
|
136
|
+
point(xIn, yIn) {
|
|
137
|
+
this.points.push({
|
|
138
|
+
x: xIn,
|
|
139
|
+
y: yIn
|
|
140
|
+
});
|
|
141
|
+
const isLastSection = this.position === this.sections - 1;
|
|
142
|
+
const isFirstSection = this.position === 0;
|
|
143
|
+
const isSharpPoint = isLastSection && !this.isIncreasing || isFirstSection && this.isIncreasing;
|
|
144
|
+
if (this.points.length < (isSharpPoint ? 3 : 4)) {
|
|
145
|
+
return;
|
|
145
146
|
}
|
|
146
147
|
borderRadiusPolygon(this.context, this.points, this.getBorderRadius());
|
|
147
148
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { CurveOptions, Point } from "./curve.types.js";
|
|
1
|
+
import { FunnelCurveGenerator, CurveOptions, Point } from "./curve.types.js";
|
|
3
2
|
/**
|
|
4
3
|
* This is a custom "step-pyramid" curve generator.
|
|
5
4
|
* It creates a step pyramid, which is a step-like shape with static lengths.
|
|
6
5
|
* It has the option to add a gap between sections while also properly handling the border radius.
|
|
7
6
|
*/
|
|
8
|
-
export declare class StepPyramid implements
|
|
7
|
+
export declare class StepPyramid implements FunnelCurveGenerator {
|
|
9
8
|
private context;
|
|
10
9
|
private position;
|
|
11
10
|
private sections;
|
|
@@ -33,7 +32,8 @@ export declare class StepPyramid implements CurveGenerator {
|
|
|
33
32
|
protected getBorderRadius(): number | number[];
|
|
34
33
|
slopeStart(index: number): Point;
|
|
35
34
|
slopeEnd(index: number): Point;
|
|
36
|
-
initialX(index: number): number;
|
|
37
|
-
initialY(index: number): number;
|
|
35
|
+
initialX(index: number, points: Point[]): number;
|
|
36
|
+
initialY(index: number, points: Point[]): number;
|
|
37
|
+
processPoints(points: Point[]): Point[];
|
|
38
38
|
point(xIn: number, yIn: number): void;
|
|
39
39
|
}
|