@mui/x-charts-pro 8.5.1 → 8.5.2

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 (78) hide show
  1. package/BarChartPro/BarChartPro.d.ts +3 -2
  2. package/CHANGELOG.md +92 -0
  3. package/ChartDataProviderPro/ChartDataProviderPro.js +1 -1
  4. package/ChartZoomSlider/internals/ChartAxisZoomSliderActiveTrack.js +2 -1
  5. package/ChartsToolbarPro/ChartsToolbarPro.d.ts +2 -1
  6. package/ChartsToolbarPro/Toolbar.types.d.ts +15 -0
  7. package/ChartsToolbarPro/Toolbar.types.js +5 -0
  8. package/FunnelChart/FunnelChart.js +4 -11
  9. package/FunnelChart/FunnelChart.plugins.d.ts +3 -2
  10. package/FunnelChart/FunnelChart.plugins.js +2 -1
  11. package/FunnelChart/FunnelPlot.d.ts +0 -5
  12. package/FunnelChart/FunnelPlot.js +30 -28
  13. package/FunnelChart/categoryAxis.types.d.ts +2 -1
  14. package/FunnelChart/curves/bump.d.ts +0 -2
  15. package/FunnelChart/curves/bump.js +10 -13
  16. package/FunnelChart/curves/linear.d.ts +1 -1
  17. package/FunnelChart/curves/linear.js +26 -23
  18. package/FunnelChart/curves/pyramid.js +10 -14
  19. package/FunnelChart/curves/step-pyramid.js +4 -6
  20. package/FunnelChart/curves/step.js +1 -15
  21. package/FunnelChart/funnelAxisPlugin/computeAxisValue.d.ts +25 -0
  22. package/FunnelChart/funnelAxisPlugin/computeAxisValue.js +124 -0
  23. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.d.ts +3 -0
  24. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.js +173 -0
  25. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.d.ts +27 -0
  26. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.js +5 -0
  27. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.d.ts +1482 -0
  28. package/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.js +27 -0
  29. package/FunnelChart/labelUtils.d.ts +3 -2
  30. package/FunnelChart/labelUtils.js +15 -15
  31. package/FunnelChart/positionGetter.d.ts +1 -0
  32. package/FunnelChart/positionGetter.js +5 -0
  33. package/FunnelChart/useFunnelChartProps.js +1 -3
  34. package/Heatmap/Heatmap.d.ts +3 -3
  35. package/LineChartPro/LineChartPro.d.ts +3 -2
  36. package/PieChartPro/PieChartPro.d.ts +3 -2
  37. package/RadarChartPro/RadarChartPro.d.ts +17 -2
  38. package/ScatterChartPro/ScatterChartPro.d.ts +3 -2
  39. package/esm/BarChartPro/BarChartPro.d.ts +3 -2
  40. package/esm/ChartDataProviderPro/ChartDataProviderPro.js +1 -1
  41. package/esm/ChartZoomSlider/internals/ChartAxisZoomSliderActiveTrack.js +2 -1
  42. package/esm/ChartsToolbarPro/ChartsToolbarPro.d.ts +2 -1
  43. package/esm/ChartsToolbarPro/Toolbar.types.d.ts +15 -0
  44. package/esm/ChartsToolbarPro/Toolbar.types.js +1 -0
  45. package/esm/FunnelChart/FunnelChart.js +4 -11
  46. package/esm/FunnelChart/FunnelChart.plugins.d.ts +3 -2
  47. package/esm/FunnelChart/FunnelChart.plugins.js +3 -2
  48. package/esm/FunnelChart/FunnelPlot.d.ts +0 -5
  49. package/esm/FunnelChart/FunnelPlot.js +31 -29
  50. package/esm/FunnelChart/categoryAxis.types.d.ts +2 -1
  51. package/esm/FunnelChart/curves/bump.d.ts +0 -2
  52. package/esm/FunnelChart/curves/bump.js +10 -13
  53. package/esm/FunnelChart/curves/linear.d.ts +1 -1
  54. package/esm/FunnelChart/curves/linear.js +26 -23
  55. package/esm/FunnelChart/curves/pyramid.js +10 -14
  56. package/esm/FunnelChart/curves/step-pyramid.js +4 -6
  57. package/esm/FunnelChart/curves/step.js +1 -15
  58. package/esm/FunnelChart/funnelAxisPlugin/computeAxisValue.d.ts +25 -0
  59. package/esm/FunnelChart/funnelAxisPlugin/computeAxisValue.js +114 -0
  60. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.d.ts +3 -0
  61. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.js +165 -0
  62. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.d.ts +27 -0
  63. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxis.types.js +1 -0
  64. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.d.ts +1482 -0
  65. package/esm/FunnelChart/funnelAxisPlugin/useChartFunnelAxisRendering.selectors.js +20 -0
  66. package/esm/FunnelChart/labelUtils.d.ts +3 -2
  67. package/esm/FunnelChart/labelUtils.js +15 -15
  68. package/esm/FunnelChart/positionGetter.d.ts +1 -0
  69. package/esm/FunnelChart/positionGetter.js +1 -0
  70. package/esm/FunnelChart/useFunnelChartProps.js +1 -3
  71. package/esm/Heatmap/Heatmap.d.ts +3 -3
  72. package/esm/LineChartPro/LineChartPro.d.ts +3 -2
  73. package/esm/PieChartPro/PieChartPro.d.ts +3 -2
  74. package/esm/RadarChartPro/RadarChartPro.d.ts +17 -2
  75. package/esm/ScatterChartPro/ScatterChartPro.d.ts +3 -2
  76. package/esm/index.js +1 -1
  77. package/index.js +1 -1
  78. package/package.json +6 -6
@@ -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;
@@ -113,21 +113,19 @@ export class Linear {
113
113
 
114
114
  // Add gaps where they are needed.
115
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);
116
+ const slopeStart = this.points.at(index <= 1 ? 0 : 3);
117
+ const slopeEnd = this.points.at(index <= 1 ? 1 : 2);
118
118
  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);
119
+ const yGetter = lerpY(slopeStart.x - this.gap, slopeStart.y, slopeEnd.x, slopeEnd.y);
121
120
  return {
122
- x: xGap,
123
- y: yGetter(xGap)
121
+ x: point.x,
122
+ y: yGetter(point.x)
124
123
  };
125
124
  }
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);
125
+ const xGetter = lerpX(slopeStart.x, slopeStart.y - this.gap, slopeEnd.x, slopeEnd.y);
128
126
  return {
129
- x: xGetter(yGap),
130
- y: yGap
127
+ x: xGetter(point.y),
128
+ y: point.y
131
129
  };
132
130
  });
133
131
  if (this.pointShape === 'sharp') {
@@ -135,23 +133,28 @@ export class Linear {
135
133
  // Else the algorithm will break.
136
134
  const isLastSection = this.position === this.sections - 1;
137
135
  const isFirstSection = this.position === 0;
136
+ let firstPoint = null;
137
+ let secondPoint = null;
138
138
  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]];
139
+ firstPoint = this.points[1];
140
+ secondPoint = this.points[2];
146
141
  }
147
142
  if (isLastSection && !this.isIncreasing) {
148
- this.points = [this.points[0], this.isHorizontal ? {
149
- x: this.max.x - this.gap,
143
+ firstPoint = this.points[3];
144
+ secondPoint = this.points[0];
145
+ }
146
+ if (firstPoint && secondPoint) {
147
+ this.points = [
148
+ // Sharp point at the start
149
+ this.isHorizontal ? {
150
+ x: this.max.x,
150
151
  y: (this.max.y + this.min.y) / 2
151
152
  } : {
152
153
  x: (this.max.x + this.min.x) / 2,
153
- y: this.max.y - this.gap
154
- }, this.points[3]];
154
+ y: this.max.y
155
+ },
156
+ // Then other points
157
+ firstPoint, secondPoint];
155
158
  }
156
159
  }
157
160
  borderRadiusPolygon(this.context, this.points, this.getBorderRadius());
@@ -97,7 +97,7 @@ export class Pyramid {
97
97
  return;
98
98
  }
99
99
 
100
- // Add gaps where they are needed.
100
+ // Replace funnel points by pyramids ones.
101
101
  this.points = this.points.map((point, index) => {
102
102
  if (this.isHorizontal) {
103
103
  const slopeEnd = {
@@ -109,10 +109,9 @@ export class Pyramid {
109
109
  y: this.max.y
110
110
  };
111
111
  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
112
  return {
114
- x: xGap,
115
- y: yGetter(xGap)
113
+ x: point.x,
114
+ y: yGetter(point.x)
116
115
  };
117
116
  }
118
117
  const slopeEnd = {
@@ -124,10 +123,9 @@ export class Pyramid {
124
123
  y: this.min.y
125
124
  } : this.min;
126
125
  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
126
  return {
129
- x: xGetter(yGap),
130
- y: yGap
127
+ x: xGetter(point.y),
128
+ y: point.y
131
129
  };
132
130
  });
133
131
 
@@ -135,13 +133,11 @@ export class Pyramid {
135
133
  // Else the algorithm will break.
136
134
  const isLastSection = this.position === this.sections - 1;
137
135
  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
- }
136
+ if (isFirstSection && this.isIncreasing) {
137
+ this.points = [this.points[0], this.points[1], this.points[2]];
138
+ }
139
+ if (isLastSection && !this.isIncreasing) {
140
+ this.points = [this.points[0], this.points[1], this.points[3]];
145
141
  }
146
142
  borderRadiusPolygon(this.context, this.points, this.getBorderRadius());
147
143
  }
@@ -37,7 +37,7 @@ export class StepPyramid {
37
37
  this.points = [];
38
38
  this.context = context;
39
39
  this.isHorizontal = isHorizontal ?? false;
40
- this.gap = (gap ?? 0) / 2;
40
+ this.gap = gap ?? 0;
41
41
  this.position = position ?? 0;
42
42
  this.sections = sections ?? 1;
43
43
  this.borderRadius = borderRadius ?? 0;
@@ -151,25 +151,23 @@ export class StepPyramid {
151
151
  return;
152
152
  }
153
153
 
154
- // Add gaps where they are needed.
154
+ // Replace funnel points by pyramids ones.
155
155
  this.points = this.points.map((point, index) => {
156
156
  const slopeStart = this.slopeStart(index);
157
157
  const slopeEnd = this.slopeEnd(index);
158
158
  if (this.isHorizontal) {
159
159
  const yGetter = lerpY(slopeStart.x, slopeStart.y, slopeEnd.x, slopeEnd.y);
160
- const xGap = point.x + (index === 0 || index === 3 ? this.gap : -this.gap);
161
160
  const xInitial = this.initialX(index);
162
161
  return {
163
- x: xGap,
162
+ x: point.x,
164
163
  y: yGetter(xInitial)
165
164
  };
166
165
  }
167
166
  const xGetter = lerpX(slopeStart.x, slopeStart.y, slopeEnd.x, slopeEnd.y);
168
- const yGap = point.y + (index === 0 || index === 3 ? this.gap : -this.gap);
169
167
  const yInitial = this.initialY(index);
170
168
  return {
171
169
  x: xGetter(yInitial),
172
- y: yGap
170
+ y: point.y
173
171
  };
174
172
  });
175
173
  borderRadiusPolygon(this.context, this.points, this.getBorderRadius());
@@ -32,7 +32,7 @@ export class Step {
32
32
  this.points = [];
33
33
  this.context = context;
34
34
  this.isHorizontal = isHorizontal ?? false;
35
- this.gap = (gap ?? 0) / 2;
35
+ this.gap = gap ?? 0;
36
36
  this.position = position ?? 0;
37
37
  this.sections = sections ?? 1;
38
38
  this.borderRadius = borderRadius ?? 0;
@@ -81,20 +81,6 @@ export class Step {
81
81
  y: index === 1 || index === 2 ? max(allY) : min(allY)
82
82
  };
83
83
  });
84
-
85
- // Add gaps where they are needed.
86
- this.points = this.points.map((point, index) => {
87
- if (this.isHorizontal) {
88
- return {
89
- x: point.x + (index === 0 || index === 3 ? this.gap : -this.gap),
90
- y: point.y
91
- };
92
- }
93
- return {
94
- x: point.x,
95
- y: point.y + (index === 0 || index === 3 ? this.gap : -this.gap)
96
- };
97
- });
98
84
  borderRadiusPolygon(this.context, this.points, this.getBorderRadius());
99
85
  }
100
86
  }
@@ -0,0 +1,25 @@
1
+ import { ChartsAxisProps } from '@mui/x-charts/ChartsAxis';
2
+ import { ChartDrawingArea } from '@mui/x-charts/hooks';
3
+ import { ComputedAxisConfig, ChartSeriesType, ProcessedSeries, ChartSeriesConfig, DefaultedYAxis, DefaultedXAxis } from '@mui/x-charts/internals';
4
+ import { ChartsXAxisProps, ChartsYAxisProps } from '@mui/x-charts/models';
5
+ export declare const xRangeGetter: (drawingArea: ChartDrawingArea, reverse?: boolean, removedSpace?: number) => [number, number];
6
+ export declare const yRangeGetter: (drawingArea: ChartDrawingArea, reverse?: boolean, removedSpace?: number) => [number, number];
7
+ export type ComputeResult<T extends ChartsAxisProps> = {
8
+ axis: ComputedAxisConfig<T>;
9
+ axisIds: string[];
10
+ };
11
+ type ComputeCommonParams<T extends ChartSeriesType = 'funnel'> = {
12
+ drawingArea: ChartDrawingArea;
13
+ formattedSeries: ProcessedSeries<T>;
14
+ seriesConfig: ChartSeriesConfig<T>;
15
+ gap: number;
16
+ };
17
+ export declare function computeAxisValue(options: ComputeCommonParams<'funnel'> & {
18
+ axis?: DefaultedYAxis[];
19
+ axisDirection: 'y';
20
+ }): ComputeResult<ChartsYAxisProps>;
21
+ export declare function computeAxisValue(options: ComputeCommonParams<'funnel'> & {
22
+ axis?: DefaultedXAxis[];
23
+ axisDirection: 'x';
24
+ }): ComputeResult<ChartsXAxisProps>;
25
+ export {};
@@ -0,0 +1,114 @@
1
+ import _extends from "@babel/runtime/helpers/esm/extends";
2
+ import { scaleBand } from '@mui/x-charts-vendor/d3-scale';
3
+ import { getAxisExtremum, isBandScaleConfig, getScale, getColorScale, getOrdinalColorScale, getTickNumber, scaleTickNumberByRange, getCartesianAxisTriggerTooltip, isDateData, createDateFormatter } from '@mui/x-charts/internals';
4
+ export const xRangeGetter = (drawingArea, reverse, removedSpace = 0) => {
5
+ const range = [drawingArea.left, drawingArea.left + drawingArea.width - removedSpace];
6
+ return reverse ? [range[1], range[0]] : [range[0], range[1]];
7
+ };
8
+ export const yRangeGetter = (drawingArea, reverse, removedSpace = 0) => {
9
+ const range = [drawingArea.top + drawingArea.height - removedSpace, drawingArea.top];
10
+ return reverse ? [range[1], range[0]] : [range[0], range[1]];
11
+ };
12
+ function getRange(drawingArea, axisDirection, axis, removedSpace = 0) {
13
+ return axisDirection === 'x' ? xRangeGetter(drawingArea, axis.reverse, removedSpace) : yRangeGetter(drawingArea, axis.reverse, removedSpace);
14
+ }
15
+ export function computeAxisValue({
16
+ drawingArea,
17
+ formattedSeries,
18
+ axis: allAxis,
19
+ seriesConfig,
20
+ axisDirection,
21
+ gap
22
+ }) {
23
+ if (allAxis === undefined) {
24
+ return {
25
+ axis: {},
26
+ axisIds: []
27
+ };
28
+ }
29
+ const axisIdsTriggeringTooltip = getCartesianAxisTriggerTooltip(axisDirection, seriesConfig, formattedSeries, allAxis[0].id);
30
+ const completeAxis = {};
31
+ allAxis.forEach((eachAxis, axisIndex) => {
32
+ const axis = eachAxis;
33
+ let range = getRange(drawingArea, axisDirection, axis);
34
+ const [minData, maxData] = getAxisExtremum(axis, axisDirection, seriesConfig, axisIndex, formattedSeries);
35
+ const triggerTooltip = !axis.ignoreTooltip && axisIdsTriggeringTooltip.has(axis.id);
36
+ const data = axis.data ?? [];
37
+ if (isBandScaleConfig(axis)) {
38
+ // Reverse range because ordinal scales are presented from top to bottom on y-axis
39
+ const scaleRange = axisDirection === 'y' ? [range[1], range[0]] : range;
40
+ const rangeSpace = Math.abs(range[1] - range[0]);
41
+ const N = axis.data.length;
42
+ const bandWidth = (rangeSpace - gap * (N - 1)) / N;
43
+ const step = bandWidth + gap;
44
+ completeAxis[axis.id] = _extends({
45
+ offset: 0,
46
+ height: 0,
47
+ categoryGapRatio: 0,
48
+ barGapRatio: 0,
49
+ triggerTooltip
50
+ }, axis, {
51
+ data,
52
+ scale: scaleBand(axis.data, scaleRange).paddingInner(gap / step).paddingOuter(0),
53
+ tickNumber: axis.data.length,
54
+ colorScale: axis.colorMap && (axis.colorMap.type === 'ordinal' ? getOrdinalColorScale(_extends({
55
+ values: axis.data
56
+ }, axis.colorMap)) : getColorScale(axis.colorMap))
57
+ });
58
+ if (isDateData(axis.data)) {
59
+ const dateFormatter = createDateFormatter(axis, scaleRange);
60
+ completeAxis[axis.id].valueFormatter = axis.valueFormatter ?? dateFormatter;
61
+ }
62
+ }
63
+ if (axis.scaleType === 'band') {
64
+ return;
65
+ }
66
+ if (axis.scaleType === 'point') {
67
+ throw new Error('Point scale is not supported in FunnelChart. Please use band scale instead.');
68
+ }
69
+ const isHorizontal = Object.values(formattedSeries.funnel?.series ?? {}).some(s => s.layout === 'horizontal');
70
+ if (isHorizontal ? axisDirection === 'x' : axisDirection === 'y') {
71
+ // For linear scale replacing the band scale, we remove the space needed for gap from the scale range.
72
+ const itemNumber = formattedSeries.funnel?.series[formattedSeries.funnel.seriesOrder[0]].data.length ?? 0;
73
+ const spaceToRemove = gap * (itemNumber - 1);
74
+ range = getRange(drawingArea, axisDirection, axis, spaceToRemove);
75
+ }
76
+ const scaleType = axis.scaleType ?? 'linear';
77
+ const domainLimit = axis.domainLimit ?? 'nice';
78
+ const axisExtremums = [axis.min ?? minData, axis.max ?? maxData];
79
+ if (typeof domainLimit === 'function') {
80
+ const {
81
+ min,
82
+ max
83
+ } = domainLimit(minData, maxData);
84
+ axisExtremums[0] = min;
85
+ axisExtremums[1] = max;
86
+ }
87
+ const rawTickNumber = getTickNumber(_extends({}, axis, {
88
+ range,
89
+ domain: axisExtremums
90
+ }));
91
+ const tickNumber = scaleTickNumberByRange(rawTickNumber, range);
92
+ const scale = getScale(scaleType, axisExtremums, range);
93
+ const finalScale = domainLimit === 'nice' ? scale.nice(rawTickNumber) : scale;
94
+ const [minDomain, maxDomain] = finalScale.domain();
95
+ const domain = [axis.min ?? minDomain, axis.max ?? maxDomain];
96
+ completeAxis[axis.id] = _extends({
97
+ offset: 0,
98
+ height: 0,
99
+ triggerTooltip
100
+ }, axis, {
101
+ data,
102
+ scaleType: scaleType,
103
+ scale: finalScale.domain(domain),
104
+ tickNumber,
105
+ colorScale: axis.colorMap && getColorScale(axis.colorMap)
106
+ });
107
+ });
108
+ return {
109
+ axis: completeAxis,
110
+ axisIds: allAxis.map(({
111
+ id
112
+ }) => id)
113
+ };
114
+ }
@@ -0,0 +1,3 @@
1
+ import { ChartPlugin } from '@mui/x-charts/internals';
2
+ import { UseChartFunnelAxisSignature } from "./useChartFunnelAxis.types.js";
3
+ export declare const useChartFunnelAxis: ChartPlugin<UseChartFunnelAxisSignature>;
@@ -0,0 +1,165 @@
1
+ 'use client';
2
+
3
+ import _extends from "@babel/runtime/helpers/esm/extends";
4
+ import * as React from 'react';
5
+ import { warnOnce } from '@mui/x-internals/warning';
6
+ import { getSVGPoint, getCartesianAxisIndex, selectorChartDrawingArea, selectorChartSeriesProcessed, selectorChartsInteractionIsInitialized, useSelector, defaultizeXAxis, defaultizeYAxis } from '@mui/x-charts/internals';
7
+ import { selectorChartXAxis, selectorChartYAxis } from "./useChartFunnelAxisRendering.selectors.js";
8
+ export const useChartFunnelAxis = ({
9
+ params,
10
+ store,
11
+ seriesConfig,
12
+ svgRef,
13
+ instance
14
+ }) => {
15
+ const {
16
+ xAxis,
17
+ yAxis,
18
+ dataset,
19
+ gap
20
+ } = params;
21
+ if (process.env.NODE_ENV !== 'production') {
22
+ const ids = [...(xAxis ?? []), ...(yAxis ?? [])].filter(axis => axis.id).map(axis => axis.id);
23
+ const duplicates = new Set(ids.filter((id, index) => ids.indexOf(id) !== index));
24
+ if (duplicates.size > 0) {
25
+ warnOnce([`MUI X Charts: The following axis ids are duplicated: ${Array.from(duplicates).join(', ')}.`, `Please make sure that each axis has a unique id.`].join('\n'), 'error');
26
+ }
27
+ }
28
+ const drawingArea = useSelector(store, selectorChartDrawingArea);
29
+ const isInteractionEnabled = useSelector(store, selectorChartsInteractionIsInitialized);
30
+ const isFirstRender = React.useRef(true);
31
+ React.useEffect(() => {
32
+ if (isFirstRender.current) {
33
+ isFirstRender.current = false;
34
+ return;
35
+ }
36
+ store.update(prev => _extends({}, prev, {
37
+ funnel: {
38
+ gap: gap ?? 0
39
+ },
40
+ cartesianAxis: _extends({}, prev.cartesianAxis, {
41
+ x: defaultizeXAxis(xAxis, dataset),
42
+ y: defaultizeYAxis(yAxis, dataset)
43
+ })
44
+ }));
45
+ }, [seriesConfig, drawingArea, xAxis, yAxis, dataset, store, gap]);
46
+ React.useEffect(() => {
47
+ const element = svgRef.current;
48
+ if (!isInteractionEnabled || element === null || params.disableAxisListener) {
49
+ return () => {};
50
+ }
51
+ const handleOut = () => {
52
+ instance.cleanInteraction?.();
53
+ };
54
+ const handleMove = event => {
55
+ const target = 'targetTouches' in event ? event.targetTouches[0] : event;
56
+ const svgPoint = getSVGPoint(element, target);
57
+ if (!instance.isPointInside(svgPoint.x, svgPoint.y, event.target)) {
58
+ instance.cleanInteraction?.();
59
+ return;
60
+ }
61
+ instance.setPointerCoordinate?.(svgPoint);
62
+ };
63
+ const handleDown = event => {
64
+ const target = event.currentTarget;
65
+ if (!target) {
66
+ return;
67
+ }
68
+ if ('hasPointerCapture' in target && target.hasPointerCapture(event.pointerId)) {
69
+ target.releasePointerCapture(event.pointerId);
70
+ }
71
+ };
72
+ element.addEventListener('pointerdown', handleDown);
73
+ element.addEventListener('pointermove', handleMove);
74
+ element.addEventListener('pointercancel', handleOut);
75
+ element.addEventListener('pointerleave', handleOut);
76
+ return () => {
77
+ element.removeEventListener('pointerdown', handleDown);
78
+ element.removeEventListener('pointermove', handleMove);
79
+ element.removeEventListener('pointercancel', handleOut);
80
+ element.removeEventListener('pointerleave', handleOut);
81
+ };
82
+ }, [svgRef, instance, params.disableAxisListener, isInteractionEnabled]);
83
+ React.useEffect(() => {
84
+ const element = svgRef.current;
85
+ const onAxisClick = params.onAxisClick;
86
+ if (element === null || !onAxisClick) {
87
+ return () => {};
88
+ }
89
+ const handleMouseClick = event => {
90
+ event.preventDefault();
91
+ const {
92
+ axis: xAxisWithScale,
93
+ axisIds: xAxisIds
94
+ } = selectorChartXAxis(store.value);
95
+ const {
96
+ axis: yAxisWithScale,
97
+ axisIds: yAxisIds
98
+ } = selectorChartYAxis(store.value);
99
+ const processedSeries = selectorChartSeriesProcessed(store.value);
100
+ const usedXAxis = xAxisIds[0];
101
+ const usedYAxis = yAxisIds[0];
102
+ let dataIndex = null;
103
+ let isXAxis = false;
104
+ const svgPoint = getSVGPoint(element, event);
105
+ const xIndex = getCartesianAxisIndex(xAxisWithScale[usedXAxis], svgPoint.x);
106
+ isXAxis = xIndex !== -1;
107
+ dataIndex = isXAxis ? xIndex : getCartesianAxisIndex(yAxisWithScale[usedYAxis], svgPoint.y);
108
+ const USED_AXIS_ID = isXAxis ? xAxisIds[0] : yAxisIds[0];
109
+ if (dataIndex == null || dataIndex === -1) {
110
+ return;
111
+ }
112
+
113
+ // The .data exist because otherwise the dataIndex would be null or -1.
114
+ const axisValue = (isXAxis ? xAxisWithScale : yAxisWithScale)[USED_AXIS_ID].data[dataIndex];
115
+ const seriesValues = {};
116
+ processedSeries.funnel?.seriesOrder.forEach(seriesId => {
117
+ const seriesItem = processedSeries.funnel.series[seriesId];
118
+ const providedXAxisId = seriesItem.xAxisId;
119
+ const providedYAxisId = seriesItem.yAxisId;
120
+ const axisKey = isXAxis ? providedXAxisId : providedYAxisId;
121
+ if (axisKey === undefined || axisKey === USED_AXIS_ID) {
122
+ seriesValues[seriesId] = seriesItem.data[dataIndex].value;
123
+ }
124
+ });
125
+ onAxisClick(event, {
126
+ dataIndex,
127
+ axisValue,
128
+ seriesValues
129
+ });
130
+ };
131
+ element.addEventListener('click', handleMouseClick);
132
+ return () => {
133
+ element.removeEventListener('click', handleMouseClick);
134
+ };
135
+ }, [params.onAxisClick, svgRef, store]);
136
+ return {};
137
+ };
138
+ useChartFunnelAxis.params = {
139
+ xAxis: true,
140
+ yAxis: true,
141
+ gap: true,
142
+ dataset: true,
143
+ onAxisClick: true,
144
+ disableAxisListener: true
145
+ };
146
+ useChartFunnelAxis.getDefaultizedParams = ({
147
+ params
148
+ }) => {
149
+ return _extends({}, params, {
150
+ gap: params.gap ?? 0,
151
+ defaultizedXAxis: defaultizeXAxis(params.xAxis, params.dataset),
152
+ defaultizedYAxis: defaultizeYAxis(params.yAxis, params.dataset)
153
+ });
154
+ };
155
+ useChartFunnelAxis.getInitialState = params => {
156
+ return {
157
+ funnel: {
158
+ gap: params.gap ?? 0
159
+ },
160
+ cartesianAxis: {
161
+ x: params.defaultizedXAxis,
162
+ y: params.defaultizedYAxis
163
+ }
164
+ };
165
+ };
@@ -0,0 +1,27 @@
1
+ import { ChartPluginSignature, UseChartCartesianAxisDefaultizedParameters, UseChartCartesianAxisParameters, UseChartCartesianAxisState, UseChartInteractionSignature, UseChartSeriesSignature, ChartsAxisData } from '@mui/x-charts/internals';
2
+ export type UseChartFunnelAxisSignature = ChartPluginSignature<{
3
+ params: Omit<UseChartCartesianAxisParameters, 'onAxisClick'> & {
4
+ /**
5
+ * The gap, in pixels, between funnel sections.
6
+ * @default 0
7
+ */
8
+ gap?: number;
9
+ /**
10
+ * The function called for onClick events.
11
+ * The second argument contains information about all funnel elements at the current position.
12
+ * @param {MouseEvent} event The mouse event recorded on the `<svg/>` element.
13
+ * @param {null | ChartsAxisData} data The data about the clicked axis and items associated with it.
14
+ */
15
+ onAxisClick?: (event: MouseEvent, data: null | ChartsAxisData) => void;
16
+ };
17
+ defaultizedParams: UseChartCartesianAxisDefaultizedParameters & {
18
+ gap: number;
19
+ };
20
+ state: Pick<UseChartCartesianAxisState, 'cartesianAxis'> & {
21
+ funnel: {
22
+ gap: number;
23
+ };
24
+ };
25
+ dependencies: [UseChartSeriesSignature<'funnel'>];
26
+ optionalDependencies: [UseChartInteractionSignature];
27
+ }>;