Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[charts] Refactor Tooltip customisation #15154

Merged
merged 30 commits into from
Nov 19, 2024
Merged
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
1ebd04b
Move interaction state in a store for axis values
alexfauquette Oct 28, 2024
b22f8e1
WIP
alexfauquette Oct 29, 2024
9568f13
Merge remote-tracking branch 'upstream/master' into use-selector
alexfauquette Oct 30, 2024
814e4f7
fix error
alexfauquette Oct 30, 2024
1fbb282
UPdate tooltip organisation
alexfauquette Oct 30, 2024
da30115
typing and proptypes
alexfauquette Oct 30, 2024
9110c02
docs:api
alexfauquette Oct 30, 2024
32154bb
remove API pages
alexfauquette Oct 30, 2024
b031bf2
remove console.log
alexfauquette Oct 31, 2024
a69ff27
fix lint
alexfauquette Oct 31, 2024
0495bbd
fix tests
alexfauquette Oct 31, 2024
5a46b31
Merge remote-tracking branch 'upstream/master' into use-selector
alexfauquette Nov 12, 2024
1d6d261
disable eslint
alexfauquette Nov 12, 2024
542cee4
move useStore to internals
alexfauquette Nov 12, 2024
ae21ae1
scripts
alexfauquette Nov 12, 2024
4d28a07
new-tooltip-behavior
alexfauquette Nov 13, 2024
0e91863
docs:typescript
alexfauquette Nov 13, 2024
b4ac1db
missing-useclient
alexfauquette Nov 13, 2024
9af0972
JC feedback
alexfauquette Nov 13, 2024
930746f
fixes
alexfauquette Nov 13, 2024
a070ed6
revert perf suggestion
alexfauquette Nov 13, 2024
adec5bf
correct-fix
alexfauquette Nov 14, 2024
ff1d54b
ts-cleaning
alexfauquette Nov 14, 2024
3584fdf
Merge remote-tracking branch 'upstream/master' into use-selector
alexfauquette Nov 18, 2024
2e99533
typo fix
alexfauquette Nov 18, 2024
9a6c0c9
Merge remote-tracking branch 'upstream/master' into use-selector
alexfauquette Nov 18, 2024
5912bf0
fixes
alexfauquette Nov 18, 2024
aa493bc
scripts
alexfauquette Nov 19, 2024
57abfb6
factorise type def
alexfauquette Nov 19, 2024
57c0d26
Merge remote-tracking branch 'upstream/master' into use-selector
alexfauquette Nov 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Merge remote-tracking branch 'upstream/master' into use-selector
alexfauquette committed Nov 18, 2024
commit 9a6c0c94c6a9f08773b796f07edce02904570699
205 changes: 7 additions & 198 deletions packages/x-charts/src/ChartsAxisHighlight/ChartsAxisHighlight.tsx
Original file line number Diff line number Diff line change
@@ -2,34 +2,10 @@
import * as React from 'react';
import PropTypes from 'prop-types';
import composeClasses from '@mui/utils/composeClasses';
import generateUtilityClass from '@mui/utils/generateUtilityClass';
import generateUtilityClasses from '@mui/utils/generateUtilityClasses';
import { styled } from '@mui/material/styles';
import { getValueToPositionMapper, useXScale, useYScale } from '../hooks/useScale';
import { isBandScale } from '../internals/isBandScale';
import { useSelector } from '../internals/useSelector';
import { useStore } from '../internals/useStore';
import {
selectorChartsInteractionXAxis,
selectorChartsInteractionYAxis,
} from '../context/InteractionSelectors';
import { useDrawingArea } from '../hooks';

export interface ChartsAxisHighlightClasses {
/** Styles applied to the root element. */
root: string;
}

export type ChartsAxisHighlightClassKey = keyof ChartsAxisHighlightClasses;

export function getAxisHighlightUtilityClass(slot: string) {
return generateUtilityClass('MuiChartsAxisHighlight', slot);
}

export const chartsAxisHighlightClasses: ChartsAxisHighlightClasses = generateUtilityClasses(
'MuiChartsAxisHighlight',
['root'],
);
import { getAxisHighlightUtilityClass } from './chartsAxisHighlightClasses';
import ChartsYHighlight from './ChartsYAxisHighlight';
import ChartsXHighlight from './ChartsXAxisHighlight';
import { ChartsAxisHighlightProps } from './ChartsAxisHighlight.types';

const useUtilityClasses = () => {
const slots = {
@@ -39,165 +15,6 @@ const useUtilityClasses = () => {
return composeClasses(slots, getAxisHighlightUtilityClass);
};

export const ChartsAxisHighlightPath = styled('path', {
name: 'MuiChartsAxisHighlight',
slot: 'Root',
overridesResolver: (_, styles) => styles.root,
})<{ ownerState: { axisHighlight: AxisHighlight } }>(({ theme }) => ({
pointerEvents: 'none',
variants: [
{
props: {
axisHighlight: 'band',
},
style: {
fill: 'white',
fillOpacity: 0.1,
...theme.applyStyles('light', {
fill: 'gray',
}),
},
},
{
props: {
axisHighlight: 'line',
},
style: {
strokeDasharray: '5 2',
stroke: '#ffffff',
...theme.applyStyles('light', {
stroke: '#000000',
}),
},
},
],
}));

type AxisHighlight = 'none' | 'line' | 'band';

export type ChartsAxisHighlightProps = {
x?: AxisHighlight;
y?: AxisHighlight;
};

function ChartsXHighlight(props: { type: AxisHighlight }) {
const { type } = props;

const classes = useUtilityClasses();

const { top, height } = useDrawingArea();

const xScale = useXScale();

const store = useStore();
const axisX = useSelector(store, selectorChartsInteractionXAxis);

const getXPosition = getValueToPositionMapper(xScale);

const isBandScaleX = type === 'band' && axisX !== null && isBandScale(xScale);

if (process.env.NODE_ENV !== 'production') {
const isError = isBandScaleX && xScale(axisX.value) === undefined;

if (isError) {
console.error(
[
`MUI X: The position value provided for the axis is not valid for the current scale.`,
`This probably means something is wrong with the data passed to the chart.`,
`The ChartsAxisHighlight component will not be displayed.`,
].join('\n'),
);
}
}

return (
<React.Fragment>
{isBandScaleX && xScale(axisX.value) !== undefined && (
<ChartsAxisHighlightPath
// @ts-expect-error, xScale value is checked in the statement above
d={`M ${xScale(axisX.value) - (xScale.step() - xScale.bandwidth()) / 2} ${
top
} l ${xScale.step()} 0 l 0 ${height} l ${-xScale.step()} 0 Z`}
className={classes.root}
ownerState={{ axisHighlight: 'band' }}
/>
)}

{type === 'line' && axisX !== null && (
<ChartsAxisHighlightPath
d={`M ${getXPosition(axisX.value)} ${top} L ${getXPosition(axisX.value)} ${top + height}`}
className={classes.root}
ownerState={{ axisHighlight: 'line' }}
/>
)}
</React.Fragment>
);
}

ChartsXHighlight.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "pnpm proptypes" |
// ----------------------------------------------------------------------
type: PropTypes.oneOf(['band', 'line', 'none']).isRequired,
} as any;

function ChartsYHighlight(props: { type: AxisHighlight }) {
const { type } = props;

const classes = useUtilityClasses();

const { left, width } = useDrawingArea();

const yScale = useYScale();

const store = useStore();
const axisY = useSelector(store, selectorChartsInteractionYAxis);

const getYPosition = getValueToPositionMapper(yScale);

const isBandScaleY = type === 'band' && axisY !== null && isBandScale(yScale);

if (process.env.NODE_ENV !== 'production') {
const isError = isBandScaleY && yScale(axisY.value) === undefined;

if (isError) {
console.error(
[
`MUI X: The position value provided for the axis is not valid for the current scale.`,
`This probably means something is wrong with the data passed to the chart.`,
`The ChartsAxisHighlight component will not be displayed.`,
].join('\n'),
);
}
}

return (
<React.Fragment>
{isBandScaleY && yScale(axisY.value) !== undefined && (
<ChartsAxisHighlightPath
d={`M ${left} ${
// @ts-expect-error, yScale value is checked in the statement above
yScale(axisY.value) - (yScale.step() - yScale.bandwidth()) / 2
} l 0 ${yScale.step()} l ${width} 0 l 0 ${-yScale.step()} Z`}
className={classes.root}
ownerState={{ axisHighlight: 'band' }}
/>
)}

{type === 'line' && axisY !== null && (
<ChartsAxisHighlightPath
d={`M ${left} ${getYPosition(axisY.value)} L ${left + width} ${getYPosition(
axisY.value,
)}`}
className={classes.root}
ownerState={{ axisHighlight: 'line' }}
/>
)}
</React.Fragment>
);
}

/**
* Demos:
*
@@ -207,22 +24,14 @@ function ChartsYHighlight(props: { type: AxisHighlight }) {
*
* - [ChartsAxisHighlight API](https://mui.com/x/api/charts/charts-axis-highlight/)
*/

ChartsYHighlight.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "pnpm proptypes" |
// ----------------------------------------------------------------------
type: PropTypes.oneOf(['band', 'line', 'none']).isRequired,
} as any;

function ChartsAxisHighlight(props: ChartsAxisHighlightProps) {
const { x: xAxisHighlight, y: yAxisHighlight } = props;

const classes = useUtilityClasses();
return (
<React.Fragment>
{xAxisHighlight && <ChartsXHighlight type={xAxisHighlight} />}
{yAxisHighlight && <ChartsYHighlight type={yAxisHighlight} />}
{xAxisHighlight && <ChartsXHighlight type={xAxisHighlight} classes={classes} />}
{yAxisHighlight && <ChartsYHighlight type={yAxisHighlight} classes={classes} />}
</React.Fragment>
);
}
Original file line number Diff line number Diff line change
@@ -27,7 +27,6 @@ function ChartsOnAxisClickHandler(props: ChartsOnAxisClickHandlerProps) {

const svgRef = useSvgRef();
const series = useSeries();

const store = useStore();

const { xAxisIds, xAxis, yAxisIds, yAxis } = useCartesianContext();
4 changes: 2 additions & 2 deletions packages/x-charts/src/ChartsTooltip/useItemTooltip.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
'use client';
import * as React from 'react';
import { ItemInteractionData } from '../context/InteractionProvider';
import { useSeries } from '../hooks/useSeries';
import { useCartesianContext } from '../context/CartesianProvider';
import { ZAxisContext } from '../context/ZAxisContextProvider';
@@ -12,9 +11,10 @@ import {
} from '../models/seriesType/config';
import { getLabel } from '../internals/getLabel';
import { CommonSeriesType } from '../models/seriesType/common';
import { useSelector } from '../internals/useSelector';
import { selectorChartsInteractionItem } from '../context/InteractionSelectors';
import { useSelector } from '../internals/useSelector';
import { useStore } from '../internals/useStore';
import { ItemInteractionData } from '../internals/plugins/models';

export interface UseItemTooltipReturnValue<T extends ChartSeriesType> {
identifier: ItemInteractionData<T>;
Original file line number Diff line number Diff line change
@@ -12,7 +12,6 @@ import { SeriesId } from '../models/seriesType/common';
import { useDrawingArea, useSvgRef } from '../hooks';
import { useHighlighted } from '../context';
import { useScatterSeries } from '../hooks/useSeries';
import { useStore } from '../internals/useStore';

export type ChartsVoronoiHandlerProps = {
/**
3 changes: 2 additions & 1 deletion packages/x-charts/src/LineChart/LineHighlightPlot.tsx
Original file line number Diff line number Diff line change
@@ -50,11 +50,12 @@ function LineHighlightPlot(props: LineHighlightPlotProps) {
const seriesData = useLineSeries();
const axisData = useCartesianContext();
const drawingArea = useDrawingArea();

const store = useStore();
const xAxisIdentifier = useSelector(store, selectorChartsInteractionXAxis);

const highlightedIndex = xAxisIdentifier?.index;

if (highlightedIndex === undefined) {
return null;
}
4 changes: 2 additions & 2 deletions packages/x-charts/src/LineChart/MarkElement.tsx
Original file line number Diff line number Diff line change
@@ -5,12 +5,12 @@ import { styled } from '@mui/material/styles';
import { symbol as d3Symbol, symbolsFill as d3SymbolsFill } from '@mui/x-charts-vendor/d3-shape';
import { animated, to, useSpring } from '@react-spring/web';
import { getSymbol } from '../internals/getSymbol';
import { useStore } from '../internals/useStore';
import { useSelector } from '../internals/useSelector';
import { useInteractionItemProps } from '../hooks/useInteractionItemProps';
import { useItemHighlighted } from '../context';
import { MarkElementOwnerState, useUtilityClasses } from './markElementClasses';
import { selectorChartsInteractionXAxis } from '../context/InteractionSelectors';
import { useSelector } from '../internals/useSelector';
import { useStore } from '../internals/useStore';

const MarkElementPath = styled(animated.path, {
name: 'MuiMarkElement',
17 changes: 12 additions & 5 deletions packages/x-charts/src/PieChart/PieChart.tsx
Original file line number Diff line number Diff line change
@@ -238,11 +238,18 @@ PieChart.propTypes = {
PropTypes.object,
]),
title: PropTypes.string,
viewBox: PropTypes.shape({
height: PropTypes.number,
width: PropTypes.number,
x: PropTypes.number,
y: PropTypes.number,
/**
* The configuration of the tooltip.
* @see See {@link https://mui.com/x/react-charts/tooltip/ tooltip docs} for more details.
* @default { trigger: 'item' }
*/
tooltip: PropTypes.shape({
axisContent: PropTypes.elementType,
classes: PropTypes.object,
itemContent: PropTypes.elementType,
slotProps: PropTypes.object,
slots: PropTypes.object,
trigger: PropTypes.oneOf(['axis', 'item', 'none']),
}),
/**
* The width of the chart in px. If not defined, it takes the width of the parent element.
6 changes: 3 additions & 3 deletions packages/x-charts/src/ScatterChart/Scatter.tsx
Original file line number Diff line number Diff line change
@@ -13,7 +13,7 @@ import { useSelector } from '../internals/useSelector';
import { D3Scale } from '../models/axis';
import { useHighlighted } from '../context';
import { useDrawingArea } from '../hooks/useDrawingArea';
import { selectorChartsInteractionUseVoronoid } from '../context/InteractionSelectors';
import { selectorChartsInteractionIsVoronoiEnabled } from '../context/InteractionSelectors';

export interface ScatterProps {
series: DefaultizedScatterSeriesType;
@@ -49,9 +49,9 @@ function Scatter(props: ScatterProps) {
const drawingArea = useDrawingArea();

const store = useStore();
const usesVoronoiInteraction = useSelector(store, selectorChartsInteractionUseVoronoid);
const isVoronoiEnabled = useSelector(store, selectorChartsInteractionIsVoronoiEnabled);

const skipInteractionHandlers = usesVoronoiInteraction || series.disableHover;
const skipInteractionHandlers = isVoronoiEnabled || series.disableHover;
const getInteractionItemProps = useInteractionItemProps(skipInteractionHandlers);
const { isFaded, isHighlighted } = useHighlighted();

13 changes: 3 additions & 10 deletions packages/x-charts/src/context/InteractionProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,22 +1,15 @@
'use client';
import * as React from 'react';
import { ChartItemIdentifier, ChartSeriesType } from '../models/seriesType/config';
import { useCharts } from '../internals/useCharts';
import { ChartsStore } from '../internals/plugins/utils/ChartsStore';
import { ChartStore } from '../internals/plugins/utils/ChartStore';

export interface InteractionProviderProps {
children: React.ReactNode;
}

export type ItemInteractionData<T extends ChartSeriesType> = ChartItemIdentifier<T>;

export const ChartsContext = React.createContext<{ store: ChartsStore } | null>(null);
export const ChartsContext = React.createContext<{ store: ChartStore } | null>(null);

if (process.env.NODE_ENV !== 'production') {
ChartsContext.displayName = 'ChartsContext';
}

function InteractionProvider(props: InteractionProviderProps) {
function InteractionProvider(props: React.PropsWithChildren) {
const { children } = props;

const { contextValue } = useCharts();
13 changes: 9 additions & 4 deletions packages/x-charts/src/context/InteractionSelectors.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ChartsState } from '../internals/plugins/models';
import { ChartState } from '../internals/plugins/models';
import { createSelector } from '../internals/plugins/utils/selectors';

function selectInteraction(state: ChartsState) {
function selectInteraction(state: ChartState) {
return state.interaction;
}

@@ -10,6 +10,11 @@ export const selectorChartsInteractionItem = createSelector(
(interaction) => interaction.item,
);

export const selectorChartsInteractionAxis = createSelector(
selectInteraction,
(interaction) => interaction.axis,
);

export const selectorChartsInteractionXAxis = createSelector(
selectInteraction,
(interaction) => interaction.axis.x,
@@ -35,7 +40,7 @@ export const selectorChartsInteractionYAxisIsDefined = createSelector(
(y) => y !== null,
);

export const selectorChartsInteractionUseVoronoid = createSelector(
export const selectorChartsInteractionIsVoronoiEnabled = createSelector(
selectInteraction,
(interaction) => interaction.useVoronoiInteraction,
(interaction) => interaction.isVoronoiEnabled,
);
Loading
You are viewing a condensed version of this merge commit. You can view the full changes here.