diff --git a/packages/x-charts/src/BarChart/BarChart.tsx b/packages/x-charts/src/BarChart/BarChart.tsx index 941f724b03039..132a40f058789 100644 --- a/packages/x-charts/src/BarChart/BarChart.tsx +++ b/packages/x-charts/src/BarChart/BarChart.tsx @@ -25,6 +25,7 @@ import { import { ChartsAxisHighlight, ChartsAxisHighlightProps } from '../ChartsAxisHighlight'; import { ChartsClipPath } from '../ChartsClipPath'; import { ChartsAxisSlotsComponent, ChartsAxisSlotComponentProps } from '../models/axis'; +import OnClickHandler from '../OnClickHandler'; export interface BarChartSlotsComponent extends ChartsAxisSlotsComponent, @@ -58,6 +59,7 @@ export interface BarChartProps */ slotProps?: BarChartSlotComponentProps; layout?: BarSeriesType['layout']; + onClick?: (event: any, series: any, itemData: any) => void; } const BarChart = React.forwardRef(function BarChart(props: BarChartProps, ref) { @@ -82,6 +84,7 @@ const BarChart = React.forwardRef(function BarChart(props: BarChartProps, ref) { children, slots, slotProps, + onClick, } = props; const id = useId(); @@ -143,6 +146,7 @@ const BarChart = React.forwardRef(function BarChart(props: BarChartProps, ref) { + {children} @@ -241,6 +245,7 @@ BarChart.propTypes = { right: PropTypes.number, top: PropTypes.number, }), + onClick: PropTypes.func, /** * Indicate which axis to display the right of the charts. * Can be a string (the id of the axis) or an object `ChartsYAxisProps`. diff --git a/packages/x-charts/src/LineChart/LineChart.tsx b/packages/x-charts/src/LineChart/LineChart.tsx index fb8a7855ec353..47fe6e91367d1 100644 --- a/packages/x-charts/src/LineChart/LineChart.tsx +++ b/packages/x-charts/src/LineChart/LineChart.tsx @@ -32,6 +32,7 @@ import { LineHighlightPlotSlotsComponent, LineHighlightPlotSlotComponentProps, } from './LineHighlightPlot'; +import OnClickHandler from '../OnClickHandler'; export interface LineChartSlotsComponent extends ChartsAxisSlotsComponent, @@ -74,6 +75,7 @@ export interface LineChartProps * @default {} */ slotProps?: LineChartSlotComponentProps; + onClick?: (event: MouseEvent, series: any, itemData: any) => void; } const LineChart = React.forwardRef(function LineChart(props: LineChartProps, ref) { const { @@ -97,6 +99,7 @@ const LineChart = React.forwardRef(function LineChart(props: LineChartProps, ref children, slots, slotProps, + onClick, } = props; const id = useId(); @@ -149,6 +152,7 @@ const LineChart = React.forwardRef(function LineChart(props: LineChartProps, ref + {children} @@ -251,6 +255,7 @@ LineChart.propTypes = { right: PropTypes.number, top: PropTypes.number, }), + onClick: PropTypes.func, /** * Indicate which axis to display the right of the charts. * Can be a string (the id of the axis) or an object `ChartsYAxisProps`. diff --git a/packages/x-charts/src/OnClickHandler.tsx b/packages/x-charts/src/OnClickHandler.tsx new file mode 100644 index 0000000000000..47a714438f9cc --- /dev/null +++ b/packages/x-charts/src/OnClickHandler.tsx @@ -0,0 +1,103 @@ +import * as React from 'react'; +import { SVGContext } from './context/DrawingProvider'; +import { + AxisInteractionData, + InteractionContext, + ItemInteractionData, +} from './context/InteractionProvider'; +import { TriggerOptions } from './ChartsTooltip/utils'; +import { CartesianChartSeriesType, ChartSeriesType } from './models/seriesType/config'; +import { SeriesContext } from './context/SeriesContextProvider'; +import { CartesianContext } from './context/CartesianContextProvider'; + +function hasData( + trigger: TriggerOptions, + displayedData: null | AxisInteractionData | ItemInteractionData, +): boolean { + if (trigger === 'item') { + return displayedData !== null; + } + + const hasAxisXData = (displayedData as AxisInteractionData).x !== null; + const hasAxisYData = (displayedData as AxisInteractionData).y !== null; + + return hasAxisXData || hasAxisYData; +} + +interface OnClickHandlerProps { + trigger: any; + onClick?: (event: any, series: any, itemData: any) => void; +} + +function OnClickHandler({ trigger, onClick }: OnClickHandlerProps) { + const svgRef = React.useContext(SVGContext); + const { item, axis } = React.useContext(InteractionContext); + + const series = React.useContext(SeriesContext); + + const { xAxisIds, yAxisIds } = React.useContext(CartesianContext); + + React.useEffect(() => { + const element = svgRef.current; + if (element === null) { + return () => {}; + } + const handleMouseDown = (event: MouseEvent) => { + event.preventDefault(); + if (onClick == null) { + return; + } + + if (trigger === 'item') { + const displayedData = item as ItemInteractionData; + const tooltipHasData = hasData(trigger, displayedData); + + if (tooltipHasData) { + const data = series[displayedData.type]!.series[displayedData.seriesId]; + const displayedLabel = + data.type === 'pie' ? data.data[displayedData.dataIndex!] : data.label; + + onClick(event, displayedLabel, displayedData); + } + } else { + const displayedData = axis as AxisInteractionData; + + if (hasData(trigger, displayedData)) { + const isXaxis = (displayedData.x && displayedData.x.index) !== undefined; + const USED_AXIS_ID = isXaxis ? xAxisIds[0] : yAxisIds[0]; + const dataIndex = isXaxis + ? displayedData.x && displayedData.x.index + : displayedData.y && displayedData.y.index; + + if (dataIndex == null) { + return; + } + const data: any[] = []; + ( + Object.keys(series).filter((seriesType) => + ['bar', 'line', 'scatter'].includes(seriesType), + ) as CartesianChartSeriesType[] + ).forEach((seriesType) => { + series[seriesType]!.seriesOrder.forEach((seriesId) => { + const seriesItem = series[seriesType]!.series[seriesId]; + const axisKey = isXaxis ? seriesItem.xAxisKey : seriesItem.yAxisKey; + if (axisKey === undefined || axisKey === USED_AXIS_ID) { + data.push(series[seriesType]!.series[seriesId]); + } + }); + }); + + onClick(event, data, displayedData); + } + } + }; + + element.addEventListener('mousedown', handleMouseDown); + return () => { + element.removeEventListener('mousedown', handleMouseDown); + }; + }, [axis, item, series, svgRef, trigger, xAxisIds, yAxisIds, onClick]); + + return null; +} +export default OnClickHandler; diff --git a/packages/x-charts/src/PieChart/PieChart.tsx b/packages/x-charts/src/PieChart/PieChart.tsx index a4153bf963941..e39c6be7dfb02 100644 --- a/packages/x-charts/src/PieChart/PieChart.tsx +++ b/packages/x-charts/src/PieChart/PieChart.tsx @@ -21,9 +21,10 @@ import { ChartsLegendSlotsComponent, } from '../ChartsLegend'; import { ChartsAxisHighlight, ChartsAxisHighlightProps } from '../ChartsAxisHighlight'; -import { PiePlot, PiePlotProps, PiePlotSlotComponentProps, PiePlotSlotsComponent } from './PiePlot'; +import { PiePlot, PiePlotSlotComponentProps, PiePlotSlotsComponent } from './PiePlot'; import { PieValueType } from '../models/seriesType/pie'; import { ChartsAxisSlotsComponent, ChartsAxisSlotComponentProps } from '../models/axis'; +import OnClickHandler from '../OnClickHandler'; export interface PieChartSlotsComponent extends ChartsAxisSlotsComponent, @@ -46,7 +47,7 @@ export interface PieChartProps * @deprecated Consider using `slotProps.legend` instead. */ legend?: ChartsLegendProps; - onClick?: PiePlotProps['onClick']; + onClick?: (event: any, series: any, itemData: any) => void; slots?: PieChartSlotsComponent; /** @@ -117,6 +118,7 @@ function PieChart(props: PieChartProps) { + {children} ); diff --git a/packages/x-charts/src/ScatterChart/ScatterChart.tsx b/packages/x-charts/src/ScatterChart/ScatterChart.tsx index 7decd47c93be4..f970e08c53918 100644 --- a/packages/x-charts/src/ScatterChart/ScatterChart.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterChart.tsx @@ -26,6 +26,7 @@ import { } from '../ChartsLegend'; import { ChartsAxisHighlight, ChartsAxisHighlightProps } from '../ChartsAxisHighlight'; import { ChartsAxisSlotsComponent, ChartsAxisSlotComponentProps } from '../models/axis'; +import OnClickHandler from '../OnClickHandler'; export interface ScatterChartSlotsComponent extends ChartsAxisSlotsComponent, @@ -58,6 +59,7 @@ export interface ScatterChartProps * @default {} */ slotProps?: ScatterChartSlotComponentProps; + onClick?: (event: any, series: any, itemData: any) => void; } const ScatterChart = React.forwardRef(function ScatterChart(props: ScatterChartProps, ref) { @@ -80,6 +82,7 @@ const ScatterChart = React.forwardRef(function ScatterChart(props: ScatterChartP children, slots, slotProps, + onClick, } = props; return ( + {children} ); @@ -201,6 +205,7 @@ ScatterChart.propTypes = { right: PropTypes.number, top: PropTypes.number, }), + onClick: PropTypes.func, /** * Indicate which axis to display the right of the charts. * Can be a string (the id of the axis) or an object `ChartsYAxisProps`. diff --git a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx index ba5924156fec0..db145b64c9554 100644 --- a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx +++ b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx @@ -25,6 +25,7 @@ import { LineHighlightPlotSlotComponentProps, } from '../LineChart/LineHighlightPlot'; import { BarPlotSlotsComponent, BarPlotSlotComponentProps } from '../BarChart/BarPlot'; +import OnClickHandler from '../OnClickHandler'; export interface SparkLineChartSlotsComponent extends AreaPlotSlotsComponent, @@ -97,6 +98,7 @@ export interface SparkLineChartProps * @default {} */ slotProps?: SparkLineChartSlotComponentProps; + onClick?: (event: any, series: any, itemData: any) => void; } const SPARKLINE_DEFAULT_MARGIN = { @@ -124,6 +126,7 @@ const SparkLineChart = React.forwardRef(function SparkLineChart(props: SparkLine data, plotType = 'line', valueFormatter = (v: number) => v.toString(), + onClick, area, curve = 'linear', } = props; @@ -166,6 +169,7 @@ const SparkLineChart = React.forwardRef(function SparkLineChart(props: SparkLine axisHighlight?.y === 'none' } > + {plotType === 'bar' && ( )} @@ -234,6 +238,7 @@ SparkLineChart.propTypes = { right: PropTypes.number, top: PropTypes.number, }), + onClick: PropTypes.func, /** * Type of plot used. * @default 'line'