@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.
Files changed (98) hide show
  1. package/BarChartPro/BarChartPro.d.ts +3 -2
  2. package/CHANGELOG.md +187 -10
  3. package/ChartDataProviderPro/ChartDataProviderPro.js +1 -1
  4. package/ChartZoomSlider/internals/ChartAxisZoomSlider.js +1 -1
  5. package/ChartZoomSlider/internals/ChartAxisZoomSliderActiveTrack.js +22 -32
  6. package/ChartZoomSlider/internals/ChartAxisZoomSliderTrack.js +1 -8
  7. package/ChartZoomSlider/internals/zoom-utils.d.ts +3 -1
  8. package/ChartZoomSlider/internals/zoom-utils.js +19 -8
  9. package/ChartsToolbarPro/ChartsToolbarPro.d.ts +2 -1
  10. package/ChartsToolbarPro/Toolbar.types.d.ts +15 -0
  11. package/ChartsToolbarPro/Toolbar.types.js +5 -0
  12. package/FunnelChart/FunnelChart.js +4 -11
  13. package/FunnelChart/FunnelChart.plugins.d.ts +3 -2
  14. package/FunnelChart/FunnelChart.plugins.js +2 -1
  15. package/FunnelChart/FunnelPlot.d.ts +0 -5
  16. package/FunnelChart/FunnelPlot.js +85 -67
  17. package/FunnelChart/FunnelSection.js +2 -0
  18. package/FunnelChart/categoryAxis.types.d.ts +2 -1
  19. package/FunnelChart/curves/bump.d.ts +3 -5
  20. package/FunnelChart/curves/bump.js +13 -13
  21. package/FunnelChart/curves/curve.types.d.ts +14 -1
  22. package/FunnelChart/curves/getFunnelCurve.d.ts +7 -2
  23. package/FunnelChart/curves/linear.d.ts +4 -4
  24. package/FunnelChart/curves/linear.js +42 -34
  25. package/FunnelChart/curves/pyramid.d.ts +3 -3
  26. package/FunnelChart/curves/pyramid.js +25 -24
  27. package/FunnelChart/curves/step-pyramid.d.ts +5 -5
  28. package/FunnelChart/curves/step-pyramid.js +24 -24
  29. package/FunnelChart/curves/step.d.ts +3 -3
  30. package/FunnelChart/curves/step.js +14 -26
  31. package/FunnelChart/funnelAxisPlugin/computeAxisValue.d.ts +25 -0
  32. package/FunnelChart/funnelAxisPlugin/computeAxisValue.js +124 -0
  33. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.d.ts +3 -0
  34. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.js +173 -0
  35. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.d.ts +27 -0
  36. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.js +5 -0
  37. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.d.ts +56 -0
  38. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.js +27 -0
  39. package/FunnelChart/labelUtils.d.ts +4 -11
  40. package/FunnelChart/labelUtils.js +43 -44
  41. package/FunnelChart/useFunnelChartProps.js +1 -3
  42. package/Heatmap/Heatmap.d.ts +3 -3
  43. package/LineChartPro/LineChartPro.d.ts +3 -2
  44. package/PieChartPro/PieChartPro.d.ts +3 -2
  45. package/RadarChartPro/RadarChartPro.d.ts +17 -2
  46. package/ScatterChartPro/ScatterChartPro.d.ts +3 -2
  47. package/esm/BarChartPro/BarChartPro.d.ts +3 -2
  48. package/esm/ChartDataProviderPro/ChartDataProviderPro.js +1 -1
  49. package/esm/ChartZoomSlider/internals/ChartAxisZoomSlider.js +1 -1
  50. package/esm/ChartZoomSlider/internals/ChartAxisZoomSliderActiveTrack.js +23 -33
  51. package/esm/ChartZoomSlider/internals/ChartAxisZoomSliderTrack.js +1 -8
  52. package/esm/ChartZoomSlider/internals/zoom-utils.d.ts +3 -1
  53. package/esm/ChartZoomSlider/internals/zoom-utils.js +19 -9
  54. package/esm/ChartsToolbarPro/ChartsToolbarPro.d.ts +2 -1
  55. package/esm/ChartsToolbarPro/Toolbar.types.d.ts +15 -0
  56. package/esm/ChartsToolbarPro/Toolbar.types.js +1 -0
  57. package/esm/FunnelChart/FunnelChart.js +4 -11
  58. package/esm/FunnelChart/FunnelChart.plugins.d.ts +3 -2
  59. package/esm/FunnelChart/FunnelChart.plugins.js +3 -2
  60. package/esm/FunnelChart/FunnelPlot.d.ts +0 -5
  61. package/esm/FunnelChart/FunnelPlot.js +86 -68
  62. package/esm/FunnelChart/FunnelSection.js +2 -0
  63. package/esm/FunnelChart/categoryAxis.types.d.ts +2 -1
  64. package/esm/FunnelChart/curves/bump.d.ts +3 -5
  65. package/esm/FunnelChart/curves/bump.js +13 -13
  66. package/esm/FunnelChart/curves/curve.types.d.ts +14 -1
  67. package/esm/FunnelChart/curves/getFunnelCurve.d.ts +7 -2
  68. package/esm/FunnelChart/curves/linear.d.ts +4 -4
  69. package/esm/FunnelChart/curves/linear.js +42 -34
  70. package/esm/FunnelChart/curves/pyramid.d.ts +3 -3
  71. package/esm/FunnelChart/curves/pyramid.js +25 -24
  72. package/esm/FunnelChart/curves/step-pyramid.d.ts +5 -5
  73. package/esm/FunnelChart/curves/step-pyramid.js +24 -24
  74. package/esm/FunnelChart/curves/step.d.ts +3 -3
  75. package/esm/FunnelChart/curves/step.js +14 -26
  76. package/esm/FunnelChart/funnelAxisPlugin/computeAxisValue.d.ts +25 -0
  77. package/esm/FunnelChart/funnelAxisPlugin/computeAxisValue.js +114 -0
  78. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.d.ts +3 -0
  79. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.js +165 -0
  80. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.d.ts +27 -0
  81. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.js +1 -0
  82. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.d.ts +56 -0
  83. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.js +20 -0
  84. package/esm/FunnelChart/labelUtils.d.ts +4 -11
  85. package/esm/FunnelChart/labelUtils.js +43 -44
  86. package/esm/FunnelChart/useFunnelChartProps.js +1 -3
  87. package/esm/Heatmap/Heatmap.d.ts +3 -3
  88. package/esm/LineChartPro/LineChartPro.d.ts +3 -2
  89. package/esm/PieChartPro/PieChartPro.d.ts +3 -2
  90. package/esm/RadarChartPro/RadarChartPro.d.ts +17 -2
  91. package/esm/ScatterChartPro/ScatterChartPro.d.ts +3 -2
  92. package/esm/index.js +1 -1
  93. package/esm/internals/plugins/useChartProZoom/useChartProZoom.selectors.d.ts +16 -595
  94. package/esm/internals/plugins/useChartProZoom/useChartProZoom.selectors.js +2 -2
  95. package/index.js +1 -1
  96. package/internals/plugins/useChartProZoom/useChartProZoom.selectors.d.ts +16 -595
  97. package/internals/plugins/useChartProZoom/useChartProZoom.selectors.js +2 -2
  98. 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", "gap"];
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 = gap => {
17
+ const useAggregatedData = () => {
18
18
  const seriesData = useFunnelSeriesContext();
19
+ const store = useStore();
19
20
  const {
20
- xAxis,
21
- xAxisIds
22
- } = useXAxes();
21
+ axis: xAxis,
22
+ axisIds: xAxisIds
23
+ } = useSelector(store, selectorChartXAxis);
23
24
  const {
24
- yAxis,
25
- yAxisIds
26
- } = useYAxes();
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 (isXAxisBand) {
51
- const position = xScale(bandIndex);
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
- return xScale(isHorizontal ? value + (stackOffset || 0) : value);
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 (isYAxisBand) {
58
- const position = yScale(bandIndex);
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
- return yScale(isHorizontal ? value : value + (stackOffset || 0));
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 line = d3Line().x(d => xPosition(d.x, baseScaleConfig.data?.[dataIndex], d.stackOffset, d.useBandWidth)).y(d => yPosition(d.y, baseScaleConfig.data?.[dataIndex], d.stackOffset, d.useBandWidth)).curve(curve);
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(values),
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.flat();
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(gap);
132
+ const data = useAggregatedData();
126
133
  return /*#__PURE__*/_jsxs(React.Fragment, {
127
- children: [data.map(({
128
- d,
129
- color,
130
- id,
131
- seriesId,
132
- dataIndex,
133
- variant
134
- }) => /*#__PURE__*/_createElement(FunnelSection, _extends({}, other, {
135
- d: d,
136
- color: color,
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
- }))), data.map(({
149
- id,
150
- label,
151
- seriesId,
152
- dataIndex
153
- }) => {
154
- if (!label || !label.value) {
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(FunnelSectionLabel, _extends({
158
- label: label,
159
- dataIndex: dataIndex,
160
- seriesId: seriesId
161
- }, other), id);
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 CategoryAxis<S extends ScaleName = ScaleName> = S extends ScaleName ? {
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 { CurveGenerator } from '@mui/x-charts-vendor/d3-shape';
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 CurveGenerator {
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 + this.gap, p0.y);
77
- this.context.lineTo(p0.x + this.gap, p0.y);
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 - this.gap, p1.y);
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 - this.gap, p2.y);
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 + this.gap, p3.y);
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 + this.gap);
94
- this.context.lineTo(p0.x, p0.y + this.gap);
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 - this.gap);
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 - this.gap);
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 + this.gap);
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 { CurveFactory } from '@mui/x-charts-vendor/d3-shape';
1
+ import type { Path } from '@mui/x-charts-vendor/d3-path';
2
2
  import { CurveOptions, FunnelCurveType } from "./curve.types.js";
3
- export declare const getFunnelCurve: (curve: FunnelCurveType | undefined, options: CurveOptions) => CurveFactory;
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 { CurveGenerator } from '@mui/x-charts-vendor/d3-shape';
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 add a gap between sections while also properly handling the border radius.
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 CurveGenerator {
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 add a gap between sections while also properly handling the border radius.
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 = (gap ?? 0) / 2;
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
- point(xIn, yIn) {
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
- this.points = this.points.map((point, index) => {
116
- const slopeStart = this.points.at(index <= 1 ? 0 : 2);
117
- const slopeEnd = this.points.at(index <= 1 ? 1 : 3);
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 - this.gap, slopeEnd.y);
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: xGap,
123
- y: yGetter(xGap)
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 - this.gap);
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(yGap),
130
- y: yGap
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
- this.points = [this.isHorizontal ? {
140
- x: this.max.x + this.gap,
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
- this.points = [this.points[0], this.isHorizontal ? {
149
- x: this.max.x - this.gap,
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 - this.gap
154
- }, this.points[3]];
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 { CurveGenerator } from '@mui/x-charts-vendor/d3-shape';
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 CurveGenerator {
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
- point(xIn, yIn) {
92
- this.points.push({
93
- x: xIn,
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: xGap,
115
- y: yGetter(xGap)
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(yGap),
130
- y: yGap
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.gap <= 0) {
139
- if (isFirstSection && this.isIncreasing) {
140
- this.points = [this.points[0], this.points[1], this.points[2]];
141
- }
142
- if (isLastSection && !this.isIncreasing) {
143
- this.points = [this.points[0], this.points[1], this.points[3]];
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 { CurveGenerator } from '@mui/x-charts-vendor/d3-shape';
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 CurveGenerator {
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
  }