diff --git a/packages/x-charts/src/RadarChart/seriesConfig/index.ts b/packages/x-charts/src/RadarChart/seriesConfig/index.ts index d96cd29f5abe4..808b9b848e831 100644 --- a/packages/x-charts/src/RadarChart/seriesConfig/index.ts +++ b/packages/x-charts/src/RadarChart/seriesConfig/index.ts @@ -5,12 +5,14 @@ import { ChartSeriesTypeConfig } from '../../internals/plugins/models/seriesConf import legendGetter from './legend'; import tooltipGetter, { axisTooltipGetter } from './tooltip'; import getSeriesWithDefaultValues from './getSeriesWithDefaultValues'; +import tooltipItemPositionGetter from './tooltipPosition'; export const radarSeriesConfig: ChartSeriesTypeConfig<'radar'> = { colorProcessor: getColor, seriesProcessor: formatter, legendGetter, tooltipGetter, + tooltipItemPositionGetter, axisTooltipGetter, getSeriesWithDefaultValues, radiusExtremumGetter, diff --git a/packages/x-charts/src/RadarChart/seriesConfig/tooltipPosition.ts b/packages/x-charts/src/RadarChart/seriesConfig/tooltipPosition.ts new file mode 100644 index 0000000000000..a6f914c671179 --- /dev/null +++ b/packages/x-charts/src/RadarChart/seriesConfig/tooltipPosition.ts @@ -0,0 +1,65 @@ +import { D3OrdinalScale } from '../../models/axis'; +import { generatePolar2svg } from '../../internals/plugins/featurePlugins/useChartPolarAxis/coordinateTransformation'; +import { getDrawingAreaCenter } from '../../internals/plugins/featurePlugins/useChartPolarAxis'; +import type { TooltipItemPositionGetter } from '../../internals/plugins/models/seriesConfig/tooltipItemPositionGetter.types'; + +const tooltipItemPositionGetter: TooltipItemPositionGetter<'radar'> = (params) => { + const { series, identifier, axesConfig, drawingArea, placement } = params; + + if (!identifier) { + return null; + } + const itemSeries = series.radar?.series[identifier.seriesId]; + + if (itemSeries == null) { + return null; + } + + const { radiusAxes, rotationAxes } = axesConfig; + + if (radiusAxes === undefined || rotationAxes === undefined) { + return null; + } + + // Only one rotation axis is supported for radar charts + const rotationAxis = rotationAxes.axis[rotationAxes.axisIds[0]]; + + const metrics = (rotationAxis.scale.domain() as (string | number)[]) ?? []; + const angles = metrics.map((key) => (rotationAxis.scale as D3OrdinalScale)(key)!); + + const { cx, cy } = getDrawingAreaCenter(drawingArea); + const polar2svg = generatePolar2svg({ cx, cy }); + + const points = itemSeries.data.map((value, dataIndex) => { + const rId = radiusAxes.axisIds[dataIndex]; + const r = radiusAxes.axis[rId].scale(value)!; + + const angle = angles[dataIndex]; + return polar2svg(r, angle); + }); + + if (points.length === 0) { + return null; + } + + const [top, right, bottom, left] = points.reduce( + (acc, [x, y]) => { + return [Math.min(y, acc[0]), Math.max(x, acc[1]), Math.max(y, acc[2]), Math.min(x, acc[3])]; + }, + [Infinity, -Infinity, -Infinity, Infinity], + ); + + switch (placement) { + case 'right': + return { x: right, y: (top + bottom) / 2 }; + case 'bottom': + return { x: (left + right) / 2, y: bottom }; + case 'left': + return { x: left, y: (top + bottom) / 2 }; + case 'top': + default: + return { x: (left + right) / 2, y: top }; + } +}; + +export default tooltipItemPositionGetter; diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartTooltip.selectors.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartTooltip.selectors.ts index df6fa6fd8b49b..dbcd3c3e6e53b 100644 --- a/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartTooltip.selectors.ts +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartInteraction/useChartTooltip.selectors.ts @@ -24,11 +24,22 @@ import { selectorChartsLastInteraction, } from './useChartInteraction.selectors'; import { ChartSeriesConfig } from '../../models/seriesConfig/seriesConfig.types'; -import { AxisId, ChartsXAxisProps, ChartsYAxisProps } from '../../../../models/axis'; +import { + AxisId, + ChartsRadiusAxisProps, + ChartsRotationAxisProps, + ChartsXAxisProps, + ChartsYAxisProps, +} from '../../../../models/axis'; import { ComputeResult } from '../useChartCartesianAxis/computeAxisValue'; import { selectorChartDrawingArea } from '../../corePlugins/useChartDimensions/useChartDimensions.selectors'; import { ChartDrawingArea } from '../../../../hooks/useDrawingArea'; import { isCartesianSeries } from '../../../isCartesian'; +import { + selectorChartRadiusAxis, + selectorChartRotationAxis, +} from '../useChartPolarAxis/useChartPolarAxis.selectors'; +import { ComputeResult as ComputePolarResult } from '../useChartPolarAxis/computeAxisValue'; export const selectorChartsTooltipItem = createSelector( selectorChartsLastInteraction, @@ -47,21 +58,68 @@ export const selectorChartsTooltipItemIsDefined = createSelector( lastInteraction === 'keyboard' ? keyboardItemIsDefined : interactionItemIsDefined, ); +const selectorChartsTooltipAxisConfig = createSelector( + selectorChartsTooltipItem, + selectorChartXAxis, + selectorChartYAxis, + selectorChartRotationAxis, + selectorChartRadiusAxis, + selectorChartSeriesProcessed, + function selectorChartsTooltipAxisConfig( + identifier: ChartItemIdentifierWithData | null, + { axis: xAxis, axisIds: xAxisIds }: ComputeResult, + { axis: yAxis, axisIds: yAxisIds }: ComputeResult, + rotationAxes: ComputePolarResult, + radiusAxes: ComputePolarResult, + series: ProcessedSeries, + ) { + if (!identifier) { + return {}; + } + + const itemSeries = series[identifier.type as T]?.series[identifier.seriesId] as + | ChartSeriesDefaultized + | undefined; + + if (!itemSeries) { + return {}; + } + const axesConfig: TooltipPositionGetterAxesConfig = { + rotationAxes, + radiusAxes, + }; + + const xAxisId: AxisId | undefined = isCartesianSeries(itemSeries) + ? (itemSeries.xAxisId ?? xAxisIds[0]) + : undefined; + const yAxisId: AxisId | undefined = isCartesianSeries(itemSeries) + ? (itemSeries.yAxisId ?? yAxisIds[0]) + : undefined; + + if (xAxisId !== undefined) { + axesConfig.x = xAxis[xAxisId]; + } + if (yAxisId !== undefined) { + axesConfig.y = yAxis[yAxisId]; + } + + return axesConfig; + }, +); + export const selectorChartsTooltipItemPosition = createSelector( selectorChartsTooltipItem, selectorChartDrawingArea, selectorChartSeriesConfig, - selectorChartXAxis, - selectorChartYAxis, selectorChartSeriesProcessed, + selectorChartsTooltipAxisConfig, function selectorChartsTooltipItemPosition( identifier: ChartItemIdentifierWithData | null, drawingArea: ChartDrawingArea, seriesConfig: ChartSeriesConfig, - { axis: xAxis, axisIds: xAxisIds }: ComputeResult, - { axis: yAxis, axisIds: yAxisIds }: ComputeResult, series: ProcessedSeries, + axesConfig: TooltipPositionGetterAxesConfig, placement: 'top' | 'bottom' | 'left' | 'right' = 'top', ) { if (!identifier) { @@ -72,34 +130,17 @@ export const selectorChartsTooltipItemPosition = createSelector( | ChartSeriesDefaultized | undefined; - if (itemSeries) { - const axesConfig: TooltipPositionGetterAxesConfig = {}; - - const xAxisId: AxisId | undefined = isCartesianSeries(itemSeries) - ? (itemSeries.xAxisId ?? xAxisIds[0]) - : undefined; - const yAxisId: AxisId | undefined = isCartesianSeries(itemSeries) - ? (itemSeries.yAxisId ?? yAxisIds[0]) - : undefined; - - if (xAxisId !== undefined) { - axesConfig.x = xAxis[xAxisId]; - } - if (yAxisId !== undefined) { - axesConfig.y = yAxis[yAxisId]; - } - - return ( - seriesConfig[itemSeries.type as T].tooltipItemPositionGetter?.({ - series, - drawingArea, - axesConfig, - identifier, - placement, - }) ?? null - ); + if (!itemSeries) { + return null; } - - return null; + return ( + seriesConfig[itemSeries.type as T].tooltipItemPositionGetter?.({ + series, + drawingArea, + axesConfig, + identifier, + placement, + }) ?? null + ); }, ); diff --git a/packages/x-charts/src/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.selectors.ts b/packages/x-charts/src/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.selectors.ts index a36fe396680cb..76b73b0b5f3c1 100644 --- a/packages/x-charts/src/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.selectors.ts +++ b/packages/x-charts/src/internals/plugins/featurePlugins/useChartPolarAxis/useChartPolarAxis.selectors.ts @@ -1,4 +1,4 @@ -import { createSelector } from '@mui/x-internals/store'; +import { createSelector, createSelectorMemoized } from '@mui/x-internals/store'; import { selectorChartDrawingArea } from '../../corePlugins/useChartDimensions'; import { selectorChartSeriesConfig, @@ -7,6 +7,7 @@ import { import { UseChartPolarAxisSignature } from './useChartPolarAxis.types'; import { ChartState } from '../../models/chart'; import { computeAxisValue } from './computeAxisValue'; +import type { ChartDrawingArea } from '../../../../hooks/useDrawingArea'; export const selectorChartPolarAxisState = (state: ChartState<[], [UseChartPolarAxisSignature]>) => state.polarAxis; @@ -25,7 +26,7 @@ export const selectorChartRawRadiusAxis = createSelector( * The only interesting selectors that merge axis data and zoom if provided. */ -export const selectorChartRotationAxis = createSelector( +export const selectorChartRotationAxis = createSelectorMemoized( selectorChartRawRotationAxis, selectorChartDrawingArea, selectorChartSeriesProcessed, @@ -40,7 +41,7 @@ export const selectorChartRotationAxis = createSelector( }), ); -export const selectorChartRadiusAxis = createSelector( +export const selectorChartRadiusAxis = createSelectorMemoized( selectorChartRawRadiusAxis, selectorChartDrawingArea, selectorChartSeriesProcessed, @@ -55,7 +56,13 @@ export const selectorChartRadiusAxis = createSelector( }), ); -export const selectorChartPolarCenter = createSelector(selectorChartDrawingArea, (drawingArea) => ({ - cx: drawingArea.left + drawingArea.width / 2, - cy: drawingArea.top + drawingArea.height / 2, -})); +export function getDrawingAreaCenter(drawingArea: ChartDrawingArea) { + return { + cx: drawingArea.left + drawingArea.width / 2, + cy: drawingArea.top + drawingArea.height / 2, + }; +} +export const selectorChartPolarCenter = createSelector( + selectorChartDrawingArea, + getDrawingAreaCenter, +); diff --git a/packages/x-charts/src/internals/plugins/models/seriesConfig/tooltipItemPositionGetter.types.ts b/packages/x-charts/src/internals/plugins/models/seriesConfig/tooltipItemPositionGetter.types.ts index 2330fc9be8a8c..cbf399a1cacac 100644 --- a/packages/x-charts/src/internals/plugins/models/seriesConfig/tooltipItemPositionGetter.types.ts +++ b/packages/x-charts/src/internals/plugins/models/seriesConfig/tooltipItemPositionGetter.types.ts @@ -5,18 +5,18 @@ import type { import { ChartsRotationAxisProps, ChartsRadiusAxisProps, - PolarAxisDefaultized, ComputedXAxis, ComputedYAxis, } from '../../../../models/axis'; import { ChartDrawingArea } from '../../../../hooks/useDrawingArea'; import { ProcessedSeries } from '../../corePlugins/useChartSeries'; +import { ComputeResult } from '../../featurePlugins/useChartPolarAxis/computeAxisValue'; export interface TooltipPositionGetterAxesConfig { x?: ComputedXAxis; y?: ComputedYAxis; - rotation?: PolarAxisDefaultized; - radius?: PolarAxisDefaultized; + rotationAxes?: ComputeResult; + radiusAxes?: ComputeResult; } export type TooltipItemPositionGetter = (params: {