diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fcd37cfc7b6..de859e4baad7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,92 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## 7.2.0 + +_Apr 12, 2024_ + +We'd like to offer a big thanks to the 12 contributors who made this release possible. Here are some highlights ✨: + +- 🎨 Make grid colors customizable through the MUI themes API +- 🌍 Improve French (fr-FR), German (de-DE), and Swedish (sv-SE) locales on the Data Grid and Pickers +- 🐞 Bugfixes +- 📚 Documentation improvements + +### Data Grid + +#### `@mui/x-data-grid@7.2.0` + +- [DataGrid] Add missing `api` property to `GridCallbackDetails` (#12742) @sai6855 +- [DataGrid] Do not escape double quotes when copying to clipboard (#12722) @cherniavskii +- [DataGrid] Fix column vertical border (#12741) @romgrk +- [DataGrid] Fix invalid date error when filtering `date`/`dateTime` columns (#12709) @cherniavskii +- [DataGrid] Fix overflow with dynamic row height (#12683) @romgrk +- [DataGrid] Make colors customizable (#12614) @romgrk +- [l10n] Improve French (fr-FR) locale (#12755) @derek-0000 +- [l10n] Improve German (de-DE) locale (#12752) @Jens-Schoen +- [l10n] Improve Swedish (sv-SE) locale (#12731) @pontusdacke + +#### `@mui/x-data-grid-pro@7.2.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-data-grid@7.2.0`. + +#### `@mui/x-data-grid-premium@7.2.0` [![premium](https://mui.com/r/x-premium-svg)](https://mui.com/r/x-premium-svg-link 'Premium plan') + +Same changes as in `@mui/x-data-grid-pro@7.2.0`, plus: + +- [DataGridPremium] Fix clipboard paste not working when cell loses focus (#12724) @cherniavskii + +### Date and Time Pickers + +#### `@mui/x-date-pickers@7.2.0` + +- [fields] Fix field editing after closing the picker (#12675) @LukasTy +- [l10n] Improve French (fr-FR) locale (#12692) @FaroukBel +- [l10n] Improve German (de-DE) locale (#12752) @Jens-Schoen +- [l10n] Improve Swedish (sv-SE) locale (#12731) @pontusdacke +- [pickers] Fix desktop date time Pickers grid layout (#12748) @LukasTy + +#### `@mui/x-date-pickers-pro@7.2.0` [![pro](https://mui.com/r/x-pro-svg)](https://mui.com/r/x-pro-svg-link 'Pro plan') + +Same changes as in `@mui/x-date-pickers@7.2.0`, plus: + +- [DateTimeRangePicker] Fix desktop toolbar style (#12760) @LukasTy + +### Charts + +#### `@mui/x-charts@7.2.0` + +- [charts] Fix Bar chart with empty dataset throwing an error (#12708) @JCQuintas +- [charts] Fix `tickLabelInterval` not working on `YAxis` (#12746) @JCQuintas + +### Tree View + +#### `@mui/x-tree-view@7.2.0` + +- [TreeView] Add a new lookup to access an item index without expansive computation (#12729) @flaviendelangle +- [TreeView] Clean up usage of term "node" in internals (#12655) @noraleonte +- [TreeView] Improve performance by removing `getNavigableChildrenIds` method (#12713) @flaviendelangle +- [TreeView] Remove `state.items.itemTree` (#12717) @flaviendelangle +- [TreeView] Remove remaining occurences of the word "node" in the codebase (#12712) @flaviendelangle +- [TreeView] Return `instance` and `publicAPI` methods from plugin and populate the main objects inside `useTreeView` (#12650) @flaviendelangle +- [TreeView] Fix behaviors when the item order changes (#12369) @flaviendelangle + +### Docs + +- [docs] Add `AxisFormatter` documentation for customizing tick/tooltip value formatting (#12700) @JCQuintas +- [docs] Add file explorer example to rich tree view customization docs (#12707) @noraleonte +- [docs] Do not use import of depth 3 in the doc (#12716) @flaviendelangle +- [docs] Explain how to clip plots with composition (#12679) @alexfauquette +- [docs] Fix typo in Data Grid v7 migration page (#12720) @bfaulk96 +- [docs] Fix typo in Pickers v7 migration page (#12721) @bfaulk96 + +### Core + +- [core] Support multiple resolved `l10n` PR packages (#12735) @LukasTy +- [core] Update Netlify release references in release README (#12687) @LukasTy +- [core] Use `describeTreeView` for icons tests (#12672) @flaviendelangle +- [core] Use `describeTreeView` in existing tests for `useTreeViewItems` (#12732) @flaviendelangle + ## 7.1.1 _Apr 5, 2024_ diff --git a/docs/data/charts/areas-demo/AreaChartFillByValue.js b/docs/data/charts/areas-demo/AreaChartFillByValue.js index 8104aba8162c..7561daa0e7a6 100644 --- a/docs/data/charts/areas-demo/AreaChartFillByValue.js +++ b/docs/data/charts/areas-demo/AreaChartFillByValue.js @@ -3,7 +3,7 @@ import * as React from 'react'; import { green, red } from '@mui/material/colors'; import Stack from '@mui/material/Stack'; import { useYScale, useDrawingArea } from '@mui/x-charts/hooks'; -import { LineChart } from '@mui/x-charts/LineChart'; +import { LineChart, areaElementClasses } from '@mui/x-charts/LineChart'; const data = [4000, 3000, -1000, 500, -2100, -250, 3490]; const xData = ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G']; @@ -43,7 +43,7 @@ export default function AreaChartFillByValue() { height={200} margin={{ top: 20, bottom: 30, left: 75 }} sx={{ - '& .MuiAreaElement-root': { + [`& .${areaElementClasses.root}`]: { fill: 'url(#swich-color-id-1)', }, }} @@ -64,7 +64,7 @@ export default function AreaChartFillByValue() { height={200} margin={{ top: 20, bottom: 30, left: 75 }} sx={{ - '& .MuiAreaElement-root': { + [`& .${areaElementClasses.root}`]: { fill: 'url(#swich-color-id-2)', }, }} diff --git a/docs/data/charts/areas-demo/AreaChartFillByValue.tsx b/docs/data/charts/areas-demo/AreaChartFillByValue.tsx index bf4e7a9fc07a..97fc2f78ff59 100644 --- a/docs/data/charts/areas-demo/AreaChartFillByValue.tsx +++ b/docs/data/charts/areas-demo/AreaChartFillByValue.tsx @@ -3,7 +3,7 @@ import { ScaleLinear } from 'd3-scale'; import { green, red } from '@mui/material/colors'; import Stack from '@mui/material/Stack'; import { useYScale, useDrawingArea } from '@mui/x-charts/hooks'; -import { LineChart } from '@mui/x-charts/LineChart'; +import { LineChart, areaElementClasses } from '@mui/x-charts/LineChart'; const data = [4000, 3000, -1000, 500, -2100, -250, 3490]; const xData = ['Page A', 'Page B', 'Page C', 'Page D', 'Page E', 'Page F', 'Page G']; @@ -50,7 +50,7 @@ export default function AreaChartFillByValue() { height={200} margin={{ top: 20, bottom: 30, left: 75 }} sx={{ - '& .MuiAreaElement-root': { + [`& .${areaElementClasses.root}`]: { fill: 'url(#swich-color-id-1)', }, }} @@ -71,7 +71,7 @@ export default function AreaChartFillByValue() { height={200} margin={{ top: 20, bottom: 30, left: 75 }} sx={{ - '& .MuiAreaElement-root': { + [`& .${areaElementClasses.root}`]: { fill: 'url(#swich-color-id-2)', }, }} diff --git a/docs/data/charts/areas-demo/SimpleAreaChart.js b/docs/data/charts/areas-demo/SimpleAreaChart.js index f85ed043ba44..89cc76fb7cb8 100644 --- a/docs/data/charts/areas-demo/SimpleAreaChart.js +++ b/docs/data/charts/areas-demo/SimpleAreaChart.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import { LineChart } from '@mui/x-charts/LineChart'; +import { LineChart, lineElementClasses } from '@mui/x-charts/LineChart'; const uData = [4000, 3000, 2000, 2780, 1890, 2390, 3490]; const xLabels = [ @@ -20,7 +20,7 @@ export default function SimpleAreaChart() { series={[{ data: uData, label: 'uv', area: true, showMark: false }]} xAxis={[{ scaleType: 'point', data: xLabels }]} sx={{ - '.MuiLineElement-root': { + [`& .${lineElementClasses.root}`]: { display: 'none', }, }} diff --git a/docs/data/charts/areas-demo/SimpleAreaChart.tsx b/docs/data/charts/areas-demo/SimpleAreaChart.tsx index f85ed043ba44..89cc76fb7cb8 100644 --- a/docs/data/charts/areas-demo/SimpleAreaChart.tsx +++ b/docs/data/charts/areas-demo/SimpleAreaChart.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { LineChart } from '@mui/x-charts/LineChart'; +import { LineChart, lineElementClasses } from '@mui/x-charts/LineChart'; const uData = [4000, 3000, 2000, 2780, 1890, 2390, 3490]; const xLabels = [ @@ -20,7 +20,7 @@ export default function SimpleAreaChart() { series={[{ data: uData, label: 'uv', area: true, showMark: false }]} xAxis={[{ scaleType: 'point', data: xLabels }]} sx={{ - '.MuiLineElement-root': { + [`& .${lineElementClasses.root}`]: { display: 'none', }, }} diff --git a/docs/data/charts/areas-demo/SimpleAreaChart.tsx.preview b/docs/data/charts/areas-demo/SimpleAreaChart.tsx.preview index ed7e1c1e212f..9cb309cab20e 100644 --- a/docs/data/charts/areas-demo/SimpleAreaChart.tsx.preview +++ b/docs/data/charts/areas-demo/SimpleAreaChart.tsx.preview @@ -4,7 +4,7 @@ series={[{ data: uData, label: 'uv', area: true, showMark: false }]} xAxis={[{ scaleType: 'point', data: xLabels }]} sx={{ - '.MuiLineElement-root': { + [`& .${lineElementClasses.root}`]: { display: 'none', }, }} diff --git a/docs/data/charts/areas-demo/StackedAreaChart.js b/docs/data/charts/areas-demo/StackedAreaChart.js index 799a62195199..11e420c3dc1a 100644 --- a/docs/data/charts/areas-demo/StackedAreaChart.js +++ b/docs/data/charts/areas-demo/StackedAreaChart.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import { LineChart } from '@mui/x-charts/LineChart'; +import { LineChart, lineElementClasses } from '@mui/x-charts/LineChart'; const uData = [4000, 3000, 2000, 2780, 1890, 2390, 3490]; const pData = [2400, 1398, 9800, 3908, 4800, 3800, 4300]; @@ -32,7 +32,7 @@ export default function StackedAreaChart() { ]} xAxis={[{ scaleType: 'point', data: xLabels }]} sx={{ - '.MuiLineElement-root': { + [`& .${lineElementClasses.root}`]: { display: 'none', }, }} diff --git a/docs/data/charts/areas-demo/StackedAreaChart.tsx b/docs/data/charts/areas-demo/StackedAreaChart.tsx index 799a62195199..11e420c3dc1a 100644 --- a/docs/data/charts/areas-demo/StackedAreaChart.tsx +++ b/docs/data/charts/areas-demo/StackedAreaChart.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { LineChart } from '@mui/x-charts/LineChart'; +import { LineChart, lineElementClasses } from '@mui/x-charts/LineChart'; const uData = [4000, 3000, 2000, 2780, 1890, 2390, 3490]; const pData = [2400, 1398, 9800, 3908, 4800, 3800, 4300]; @@ -32,7 +32,7 @@ export default function StackedAreaChart() { ]} xAxis={[{ scaleType: 'point', data: xLabels }]} sx={{ - '.MuiLineElement-root': { + [`& .${lineElementClasses.root}`]: { display: 'none', }, }} diff --git a/docs/data/charts/axis/AxisCustomizationNoSnap.js b/docs/data/charts/axis/AxisCustomizationNoSnap.js index f33f0352d481..c5d485e2286f 100644 --- a/docs/data/charts/axis/AxisCustomizationNoSnap.js +++ b/docs/data/charts/axis/AxisCustomizationNoSnap.js @@ -1,7 +1,6 @@ import * as React from 'react'; import Box from '@mui/material/Box'; import ChartsUsageDemo from 'docsx/src/modules/components/ChartsUsageDemo'; -import { DEFAULT_X_AXIS_KEY } from '@mui/x-charts/constants'; import { ScatterChart } from '@mui/x-charts/ScatterChart'; import { Chance } from 'chance'; @@ -41,7 +40,6 @@ export default function AxisCustomizationNoSnap() { ]} leftAxis={null} bottomAxis={{ - axisId: DEFAULT_X_AXIS_KEY, ...defaultXAxis, ...props, }} diff --git a/docs/data/charts/axis/ModifyAxisPosition.js b/docs/data/charts/axis/ModifyAxisPosition.js index e1d52c3d5ac8..dcbd3e3db6cc 100644 --- a/docs/data/charts/axis/ModifyAxisPosition.js +++ b/docs/data/charts/axis/ModifyAxisPosition.js @@ -1,6 +1,5 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '@mui/x-charts/constants'; import { ScatterChart } from '@mui/x-charts/ScatterChart'; import { Chance } from 'chance'; @@ -22,8 +21,8 @@ export default function ModifyAxisPosition() { {...params} leftAxis={null} bottomAxis={null} - topAxis={DEFAULT_X_AXIS_KEY} - rightAxis={DEFAULT_Y_AXIS_KEY} + topAxis={{}} + rightAxis={{}} margin={{ top: 30, bottom: 10 }} /> diff --git a/docs/data/charts/axis/ModifyAxisPosition.tsx b/docs/data/charts/axis/ModifyAxisPosition.tsx index e1d52c3d5ac8..dcbd3e3db6cc 100644 --- a/docs/data/charts/axis/ModifyAxisPosition.tsx +++ b/docs/data/charts/axis/ModifyAxisPosition.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import Box from '@mui/material/Box'; -import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '@mui/x-charts/constants'; import { ScatterChart } from '@mui/x-charts/ScatterChart'; import { Chance } from 'chance'; @@ -22,8 +21,8 @@ export default function ModifyAxisPosition() { {...params} leftAxis={null} bottomAxis={null} - topAxis={DEFAULT_X_AXIS_KEY} - rightAxis={DEFAULT_Y_AXIS_KEY} + topAxis={{}} + rightAxis={{}} margin={{ top: 30, bottom: 10 }} /> diff --git a/docs/data/charts/axis/ModifyAxisPosition.tsx.preview b/docs/data/charts/axis/ModifyAxisPosition.tsx.preview index 3f5c3cd0a5fb..2c7210171b72 100644 --- a/docs/data/charts/axis/ModifyAxisPosition.tsx.preview +++ b/docs/data/charts/axis/ModifyAxisPosition.tsx.preview @@ -2,7 +2,7 @@ {...params} leftAxis={null} bottomAxis={null} - topAxis={DEFAULT_X_AXIS_KEY} - rightAxis={DEFAULT_Y_AXIS_KEY} + topAxis={{}} + rightAxis={{}} margin={{ top: 30, bottom: 10 }} /> \ No newline at end of file diff --git a/docs/data/charts/axis/axis.md b/docs/data/charts/axis/axis.md index c514ecf23580..10bb97df86c3 100644 --- a/docs/data/charts/axis/axis.md +++ b/docs/data/charts/axis/axis.md @@ -197,7 +197,9 @@ Those pros can accept three type of value: - `null` to not display the axis - `string` which should correspond to the id of a `xAxis` for top and bottom. Or to the id of a `yAxis` for left and right. -- `object` which will be passed as props to `` or ``. It allows to specify which axis should be represent, and to customize the design of the axis. +- `object` which will be passed as props to `` or ``. It allows to specify which axis should be represent with the `axisId` property, and to customize the design of the axis. + +The demo below uses `leftAxis={null}` to remove the left axis, and `rightAxis={{}}` to set a right axis without overriding the default y-axis configuration. {{"demo": "ModifyAxisPosition.js"}} diff --git a/docs/data/charts/bars/ColorScaleNoSnap.js b/docs/data/charts/bars/ColorScaleNoSnap.js new file mode 100644 index 000000000000..a0062052c275 --- /dev/null +++ b/docs/data/charts/bars/ColorScaleNoSnap.js @@ -0,0 +1,174 @@ +import * as React from 'react'; +import { BarChart } from '@mui/x-charts/BarChart'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import MenuItem from '@mui/material/MenuItem'; +// @ts-ignore +import HighlightedCode from 'docs/src/modules/components/HighlightedCode'; + +const series = [{ data: [-2, -9, 12, 11, 6, -4] }]; + +export default function ColorScaleNoSnap() { + const [colorX, setColorX] = React.useState('piecewise'); + const [colorY, setColorY] = React.useState('None'); + + return ( + + + setColorX(event.target.value)} + > + None + piecewise + continuous + ordinal + + setColorY(event.target.value)} + > + None + piecewise + continuous + + + + value.getFullYear().toString(), + colorMap: + (colorX === 'ordinal' && { + type: 'ordinal', + colors: [ + '#ccebc5', + '#a8ddb5', + '#7bccc4', + '#4eb3d3', + '#2b8cbe', + '#08589e', + ], + }) || + (colorX === 'continuous' && { + type: 'continuous', + min: new Date(2019, 1, 1), + max: new Date(2024, 1, 1), + color: ['green', 'orange'], + }) || + (colorX === 'piecewise' && { + type: 'piecewise', + thresholds: [new Date(2021, 1, 1), new Date(2023, 1, 1)], + colors: ['blue', 'red', 'blue'], + }) || + undefined, + }, + ]} + /> + `, + ].join('\n')} + language="jsx" + copyButtonHidden + /> + + ); +} diff --git a/docs/data/charts/bars/ColorScaleNoSnap.tsx b/docs/data/charts/bars/ColorScaleNoSnap.tsx new file mode 100644 index 000000000000..bef053df0b9c --- /dev/null +++ b/docs/data/charts/bars/ColorScaleNoSnap.tsx @@ -0,0 +1,187 @@ +import * as React from 'react'; +import { BarChart } from '@mui/x-charts/BarChart'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import MenuItem from '@mui/material/MenuItem'; +// @ts-ignore +import HighlightedCode from 'docs/src/modules/components/HighlightedCode'; + +const series = [{ data: [-2, -9, 12, 11, 6, -4] }]; + +export default function ColorScaleNoSnap() { + const [colorX, setColorX] = React.useState< + 'None' | 'piecewise' | 'continuous' | 'ordinal' + >('piecewise'); + const [colorY, setColorY] = React.useState<'None' | 'piecewise' | 'continuous'>( + 'None', + ); + + return ( + + + + setColorX( + event.target.value as 'None' | 'piecewise' | 'continuous' | 'ordinal', + ) + } + > + None + piecewise + continuous + ordinal + + + setColorY(event.target.value as 'None' | 'piecewise' | 'continuous') + } + > + None + piecewise + continuous + + + + value.getFullYear().toString(), + colorMap: + (colorX === 'ordinal' && { + type: 'ordinal', + colors: [ + '#ccebc5', + '#a8ddb5', + '#7bccc4', + '#4eb3d3', + '#2b8cbe', + '#08589e', + ], + }) || + (colorX === 'continuous' && { + type: 'continuous', + min: new Date(2019, 1, 1), + max: new Date(2024, 1, 1), + color: ['green', 'orange'], + }) || + (colorX === 'piecewise' && { + type: 'piecewise', + thresholds: [new Date(2021, 1, 1), new Date(2023, 1, 1)], + colors: ['blue', 'red', 'blue'], + }) || + undefined, + }, + ]} + /> + `, + ].join('\n')} + language="jsx" + copyButtonHidden + /> + + ); +} diff --git a/docs/data/charts/bars/bars.md b/docs/data/charts/bars/bars.md index 8c74fde88ac7..3287a06ee42f 100644 --- a/docs/data/charts/bars/bars.md +++ b/docs/data/charts/bars/bars.md @@ -85,6 +85,21 @@ See [Axis—Grid](/x/react-charts/axis/#grid) documentation for more information {{"demo": "GridDemo.js"}} +### Color scale + +As with other charts, you can modify the [series color](/x/react-charts/styling/#colors) either directly, or with the color palette. + +You can also modify the color by using axes `colorMap` which maps values to colors. +The bar charts use by priority: + +1. The value axis color +2. The band axis color +3. The series color + +Learn more about the `colorMap` properties in the [Styling docs](/x/react-charts/styling/#values-color). + +{{"demo": "ColorScaleNoSnap.js"}} + ## Click event Bar charts provides two click handlers: diff --git a/docs/data/charts/line-demo/DashedLineChart.js b/docs/data/charts/line-demo/DashedLineChart.js index 52a2530897c0..9544f962c77f 100644 --- a/docs/data/charts/line-demo/DashedLineChart.js +++ b/docs/data/charts/line-demo/DashedLineChart.js @@ -1,5 +1,9 @@ import * as React from 'react'; -import { LineChart } from '@mui/x-charts/LineChart'; +import { + LineChart, + lineElementClasses, + markElementClasses, +} from '@mui/x-charts/LineChart'; const uData = [4000, 3000, 2000, 2780, 1890, 2390, 3490]; const pData = [2400, 1398, 9800, 3908, 4800, 3800, 4300]; @@ -24,7 +28,7 @@ export default function DashedLineChart() { ]} xAxis={[{ scaleType: 'point', data: xLabels }]} sx={{ - '.MuiLineElement-root, .MuiMarkElement-root': { + [`.${lineElementClasses.root}, .${markElementClasses.root}`]: { strokeWidth: 1, }, '.MuiLineElement-series-pvId': { @@ -33,10 +37,10 @@ export default function DashedLineChart() { '.MuiLineElement-series-uvId': { strokeDasharray: '3 4 5 2', }, - '.MuiMarkElement-root:not(.MuiMarkElement-highlighted)': { + [`.${markElementClasses.root}:not(.${markElementClasses.highlighted})`]: { fill: '#fff', }, - '& .MuiMarkElement-highlighted': { + [`& .${markElementClasses.highlighted}`]: { stroke: 'none', }, }} diff --git a/docs/data/charts/line-demo/DashedLineChart.tsx b/docs/data/charts/line-demo/DashedLineChart.tsx index 52a2530897c0..9544f962c77f 100644 --- a/docs/data/charts/line-demo/DashedLineChart.tsx +++ b/docs/data/charts/line-demo/DashedLineChart.tsx @@ -1,5 +1,9 @@ import * as React from 'react'; -import { LineChart } from '@mui/x-charts/LineChart'; +import { + LineChart, + lineElementClasses, + markElementClasses, +} from '@mui/x-charts/LineChart'; const uData = [4000, 3000, 2000, 2780, 1890, 2390, 3490]; const pData = [2400, 1398, 9800, 3908, 4800, 3800, 4300]; @@ -24,7 +28,7 @@ export default function DashedLineChart() { ]} xAxis={[{ scaleType: 'point', data: xLabels }]} sx={{ - '.MuiLineElement-root, .MuiMarkElement-root': { + [`.${lineElementClasses.root}, .${markElementClasses.root}`]: { strokeWidth: 1, }, '.MuiLineElement-series-pvId': { @@ -33,10 +37,10 @@ export default function DashedLineChart() { '.MuiLineElement-series-uvId': { strokeDasharray: '3 4 5 2', }, - '.MuiMarkElement-root:not(.MuiMarkElement-highlighted)': { + [`.${markElementClasses.root}:not(.${markElementClasses.highlighted})`]: { fill: '#fff', }, - '& .MuiMarkElement-highlighted': { + [`& .${markElementClasses.highlighted}`]: { stroke: 'none', }, }} diff --git a/docs/data/charts/line-demo/TinyLineChart.js b/docs/data/charts/line-demo/TinyLineChart.js index 3b33df37e940..0dee6bfee644 100644 --- a/docs/data/charts/line-demo/TinyLineChart.js +++ b/docs/data/charts/line-demo/TinyLineChart.js @@ -1,6 +1,11 @@ import * as React from 'react'; import { ChartContainer } from '@mui/x-charts'; -import { LinePlot, MarkPlot } from '@mui/x-charts/LineChart'; +import { + LinePlot, + MarkPlot, + lineElementClasses, + markElementClasses, +} from '@mui/x-charts/LineChart'; const pData = [2400, 1398, 9800, 3908, 4800, 3800, 4300]; const xLabels = [ @@ -21,11 +26,11 @@ export default function TinyLineChart() { series={[{ type: 'line', data: pData }]} xAxis={[{ scaleType: 'point', data: xLabels }]} sx={{ - '.MuiLineElement-root': { + [`& .${lineElementClasses.root}`]: { stroke: '#8884d8', strokeWidth: 2, }, - '.MuiMarkElement-root': { + [`& .${markElementClasses.root}`]: { stroke: '#8884d8', scale: '0.6', fill: '#fff', diff --git a/docs/data/charts/line-demo/TinyLineChart.tsx b/docs/data/charts/line-demo/TinyLineChart.tsx index 3b33df37e940..0dee6bfee644 100644 --- a/docs/data/charts/line-demo/TinyLineChart.tsx +++ b/docs/data/charts/line-demo/TinyLineChart.tsx @@ -1,6 +1,11 @@ import * as React from 'react'; import { ChartContainer } from '@mui/x-charts'; -import { LinePlot, MarkPlot } from '@mui/x-charts/LineChart'; +import { + LinePlot, + MarkPlot, + lineElementClasses, + markElementClasses, +} from '@mui/x-charts/LineChart'; const pData = [2400, 1398, 9800, 3908, 4800, 3800, 4300]; const xLabels = [ @@ -21,11 +26,11 @@ export default function TinyLineChart() { series={[{ type: 'line', data: pData }]} xAxis={[{ scaleType: 'point', data: xLabels }]} sx={{ - '.MuiLineElement-root': { + [`& .${lineElementClasses.root}`]: { stroke: '#8884d8', strokeWidth: 2, }, - '.MuiMarkElement-root': { + [`& .${markElementClasses.root}`]: { stroke: '#8884d8', scale: '0.6', fill: '#fff', diff --git a/docs/data/charts/lines/CSSCustomization.js b/docs/data/charts/lines/CSSCustomization.js index 480e020b8956..933eaa75f501 100644 --- a/docs/data/charts/lines/CSSCustomization.js +++ b/docs/data/charts/lines/CSSCustomization.js @@ -1,5 +1,5 @@ import * as React from 'react'; -import { LineChart } from '@mui/x-charts/LineChart'; +import { LineChart, lineElementClasses } from '@mui/x-charts/LineChart'; const years = [ new Date(1990, 0, 1), @@ -58,7 +58,7 @@ export default function CSSCustomization() { return ( + + setColorX(event.target.value)} + > + None + piecewise + continuous + + setColorY(event.target.value)} + > + None + piecewise + continuous + + + + value.getFullYear().toString(), + colorMap: + (colorX === 'continuous' && { + type: 'continuous', + min: new Date(2019, 1, 1), + max: new Date(2024, 1, 1), + color: ['green', 'orange'], + }) || + (colorX === 'piecewise' && { + type: 'piecewise', + thresholds: [new Date(2021, 1, 1), new Date(2023, 1, 1)], + colors: ['blue', 'red', 'blue'], + }) || + undefined, + }, + ]} + /> + `, + ].join('\n')} + language="jsx" + copyButtonHidden + /> + + ); +} diff --git a/docs/data/charts/lines/ColorScaleNoSnap.tsx b/docs/data/charts/lines/ColorScaleNoSnap.tsx new file mode 100644 index 000000000000..687ba56d7ee9 --- /dev/null +++ b/docs/data/charts/lines/ColorScaleNoSnap.tsx @@ -0,0 +1,165 @@ +import * as React from 'react'; +import { LineChart } from '@mui/x-charts/LineChart'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import MenuItem from '@mui/material/MenuItem'; +// @ts-ignore +import HighlightedCode from 'docs/src/modules/components/HighlightedCode'; + +export default function ColorScaleNoSnap() { + const [colorX, setColorX] = React.useState< + 'None' | 'piecewise' | 'continuous' | 'ordinal' + >('None'); + const [colorY, setColorY] = React.useState<'None' | 'piecewise' | 'continuous'>( + 'piecewise', + ); + + return ( + + + + setColorX(event.target.value as 'None' | 'piecewise' | 'continuous') + } + > + None + piecewise + continuous + + + setColorY(event.target.value as 'None' | 'piecewise' | 'continuous') + } + > + None + piecewise + continuous + + + + value.getFullYear().toString(), + colorMap: + (colorX === 'continuous' && { + type: 'continuous', + min: new Date(2019, 1, 1), + max: new Date(2024, 1, 1), + color: ['green', 'orange'], + }) || + (colorX === 'piecewise' && { + type: 'piecewise', + thresholds: [new Date(2021, 1, 1), new Date(2023, 1, 1)], + colors: ['blue', 'red', 'blue'], + }) || + undefined, + }, + ]} + /> + `, + ].join('\n')} + language="jsx" + copyButtonHidden + /> + + ); +} diff --git a/docs/data/charts/lines/lines.md b/docs/data/charts/lines/lines.md index 5b949e9b4a4a..9b203541adca 100644 --- a/docs/data/charts/lines/lines.md +++ b/docs/data/charts/lines/lines.md @@ -146,6 +146,25 @@ See [Axis—Grid](/x/react-charts/axis/#grid) documentation for more information {{"demo": "GridDemo.js"}} +### Color scale + +As with other charts, you can modify the [series color](/x/react-charts/styling/#colors) either directly, or with the color palette. + +You can also modify the color by using axes `colorMap` which maps values to colors. +The line charts use by priority: + +1. The y-axis color +2. The x-axis color +3. The series color + +Learn more about the `colorMap` properties in the [Styling docs](/x/react-charts/styling/#values-color). + +{{"demo": "ColorScaleNoSnap.js"}} + +:::warning +For now, ordinal config is not supported for line chart. +::: + ### Interpolation The interpolation between data points can be customized by the `curve` property. diff --git a/docs/data/charts/scatter/ColorScaleNoSnap.js b/docs/data/charts/scatter/ColorScaleNoSnap.js new file mode 100644 index 000000000000..ea446b982627 --- /dev/null +++ b/docs/data/charts/scatter/ColorScaleNoSnap.js @@ -0,0 +1,172 @@ +import * as React from 'react'; +import { ScatterChart } from '@mui/x-charts/ScatterChart'; + +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import MenuItem from '@mui/material/MenuItem'; +// @ts-ignore +import HighlightedCode from 'docs/src/modules/components/HighlightedCode'; + +import { Chance } from 'chance'; + +const chance = new Chance(42); + +export default function ColorScaleNoSnap() { + const [colorX, setColorX] = React.useState('piecewise'); + const [colorY, setColorY] = React.useState('None'); + + return ( + + + setColorX(event.target.value)} + > + None + piecewise + continuous + + setColorY(event.target.value)} + > + None + piecewise + continuous + + + + + `, + ].join('\n')} + language="jsx" + copyButtonHidden + /> + + ); +} + +const series = [{ data: getGaussianSeriesData([0, 0], [1, 1], 200) }].map((s) => ({ + ...s, + valueFormatter: (v) => `(${v.x.toFixed(1)}, ${v.y.toFixed(1)})`, +})); + +function getGaussianSeriesData(mean, stdev = [0.3, 0.4], N = 50) { + return [...Array(N)].map((_, i) => { + const x = + Math.sqrt(-2.0 * Math.log(1 - chance.floating({ min: 0, max: 0.99 }))) * + Math.cos(2.0 * Math.PI * chance.floating({ min: 0, max: 0.99 })) * + stdev[0] + + mean[0]; + const y = + Math.sqrt(-2.0 * Math.log(1 - chance.floating({ min: 0, max: 0.99 }))) * + Math.cos(2.0 * Math.PI * chance.floating({ min: 0, max: 0.99 })) * + stdev[1] + + mean[1]; + return { x, y, id: i }; + }); +} diff --git a/docs/data/charts/scatter/ColorScaleNoSnap.tsx b/docs/data/charts/scatter/ColorScaleNoSnap.tsx new file mode 100644 index 000000000000..3ab5d985ef84 --- /dev/null +++ b/docs/data/charts/scatter/ColorScaleNoSnap.tsx @@ -0,0 +1,185 @@ +import * as React from 'react'; +import { ScatterChart } from '@mui/x-charts/ScatterChart'; +import { ScatterValueType } from '@mui/x-charts'; +import Stack from '@mui/material/Stack'; +import TextField from '@mui/material/TextField'; +import MenuItem from '@mui/material/MenuItem'; +// @ts-ignore +import HighlightedCode from 'docs/src/modules/components/HighlightedCode'; + +import { Chance } from 'chance'; + +const chance = new Chance(42); + +export default function ColorScaleNoSnap() { + const [colorX, setColorX] = React.useState<'None' | 'piecewise' | 'continuous'>( + 'piecewise', + ); + const [colorY, setColorY] = React.useState<'None' | 'piecewise' | 'continuous'>( + 'None', + ); + + return ( + + + + setColorX(event.target.value as 'None' | 'piecewise' | 'continuous') + } + > + None + piecewise + continuous + + + setColorY(event.target.value as 'None' | 'piecewise' | 'continuous') + } + > + None + piecewise + continuous + + + + + `, + ].join('\n')} + language="jsx" + copyButtonHidden + /> + + ); +} + +const series = [{ data: getGaussianSeriesData([0, 0], [1, 1], 200) }].map((s) => ({ + ...s, + valueFormatter: (v: ScatterValueType) => `(${v.x.toFixed(1)}, ${v.y.toFixed(1)})`, +})); + +function getGaussianSeriesData( + mean: [number, number], + stdev: [number, number] = [0.3, 0.4], + N: number = 50, +) { + return [...Array(N)].map((_, i) => { + const x = + Math.sqrt(-2.0 * Math.log(1 - chance.floating({ min: 0, max: 0.99 }))) * + Math.cos(2.0 * Math.PI * chance.floating({ min: 0, max: 0.99 })) * + stdev[0] + + mean[0]; + const y = + Math.sqrt(-2.0 * Math.log(1 - chance.floating({ min: 0, max: 0.99 }))) * + Math.cos(2.0 * Math.PI * chance.floating({ min: 0, max: 0.99 })) * + stdev[1] + + mean[1]; + return { x, y, id: i }; + }); +} diff --git a/docs/data/charts/scatter/scatter.md b/docs/data/charts/scatter/scatter.md index 75e038b43e04..89dbca75e8a1 100644 --- a/docs/data/charts/scatter/scatter.md +++ b/docs/data/charts/scatter/scatter.md @@ -53,6 +53,21 @@ Otherwise, the click behavior will be the same as defined in the [interaction se ## Styling +### Color scale + +As with other charts, you can modify the [series color](/x/react-charts/styling/#colors) either directly, or with the color palette. + +You can also modify the color by using axes `colorMap` which maps values to colors. +The scatter charts use by priority: + +1. The y-axis color +2. The x-axis color +3. The series color + +Learn more about the `colorMap` properties in the [Styling docs](/x/react-charts/styling/#values-color). + +{{"demo": "ColorScaleNoSnap.js"}} + ### Grid You can add a grid in the background of the chart with the `grid` prop. diff --git a/docs/data/charts/styling/MarginNoSnap.js b/docs/data/charts/styling/MarginNoSnap.js index a3d650272b81..6e00cc274bf9 100644 --- a/docs/data/charts/styling/MarginNoSnap.js +++ b/docs/data/charts/styling/MarginNoSnap.js @@ -1,7 +1,6 @@ import * as React from 'react'; import ChartsUsageDemo from 'docsx/src/modules/components/ChartsUsageDemo'; import { BarChart } from '@mui/x-charts/BarChart'; -import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '@mui/x-charts/constants'; const data = ['left', 'right', 'top', 'bottom'].map((propName) => ({ propName, @@ -29,13 +28,13 @@ export default function MarginNoSnap() { }} xAxis={[ { - id: DEFAULT_X_AXIS_KEY, + id: 'x-axis', scaleType: 'band', data: ['Page 1', 'Page 2', 'Page 3'], }, ]} - topAxis={DEFAULT_X_AXIS_KEY} - rightAxis={DEFAULT_Y_AXIS_KEY} + topAxis="x-axis" + rightAxis={{}} /> )} diff --git a/docs/data/charts/styling/styling.md b/docs/data/charts/styling/styling.md index 1892ccce2dce..22d64de01465 100644 --- a/docs/data/charts/styling/styling.md +++ b/docs/data/charts/styling/styling.md @@ -42,6 +42,66 @@ Here is an example of the d3 Categorical color palette. {{"demo": "ColorTemplate.js"}} +### Values color + +Colors can also be set according to item values using the `colorMap` property of the corresponding axis. + +Learn more about how to use this feature with each chart component in their dedicated docs section: + +- [bar charts](/x/react-charts/bars/#color-scale) +- [line charts](/x/react-charts/lines/#color-scale) +- [scatter charts](/x/react-charts/scatter/#color-scale) + +The `colorMap` property can accept three kinds of objects defined below. + +#### Piecewise color map + +The piecewise configuration takes an array of _n_ `thresholds` values and _n+1_ `colors`. + +```ts +{ + type: 'piecewise'; + thresholds: Value[]; + colors: string[]; +} +``` + +#### Continuous color map + +The continuous configuration lets you map values from `min` to `max` properties to their corresponding colors. + +The `color` property can either be an array of two colors to interpolate, or an interpolation function that returns a color corresponding to a number _t_ with a value between 0 and 1. +The [d3-scale-chromatic](https://d3js.org/d3-scale-chromatic) offers a lot of those functions. + +Values lower than the `min` get the color of the `min` value; similarly, values higher than the `max` get the color of the `max` value. +By default the `min`/`max` range is set to 0 / 100. + +```ts +{ + type: 'continuous'; + min?: Value; + max?: Value; + color: [string, string] | ((t: number) => string); +} +``` + +#### Ordinal color map + +This configuration takes two properties—`values` and `colors`—and maps those values to their respective colors. + +If a value is not defined, it will fall back to the `unknownColor`, and if this is also undefined, then it falls back on the series color. + +This configuration can be used in Bar Charts to set colors according to string categories. + +```ts +{ + type: 'ordinal'; + values: Value[]; + colors: string[]; + unknownColor?: string; +} +``` + ## Styling ### Size diff --git a/docs/data/charts/tooltip/Formatting.js b/docs/data/charts/tooltip/Formatting.js index e9ff77e25254..cc6d43e98ccd 100644 --- a/docs/data/charts/tooltip/Formatting.js +++ b/docs/data/charts/tooltip/Formatting.js @@ -87,9 +87,9 @@ export default function Formatting() { ({ - ...serie, - valueFormatter: currencyFormatter, + series={lineChartsParams.series.map((series) => ({ + ...series, + valueFormatter: (v) => (v === null ? '' : currencyFormatter(v)), }))} /> ); diff --git a/docs/data/charts/tooltip/Formatting.tsx b/docs/data/charts/tooltip/Formatting.tsx index 6384957f71a6..880f305e857b 100644 --- a/docs/data/charts/tooltip/Formatting.tsx +++ b/docs/data/charts/tooltip/Formatting.tsx @@ -87,9 +87,9 @@ export default function Formatting() { ({ - ...serie, - valueFormatter: currencyFormatter, + series={lineChartsParams.series.map((series) => ({ + ...series, + valueFormatter: (v) => (v === null ? '' : currencyFormatter(v)), }))} /> ); diff --git a/docs/data/charts/tooltip/Formatting.tsx.preview b/docs/data/charts/tooltip/Formatting.tsx.preview index 0c659b2551fb..934617577916 100644 --- a/docs/data/charts/tooltip/Formatting.tsx.preview +++ b/docs/data/charts/tooltip/Formatting.tsx.preview @@ -1,8 +1,8 @@ ({ - ...serie, - valueFormatter: currencyFormatter, + series={lineChartsParams.series.map((series) => ({ + ...series, + valueFormatter: (v) => (v === null ? '' : currencyFormatter(v)), }))} /> \ No newline at end of file diff --git a/docs/data/charts/tooltip/SeriesFormatter.js b/docs/data/charts/tooltip/SeriesFormatter.js new file mode 100644 index 000000000000..dbb58a71b719 --- /dev/null +++ b/docs/data/charts/tooltip/SeriesFormatter.js @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { PieChart } from '@mui/x-charts/PieChart'; +import { legendClasses } from '@mui/x-charts'; + +const otherProps = { + width: 400, + height: 200, + sx: { + [`.${legendClasses.root}`]: { + transform: 'translate(20px, 0)', + }, + }, +}; + +const data = [ + { team: 'Amber Ants', rank: 3, points: 31 }, + { team: 'Eagle Warriors', rank: 1, points: 50 }, + { team: 'Elephant Trunk', rank: 4, points: 18 }, + { team: 'Jaguars', rank: 2, points: 37 }, + { team: 'Smooth Pandas', rank: 5, points: 6 }, +]; + +export default function SeriesFormatter() { + return ( + ({ label: d.team, id: d.team, value: d.points })), + valueFormatter: (v, { dataIndex }) => { + const { rank } = data[dataIndex]; + return `has ${v.value} points and is ranked ${rank}.`; + }, + }, + ]} + {...otherProps} + /> + ); +} diff --git a/docs/data/charts/tooltip/SeriesFormatter.tsx b/docs/data/charts/tooltip/SeriesFormatter.tsx new file mode 100644 index 000000000000..801c7add0551 --- /dev/null +++ b/docs/data/charts/tooltip/SeriesFormatter.tsx @@ -0,0 +1,38 @@ +import * as React from 'react'; +import { PieChart, PieChartProps } from '@mui/x-charts/PieChart'; +import { legendClasses } from '@mui/x-charts'; + +const otherProps: Partial = { + width: 400, + height: 200, + sx: { + [`.${legendClasses.root}`]: { + transform: 'translate(20px, 0)', + }, + }, +}; + +const data = [ + { team: 'Amber Ants', rank: 3, points: 31 }, + { team: 'Eagle Warriors', rank: 1, points: 50 }, + { team: 'Elephant Trunk', rank: 4, points: 18 }, + { team: 'Jaguars', rank: 2, points: 37 }, + { team: 'Smooth Pandas', rank: 5, points: 6 }, +]; + +export default function SeriesFormatter() { + return ( + ({ label: d.team, id: d.team, value: d.points })), + valueFormatter: (v, { dataIndex }) => { + const { rank } = data[dataIndex]; + return `has ${v.value} points and is ranked ${rank}.`; + }, + }, + ]} + {...otherProps} + /> + ); +} diff --git a/docs/data/charts/tooltip/SeriesFormatter.tsx.preview b/docs/data/charts/tooltip/SeriesFormatter.tsx.preview new file mode 100644 index 000000000000..e7784e08ebfe --- /dev/null +++ b/docs/data/charts/tooltip/SeriesFormatter.tsx.preview @@ -0,0 +1,12 @@ + ({ label: d.team, id: d.team, value: d.points })), + valueFormatter: (v, { dataIndex }) => { + const { rank } = data[dataIndex]; + return `has ${v.value} points and is ranked ${rank}.`; + }, + }, + ]} + {...otherProps} +/> \ No newline at end of file diff --git a/docs/data/charts/tooltip/tooltip.md b/docs/data/charts/tooltip/tooltip.md index a3c463c96dcf..b61cef8290f8 100644 --- a/docs/data/charts/tooltip/tooltip.md +++ b/docs/data/charts/tooltip/tooltip.md @@ -69,6 +69,14 @@ Here is a demo with: {{"demo": "Formatting.js"}} +### Advanced formatting + +The series `valueFormatter` provides a context as its second argument containing a `dataIndex` property which you can use to calculate other data-related values. + +In the demo below you can notice we use `dataIndex` to add each team's rank in the tooltip. + +{{"demo": "SeriesFormatter.js"}} + ### Axis formatter To modify how data is displayed in the axis use the `valueFormatter` property. diff --git a/docs/data/data-grid/filtering/header-filters.md b/docs/data/data-grid/filtering/header-filters.md index 4022fffdfdd7..fea11f2b5452 100644 --- a/docs/data/data-grid/filtering/header-filters.md +++ b/docs/data/data-grid/filtering/header-filters.md @@ -67,6 +67,15 @@ Additionally, `slots.headerFilterMenu` could also be used to customize the menu {{"demo": "CustomHeaderFilterDataGridPro.js", "bg": "inline", "defaultCodeOpen": false}} +### Custom header filter height + +By default, the height of the header filter row is the same as the header row (represented by `columnHeaderHeight` prop). +You can customize the height of the header filter cell using the `headerFilterHeight` prop. + +```tsx + +``` + ## Ignore diacritics (accents) You can ignore diacritics (accents) when filtering the rows. See [Quick filter - Ignore diacritics (accents)](/x/react-data-grid/filtering/quick-filter/#ignore-diacritics-accents). diff --git a/docs/data/data-grid/localization/data.json b/docs/data/data-grid/localization/data.json index 4c0a0d9dbcbe..6726ad619312 100644 --- a/docs/data/data-grid/localization/data.json +++ b/docs/data/data-grid/localization/data.json @@ -67,7 +67,7 @@ "languageTag": "da-DK", "importName": "daDK", "localeName": "Danish", - "missingKeysCount": 3, + "missingKeysCount": 0, "totalKeysCount": 117, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/daDK.ts" }, @@ -91,7 +91,7 @@ "languageTag": "fr-FR", "importName": "frFR", "localeName": "French", - "missingKeysCount": 3, + "missingKeysCount": 0, "totalKeysCount": 117, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/frFR.ts" }, @@ -99,7 +99,7 @@ "languageTag": "de-DE", "importName": "deDE", "localeName": "German", - "missingKeysCount": 3, + "missingKeysCount": 0, "totalKeysCount": 117, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-data-grid/src/locales/deDE.ts" }, diff --git a/docs/data/date-pickers/localization/data.json b/docs/data/date-pickers/localization/data.json index 9f57aab37387..57afc42d751b 100644 --- a/docs/data/date-pickers/localization/data.json +++ b/docs/data/date-pickers/localization/data.json @@ -83,7 +83,7 @@ "languageTag": "de-DE", "importName": "deDE", "localeName": "German", - "missingKeysCount": 13, + "missingKeysCount": 0, "totalKeysCount": 50, "githubLink": "https://github.com/mui/mui-x/blob/master/packages/x-date-pickers/src/locales/deDE.ts" }, diff --git a/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md b/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md index 6f52bc1d05ab..cdfd95277d1f 100644 --- a/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md +++ b/docs/data/migration/migration-data-grid-v6/migration-data-grid-v6.md @@ -510,7 +510,20 @@ See the [Direct state access](/x/react-data-grid/state/#direct-selector-access) - The `.MuiDataGrid--pinnedColumns-(left\|right)` class for pinned columns has been removed. - The `.MuiDataGrid-cell--withRenderer` class has been removed. - The cell element isn't `display: flex` by default. You can add `display: 'flex'` on the column definition to restore the behavior. - NOTE: If you're using **dynamic row height**, this also means cells aren't vertically centered by default anymore, you might want to set the `display: 'flex'` for all non-dynamic columns. This may also affect text-ellipsis, which you can restore by adding your own wrapper with `text-overflow: ellipsis;`. + + **NOTE**: If you're using **dynamic row height**, this also means cells aren't vertically centered by default anymore, you might want to set the `display: 'flex'` for all non-dynamic columns. This may also affect text-ellipsis, which you can restore by adding your own wrapper with `text-overflow: ellipsis`. + + ```tsx + { + display: 'flex', + renderCell: ({ value }) => ( +
+ {value} +
+ ), + }, + ``` + - The `columnHeader--showColumnBorder` class was replaced by `columnHeader--withLeftBorder` and `columnHeader--withRightBorder`. - The `columnHeadersInner`, `columnHeadersInner--scrollable`, and `columnHeaderDropZone` classes were removed since the inner wrapper was removed in our effort to simplify the DOM structure and improve accessibility. - The `pinnedColumnHeaders`, `pinnedColumnHeaders--left`, and `pinnedColumnHeaders--right` classes were removed along with the element they were applied to. diff --git a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js new file mode 100644 index 000000000000..2ac57640beea --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.js @@ -0,0 +1,267 @@ +import * as React from 'react'; +import clsx from 'clsx'; +import { animated, useSpring } from '@react-spring/web'; +import { styled, alpha } from '@mui/material/styles'; + +import Box from '@mui/material/Box'; +import Collapse from '@mui/material/Collapse'; +import Typography from '@mui/material/Typography'; +import ArticleIcon from '@mui/icons-material/Article'; +import DeleteIcon from '@mui/icons-material/Delete'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; +import FolderRounded from '@mui/icons-material/FolderRounded'; +import ImageIcon from '@mui/icons-material/Image'; +import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; +import VideoCameraBackIcon from '@mui/icons-material/VideoCameraBack'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { treeItemClasses } from '@mui/x-tree-view/TreeItem'; +import { unstable_useTreeItem2 as useTreeItem2 } from '@mui/x-tree-view/useTreeItem2'; +import { + TreeItem2Content, + TreeItem2IconContainer, + TreeItem2Label, + TreeItem2Root, +} from '@mui/x-tree-view/TreeItem2'; +import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; +import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; + +const ITEMS = [ + { + id: '1', + label: 'Documents', + children: [ + { + id: '1.1', + label: 'Company', + children: [ + { id: '1.1.1', label: 'Invoice', fileType: 'pdf' }, + { id: '1.1.2', label: 'Meeting notes', fileType: 'doc' }, + { id: '1.1.3', label: 'Tasks list', fileType: 'doc' }, + { id: '1.1.4', label: 'Equipment', fileType: 'pdf' }, + { id: '1.1.5', label: 'Video conference', fileType: 'video' }, + ], + }, + { id: '1.2', label: 'Personal', fileType: 'folder' }, + { id: '1.3', label: 'Group photo', fileType: 'image' }, + ], + }, + { + id: '2', + label: 'Bookmarked', + fileType: 'pinned', + children: [ + { id: '2.1', label: 'Learning materials', fileType: 'folder' }, + { id: '2.2', label: 'News', fileType: 'folder' }, + { id: '2.3', label: 'Forums', fileType: 'folder' }, + { id: '2.4', label: 'Travel documents', fileType: 'pdf' }, + ], + }, + { id: '3', label: 'History', fileType: 'folder' }, + { id: '4', label: 'Trash', fileType: 'trash' }, +]; + +function DotIcon() { + return ( + + ); +} + +const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ + color: + theme.palette.mode === 'light' + ? theme.palette.grey[800] + : theme.palette.grey[400], + position: 'relative', + [`& .${treeItemClasses.groupTransition}`]: { + marginLeft: theme.spacing(3.5), + }, +})); + +const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ + flexDirection: 'row-reverse', + borderRadius: theme.spacing(0.7), + marginBottom: theme.spacing(0.5), + marginTop: theme.spacing(0.5), + padding: theme.spacing(0.5), + paddingRight: theme.spacing(1), + fontWeight: 500, + [`& .${treeItemClasses.iconContainer}`]: { + marginRight: theme.spacing(2), + }, + [`&.Mui-expanded `]: { + '&:not(.Mui-focused, .Mui-selected, .Mui-selected.Mui-focused) .labelIcon': { + color: + theme.palette.mode === 'light' + ? theme.palette.primary.main + : theme.palette.primary.dark, + }, + '&::before': { + content: '""', + display: 'block', + position: 'absolute', + left: '16px', + top: '44px', + height: 'calc(100% - 48px)', + width: '1.5px', + backgroundColor: + theme.palette.mode === 'light' + ? theme.palette.grey[300] + : theme.palette.grey[700], + }, + }, + '&:hover': { + backgroundColor: alpha(theme.palette.primary.main, 0.1), + color: theme.palette.mode === 'light' ? theme.palette.primary.main : 'white', + }, + [`&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused`]: { + backgroundColor: + theme.palette.mode === 'light' + ? theme.palette.primary.main + : theme.palette.primary.dark, + color: theme.palette.primary.contrastText, + }, +})); + +const AnimatedCollapse = animated(Collapse); + +function TransitionComponent(props) { + const style = useSpring({ + to: { + opacity: props.in ? 1 : 0, + transform: `translate3d(0,${props.in ? 0 : 20}px,0)`, + }, + }); + + return ; +} + +const StyledTreeItemLabelText = styled(Typography)({ + color: 'inherit', + fontFamily: 'General Sans', + fontWeight: 500, +}); + +function CustomLabel({ icon: Icon, expandable, children, ...other }) { + return ( + + {Icon && ( + + )} + + {children} + {expandable && } + + ); +} + +const isExpandable = (reactChildren) => { + if (Array.isArray(reactChildren)) { + return reactChildren.length > 0 && reactChildren.some(isExpandable); + } + return Boolean(reactChildren); +}; + +const getIconFromFileType = (fileType) => { + switch (fileType) { + case 'image': + return ImageIcon; + case 'pdf': + return PictureAsPdfIcon; + case 'doc': + return ArticleIcon; + case 'video': + return VideoCameraBackIcon; + case 'folder': + return FolderRounded; + case 'pinned': + return FolderOpenIcon; + case 'trash': + return DeleteIcon; + default: + return ArticleIcon; + } +}; + +const CustomTreeItem = React.forwardRef(function CustomTreeItem(props, ref) { + const { id, itemId, label, disabled, children, ...other } = props; + + const { + getRootProps, + getContentProps, + getIconContainerProps, + getLabelProps, + getGroupTransitionProps, + status, + publicAPI, + } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + + const item = publicAPI.getItem(itemId); + const expandable = isExpandable(children); + let icon; + if (expandable) { + icon = FolderRounded; + } else if (item.fileType) { + icon = getIconFromFileType(item.fileType); + } + + return ( + + + + + + + + + + {children && } + + + ); +}); + +export default function FileExplorer() { + return ( + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx new file mode 100644 index 000000000000..d12cf981003a --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx @@ -0,0 +1,303 @@ +import * as React from 'react'; +import clsx from 'clsx'; +import { animated, useSpring } from '@react-spring/web'; +import { styled, alpha } from '@mui/material/styles'; +import { TransitionProps } from '@mui/material/transitions'; +import Box from '@mui/material/Box'; +import Collapse from '@mui/material/Collapse'; +import Typography from '@mui/material/Typography'; +import ArticleIcon from '@mui/icons-material/Article'; +import DeleteIcon from '@mui/icons-material/Delete'; +import FolderOpenIcon from '@mui/icons-material/FolderOpen'; +import FolderRounded from '@mui/icons-material/FolderRounded'; +import ImageIcon from '@mui/icons-material/Image'; +import PictureAsPdfIcon from '@mui/icons-material/PictureAsPdf'; +import VideoCameraBackIcon from '@mui/icons-material/VideoCameraBack'; +import { RichTreeView } from '@mui/x-tree-view/RichTreeView'; +import { treeItemClasses } from '@mui/x-tree-view/TreeItem'; +import { + unstable_useTreeItem2 as useTreeItem2, + UseTreeItem2Parameters, +} from '@mui/x-tree-view/useTreeItem2'; +import { + TreeItem2Content, + TreeItem2IconContainer, + TreeItem2Label, + TreeItem2Root, +} from '@mui/x-tree-view/TreeItem2'; +import { TreeItem2Icon } from '@mui/x-tree-view/TreeItem2Icon'; +import { TreeItem2Provider } from '@mui/x-tree-view/TreeItem2Provider'; +import { TreeViewBaseItem } from '@mui/x-tree-view/models'; + +type FileType = 'image' | 'pdf' | 'doc' | 'video' | 'folder' | 'pinned' | 'trash'; + +type ExtendedTreeItemProps = { + fileType?: FileType; + id: string; + label: string; +}; + +const ITEMS: TreeViewBaseItem[] = [ + { + id: '1', + label: 'Documents', + children: [ + { + id: '1.1', + label: 'Company', + children: [ + { id: '1.1.1', label: 'Invoice', fileType: 'pdf' }, + { id: '1.1.2', label: 'Meeting notes', fileType: 'doc' }, + { id: '1.1.3', label: 'Tasks list', fileType: 'doc' }, + { id: '1.1.4', label: 'Equipment', fileType: 'pdf' }, + { id: '1.1.5', label: 'Video conference', fileType: 'video' }, + ], + }, + { id: '1.2', label: 'Personal', fileType: 'folder' }, + { id: '1.3', label: 'Group photo', fileType: 'image' }, + ], + }, + { + id: '2', + label: 'Bookmarked', + fileType: 'pinned', + children: [ + { id: '2.1', label: 'Learning materials', fileType: 'folder' }, + { id: '2.2', label: 'News', fileType: 'folder' }, + { id: '2.3', label: 'Forums', fileType: 'folder' }, + { id: '2.4', label: 'Travel documents', fileType: 'pdf' }, + ], + }, + { id: '3', label: 'History', fileType: 'folder' }, + { id: '4', label: 'Trash', fileType: 'trash' }, +]; + +function DotIcon() { + return ( + + ); +} +declare module 'react' { + interface CSSProperties { + '--tree-view-color'?: string; + '--tree-view-bg-color'?: string; + } +} + +const StyledTreeItemRoot = styled(TreeItem2Root)(({ theme }) => ({ + color: + theme.palette.mode === 'light' + ? theme.palette.grey[800] + : theme.palette.grey[400], + position: 'relative', + [`& .${treeItemClasses.groupTransition}`]: { + marginLeft: theme.spacing(3.5), + }, +})) as unknown as typeof TreeItem2Root; + +const CustomTreeItemContent = styled(TreeItem2Content)(({ theme }) => ({ + flexDirection: 'row-reverse', + borderRadius: theme.spacing(0.7), + marginBottom: theme.spacing(0.5), + marginTop: theme.spacing(0.5), + padding: theme.spacing(0.5), + paddingRight: theme.spacing(1), + fontWeight: 500, + [`& .${treeItemClasses.iconContainer}`]: { + marginRight: theme.spacing(2), + }, + [`&.Mui-expanded `]: { + '&:not(.Mui-focused, .Mui-selected, .Mui-selected.Mui-focused) .labelIcon': { + color: + theme.palette.mode === 'light' + ? theme.palette.primary.main + : theme.palette.primary.dark, + }, + '&::before': { + content: '""', + display: 'block', + position: 'absolute', + left: '16px', + top: '44px', + height: 'calc(100% - 48px)', + width: '1.5px', + backgroundColor: + theme.palette.mode === 'light' + ? theme.palette.grey[300] + : theme.palette.grey[700], + }, + }, + '&:hover': { + backgroundColor: alpha(theme.palette.primary.main, 0.1), + color: theme.palette.mode === 'light' ? theme.palette.primary.main : 'white', + }, + [`&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused`]: { + backgroundColor: + theme.palette.mode === 'light' + ? theme.palette.primary.main + : theme.palette.primary.dark, + color: theme.palette.primary.contrastText, + }, +})); + +const AnimatedCollapse = animated(Collapse); + +function TransitionComponent(props: TransitionProps) { + const style = useSpring({ + to: { + opacity: props.in ? 1 : 0, + transform: `translate3d(0,${props.in ? 0 : 20}px,0)`, + }, + }); + + return ; +} + +const StyledTreeItemLabelText = styled(Typography)({ + color: 'inherit', + fontFamily: 'General Sans', + fontWeight: 500, +}) as unknown as typeof Typography; + +interface CustomLabelProps { + children: React.ReactNode; + icon?: React.ElementType; + expandable?: boolean; +} + +function CustomLabel({ + icon: Icon, + expandable, + children, + ...other +}: CustomLabelProps) { + return ( + + {Icon && ( + + )} + + {children} + {expandable && } + + ); +} + +const isExpandable = (reactChildren: React.ReactNode) => { + if (Array.isArray(reactChildren)) { + return reactChildren.length > 0 && reactChildren.some(isExpandable); + } + return Boolean(reactChildren); +}; + +const getIconFromFileType = (fileType: FileType) => { + switch (fileType) { + case 'image': + return ImageIcon; + case 'pdf': + return PictureAsPdfIcon; + case 'doc': + return ArticleIcon; + case 'video': + return VideoCameraBackIcon; + case 'folder': + return FolderRounded; + case 'pinned': + return FolderOpenIcon; + case 'trash': + return DeleteIcon; + default: + return ArticleIcon; + } +}; + +interface CustomTreeItemProps + extends Omit, + Omit, 'onFocus'> {} + +const CustomTreeItem = React.forwardRef(function CustomTreeItem( + props: CustomTreeItemProps, + ref: React.Ref, +) { + const { id, itemId, label, disabled, children, ...other } = props; + + const { + getRootProps, + getContentProps, + getIconContainerProps, + getLabelProps, + getGroupTransitionProps, + status, + publicAPI, + } = useTreeItem2({ id, itemId, children, label, disabled, rootRef: ref }); + + const item = publicAPI.getItem(itemId); + const expandable = isExpandable(children); + let icon; + if (expandable) { + icon = FolderRounded; + } else if (item.fileType) { + icon = getIconFromFileType(item.fileType); + } + + return ( + + + + + + + + + + {children && } + + + ); +}); + +export default function FileExplorer() { + return ( + + ); +} diff --git a/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx.preview b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx.preview new file mode 100644 index 000000000000..5f87458bdbbe --- /dev/null +++ b/docs/data/tree-view/rich-tree-view/customization/FileExplorer.tsx.preview @@ -0,0 +1,8 @@ + \ No newline at end of file diff --git a/docs/data/tree-view/rich-tree-view/customization/customization.md b/docs/data/tree-view/rich-tree-view/customization/customization.md index 5d33efde72b5..da4b3e57a2cc 100644 --- a/docs/data/tree-view/rich-tree-view/customization/customization.md +++ b/docs/data/tree-view/rich-tree-view/customization/customization.md @@ -69,3 +69,16 @@ The demo below shows how to add an avatar and custom typography elements. The demo below shows how to trigger the expansion interaction just by clicking on the icon container instead of the whole Tree Item surface. {{"demo": "IconExpansionTreeView.js", "defaultCodeOpen": false}} + +### File explorer + +:::warning +This example is built using the new `TreeItem2` component +which adds several slots to modify the content of the Tree Item or change its behavior. + +You can learn more about this new component in the [Overview page](/x/react-tree-view/#tree-item-components). +::: + +The demo below shows many of the previous customization examples brought together to make the Tree View component look completely different than its default design. + +{{"demo": "FileExplorer.js", "defaultCodeOpen": false}} diff --git a/docs/data/tree-view/simple-tree-view/customization/CustomizedTreeView.js b/docs/data/tree-view/simple-tree-view/customization/CustomizedTreeView.js deleted file mode 100644 index 572f804db372..000000000000 --- a/docs/data/tree-view/simple-tree-view/customization/CustomizedTreeView.js +++ /dev/null @@ -1,179 +0,0 @@ -import * as React from 'react'; -import { styled, alpha } from '@mui/material/styles'; -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; -import DeleteIcon from '@mui/icons-material/Delete'; -import Label from '@mui/icons-material/Label'; -import FolderRounded from '@mui/icons-material/FolderRounded'; -import AirplanemodeActiveIcon from '@mui/icons-material/AirplanemodeActive'; -import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; -import { TreeItem, treeItemClasses } from '@mui/x-tree-view/TreeItem'; -import Collapse from '@mui/material/Collapse'; - -import { animated, useSpring } from '@react-spring/web'; - -function DotIcon() { - return ( - - ); -} - -const StyledTreeItemLabel = styled(Typography)({ - color: 'inherit', - fontFamily: 'General Sans', - fontWeight: 'inherit', - flexGrow: 1, -}); - -const StyledTreeItemRoot = styled(TreeItem)(({ theme }) => ({ - color: - theme.palette.mode === 'light' - ? theme.palette.grey[800] - : theme.palette.grey[400], - position: 'relative', - [`& .${treeItemClasses.content}`]: { - flexDirection: 'row-reverse', - borderRadius: theme.spacing(0.7), - marginBottom: theme.spacing(0.5), - marginTop: theme.spacing(0.5), - padding: theme.spacing(0.5), - paddingRight: theme.spacing(1), - fontWeight: 500, - [`& .${treeItemClasses.label}`]: { - fontWeight: 'inherit', - }, - [`& .${treeItemClasses.iconContainer}`]: { - marginRight: theme.spacing(2), - }, - [`&.Mui-expanded `]: { - '&:not(.Mui-focused, .Mui-selected, .Mui-selected.Mui-focused) .labelIcon': { - color: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.secondary.dark, - }, - '&::before': { - content: '""', - display: 'block', - position: 'absolute', - left: '16px', - top: '44px', - height: 'calc(100% - 48px)', - width: '1.5px', - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.grey[300] - : theme.palette.grey[700], - }, - }, - '&:hover': { - backgroundColor: alpha(theme.palette.primary.main, 0.1), - color: theme.palette.mode === 'light' ? theme.palette.primary.main : 'white', - }, - [`&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused`]: { - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.secondary.dark, - color: 'white', - }, - }, - [`& .${treeItemClasses.groupTransition}`]: { - marginLeft: theme.spacing(3.5), - [`& .${treeItemClasses.content}`]: { - fontWeight: 500, - }, - }, -})); - -const AnimatedCollapse = animated(Collapse); - -function TransitionComponent(props) { - const style = useSpring({ - to: { - opacity: props.in ? 1 : 0, - transform: `translate3d(0,${props.in ? 0 : 20}px,0)`, - }, - }); - - return ; -} - -const StyledTreeItem = React.forwardRef(function StyledTreeItem(props, ref) { - const { labelIcon: LabelIcon, labelText, ...other } = props; - - return ( - - - {labelText} - - } - {...other} - ref={ref} - /> - ); -}); - -export default function CustomizedTreeView() { - return ( - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/docs/data/tree-view/simple-tree-view/customization/CustomizedTreeView.tsx b/docs/data/tree-view/simple-tree-view/customization/CustomizedTreeView.tsx deleted file mode 100644 index f28c321d880f..000000000000 --- a/docs/data/tree-view/simple-tree-view/customization/CustomizedTreeView.tsx +++ /dev/null @@ -1,194 +0,0 @@ -import * as React from 'react'; -import { styled, alpha } from '@mui/material/styles'; -import Box from '@mui/material/Box'; -import Typography from '@mui/material/Typography'; -import DeleteIcon from '@mui/icons-material/Delete'; -import Label from '@mui/icons-material/Label'; -import FolderRounded from '@mui/icons-material/FolderRounded'; -import AirplanemodeActiveIcon from '@mui/icons-material/AirplanemodeActive'; -import { SimpleTreeView } from '@mui/x-tree-view/SimpleTreeView'; -import { TreeItem, TreeItemProps, treeItemClasses } from '@mui/x-tree-view/TreeItem'; -import Collapse from '@mui/material/Collapse'; -import { TransitionProps } from '@mui/material/transitions'; -import { animated, useSpring } from '@react-spring/web'; - -function DotIcon() { - return ( - - ); -} - -declare module 'react' { - interface CSSProperties { - '--tree-view-color'?: string; - '--tree-view-bg-color'?: string; - } -} - -type StyledTreeItemProps = Omit & { - labelIcon: React.ElementType; - labelText: string; -}; - -const StyledTreeItemLabel = styled(Typography)({ - color: 'inherit', - fontFamily: 'General Sans', - fontWeight: 'inherit', - flexGrow: 1, -}) as unknown as typeof Typography; - -const StyledTreeItemRoot = styled(TreeItem)(({ theme }) => ({ - color: - theme.palette.mode === 'light' - ? theme.palette.grey[800] - : theme.palette.grey[400], - position: 'relative', - [`& .${treeItemClasses.content}`]: { - flexDirection: 'row-reverse', - borderRadius: theme.spacing(0.7), - marginBottom: theme.spacing(0.5), - marginTop: theme.spacing(0.5), - padding: theme.spacing(0.5), - paddingRight: theme.spacing(1), - fontWeight: 500, - [`& .${treeItemClasses.label}`]: { - fontWeight: 'inherit', - }, - [`& .${treeItemClasses.iconContainer}`]: { - marginRight: theme.spacing(2), - }, - [`&.Mui-expanded `]: { - '&:not(.Mui-focused, .Mui-selected, .Mui-selected.Mui-focused) .labelIcon': { - color: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.secondary.dark, - }, - '&::before': { - content: '""', - display: 'block', - position: 'absolute', - left: '16px', - top: '44px', - height: 'calc(100% - 48px)', - width: '1.5px', - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.grey[300] - : theme.palette.grey[700], - }, - }, - '&:hover': { - backgroundColor: alpha(theme.palette.primary.main, 0.1), - color: theme.palette.mode === 'light' ? theme.palette.primary.main : 'white', - }, - [`&.Mui-focused, &.Mui-selected, &.Mui-selected.Mui-focused`]: { - backgroundColor: - theme.palette.mode === 'light' - ? theme.palette.primary.main - : theme.palette.secondary.dark, - color: 'white', - }, - }, - [`& .${treeItemClasses.groupTransition}`]: { - marginLeft: theme.spacing(3.5), - [`& .${treeItemClasses.content}`]: { - fontWeight: 500, - }, - }, -})) as unknown as typeof TreeItem; - -const AnimatedCollapse = animated(Collapse); - -function TransitionComponent(props: TransitionProps) { - const style = useSpring({ - to: { - opacity: props.in ? 1 : 0, - transform: `translate3d(0,${props.in ? 0 : 20}px,0)`, - }, - }); - - return ; -} - -const StyledTreeItem = React.forwardRef(function StyledTreeItem( - props: StyledTreeItemProps, - ref: React.Ref, -) { - const { labelIcon: LabelIcon, labelText, ...other } = props; - - return ( - - - {labelText} - - } - {...other} - ref={ref} - /> - ); -}); - -export default function CustomizedTreeView() { - return ( - - - - - - - - - - - - - - - - - - - - - ); -} diff --git a/docs/data/tree-view/simple-tree-view/customization/customization.md b/docs/data/tree-view/simple-tree-view/customization/customization.md index 819205b2d815..4e74f185137d 100644 --- a/docs/data/tree-view/simple-tree-view/customization/customization.md +++ b/docs/data/tree-view/simple-tree-view/customization/customization.md @@ -83,12 +83,6 @@ The demo below shows how to trigger the expansion interaction just by clicking o {{"demo": "IconExpansionTreeView.js", "defaultCodeOpen": false}} -### File explorer - -The demo below shows many of the previous customization examples brought together to make the Tree View component look completely different than its default design. - -{{"demo": "CustomizedTreeView.js"}} - ### Gmail clone :::warning diff --git a/docs/pages/x/api/charts/bar-chart.json b/docs/pages/x/api/charts/bar-chart.json index f5440ca898e9..9098d9ca0a4a 100644 --- a/docs/pages/x/api/charts/bar-chart.json +++ b/docs/pages/x/api/charts/bar-chart.json @@ -95,13 +95,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } } }, diff --git a/docs/pages/x/api/charts/chart-container.json b/docs/pages/x/api/charts/chart-container.json index 74e710cbacf8..ac92c9e28613 100644 --- a/docs/pages/x/api/charts/chart-container.json +++ b/docs/pages/x/api/charts/chart-container.json @@ -22,13 +22,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } } }, diff --git a/docs/pages/x/api/charts/default-charts-item-tooltip-content.json b/docs/pages/x/api/charts/default-charts-item-tooltip-content.json index b97bee2e777d..9f4c4bdaaa54 100644 --- a/docs/pages/x/api/charts/default-charts-item-tooltip-content.json +++ b/docs/pages/x/api/charts/default-charts-item-tooltip-content.json @@ -5,6 +5,15 @@ "required": true, "additionalInfo": { "cssApi": true } }, + "getColor": { + "type": { "name": "func" }, + "required": true, + "signature": { + "type": "function(dataIndex: number) => string", + "describedArgs": ["dataIndex"], + "returned": "string" + } + }, "itemData": { "type": { "name": "shape", diff --git a/docs/pages/x/api/charts/line-chart.json b/docs/pages/x/api/charts/line-chart.json index dd15be4e5d23..700a593f0242 100644 --- a/docs/pages/x/api/charts/line-chart.json +++ b/docs/pages/x/api/charts/line-chart.json @@ -90,13 +90,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } } }, diff --git a/docs/pages/x/api/charts/pie-chart.json b/docs/pages/x/api/charts/pie-chart.json index 1e974abe72a0..3291f4fec6cb 100644 --- a/docs/pages/x/api/charts/pie-chart.json +++ b/docs/pages/x/api/charts/pie-chart.json @@ -86,13 +86,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } } }, diff --git a/docs/pages/x/api/charts/responsive-chart-container.json b/docs/pages/x/api/charts/responsive-chart-container.json index 216f02afe84b..a1b720b5030c 100644 --- a/docs/pages/x/api/charts/responsive-chart-container.json +++ b/docs/pages/x/api/charts/responsive-chart-container.json @@ -22,13 +22,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } } }, diff --git a/docs/pages/x/api/charts/scatter-chart.json b/docs/pages/x/api/charts/scatter-chart.json index 81ca7241fd46..b0314f723843 100644 --- a/docs/pages/x/api/charts/scatter-chart.json +++ b/docs/pages/x/api/charts/scatter-chart.json @@ -87,13 +87,13 @@ "xAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } }, "yAxis": { "type": { "name": "arrayOf", - "description": "Array<{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" + "description": "Array<{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }>" } } }, diff --git a/docs/pages/x/api/charts/spark-line-chart.json b/docs/pages/x/api/charts/spark-line-chart.json index 73fa87be1352..2ac3ba9a0e18 100644 --- a/docs/pages/x/api/charts/spark-line-chart.json +++ b/docs/pages/x/api/charts/spark-line-chart.json @@ -44,7 +44,7 @@ "xAxis": { "type": { "name": "shape", - "description": "{ axisId?: number
| string, classes?: object, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }" + "description": "{ axisId?: number
| string, classes?: object, colorMap?: { color: Array<string>
| func, max?: Date
| number, min?: Date
| number, type: 'continuous' }
| { colors: Array<string>, thresholds: Array<Date
| number>, type: 'piecewise' }
| { colors: Array<string>, type: 'ordinal', unknownColor?: string, values?: Array<Date
| number
| string> }, data?: array, dataKey?: string, disableLine?: bool, disableTicks?: bool, fill?: string, hideTooltip?: bool, id?: number
| string, label?: string, labelFontSize?: number, labelStyle?: object, max?: Date
| number, min?: Date
| number, position?: 'bottom'
| 'left'
| 'right'
| 'top', reverse?: bool, scaleType?: 'band'
| 'linear'
| 'log'
| 'point'
| 'pow'
| 'sqrt'
| 'time'
| 'utc', slotProps?: object, slots?: object, stroke?: string, tickFontSize?: number, tickInterval?: 'auto'
| array
| func, tickLabelInterval?: 'auto'
| func, tickLabelPlacement?: 'middle'
| 'tick', tickLabelStyle?: object, tickMaxStep?: number, tickMinStep?: number, tickNumber?: number, tickPlacement?: 'end'
| 'extremities'
| 'middle'
| 'start', tickSize?: number, valueFormatter?: func }" } } }, diff --git a/docs/pages/x/api/data-grid/data-grid-premium.json b/docs/pages/x/api/data-grid/data-grid-premium.json index 56215024f027..5d72c3f2e9fc 100644 --- a/docs/pages/x/api/data-grid/data-grid-premium.json +++ b/docs/pages/x/api/data-grid/data-grid-premium.json @@ -163,6 +163,7 @@ } }, "groupingColDef": { "type": { "name": "union", "description": "func
| object" } }, + "headerFilterHeight": { "type": { "name": "number" } }, "headerFilters": { "type": { "name": "bool" }, "default": "false" }, "hideFooter": { "type": { "name": "bool" }, "default": "false" }, "hideFooterPagination": { "type": { "name": "bool" }, "default": "false" }, @@ -1268,6 +1269,12 @@ "description": "Styles applied to the column header if the column has a filter applied to it.", "isGlobal": false }, + { + "key": "columnHeader--last", + "className": "MuiDataGridPremium-columnHeader--last", + "description": "Styles applied to the last column header element.", + "isGlobal": false + }, { "key": "columnHeader--moving", "className": "MuiDataGridPremium-columnHeader--moving", diff --git a/docs/pages/x/api/data-grid/data-grid-pro.json b/docs/pages/x/api/data-grid/data-grid-pro.json index 2de559b31451..732f9d1513e9 100644 --- a/docs/pages/x/api/data-grid/data-grid-pro.json +++ b/docs/pages/x/api/data-grid/data-grid-pro.json @@ -140,6 +140,7 @@ } }, "groupingColDef": { "type": { "name": "union", "description": "func
| object" } }, + "headerFilterHeight": { "type": { "name": "number" } }, "headerFilters": { "type": { "name": "bool" }, "default": "false" }, "hideFooter": { "type": { "name": "bool" }, "default": "false" }, "hideFooterPagination": { "type": { "name": "bool" }, "default": "false" }, @@ -1185,6 +1186,12 @@ "description": "Styles applied to the column header if the column has a filter applied to it.", "isGlobal": false }, + { + "key": "columnHeader--last", + "className": "MuiDataGridPro-columnHeader--last", + "description": "Styles applied to the last column header element.", + "isGlobal": false + }, { "key": "columnHeader--moving", "className": "MuiDataGridPro-columnHeader--moving", diff --git a/docs/pages/x/api/data-grid/data-grid.json b/docs/pages/x/api/data-grid/data-grid.json index 4ca488287bf5..4b7fc7836b78 100644 --- a/docs/pages/x/api/data-grid/data-grid.json +++ b/docs/pages/x/api/data-grid/data-grid.json @@ -1072,6 +1072,12 @@ "description": "Styles applied to the column header if the column has a filter applied to it.", "isGlobal": false }, + { + "key": "columnHeader--last", + "className": "MuiDataGrid-columnHeader--last", + "description": "Styles applied to the last column header element.", + "isGlobal": false + }, { "key": "columnHeader--moving", "className": "MuiDataGrid-columnHeader--moving", diff --git a/docs/translations/api-docs/charts/bar-chart/bar-chart.json b/docs/translations/api-docs/charts/bar-chart/bar-chart.json index 37cbf6fb2c98..3d356906dd2b 100644 --- a/docs/translations/api-docs/charts/bar-chart/bar-chart.json +++ b/docs/translations/api-docs/charts/bar-chart/bar-chart.json @@ -58,10 +58,10 @@ "description": "The width of the chart in px. If not defined, it takes the width of the parent element." }, "xAxis": { - "description": "The configuration of the x-axes. If not provided, a default axis config is used with id set to DEFAULT_X_AXIS_KEY." + "description": "The configuration of the x-axes. If not provided, a default axis config is used." }, "yAxis": { - "description": "The configuration of the y-axes. If not provided, a default axis config is used with id set to DEFAULT_Y_AXIS_KEY." + "description": "The configuration of the y-axes. If not provided, a default axis config is used." } }, "classDescriptions": {}, diff --git a/docs/translations/api-docs/charts/chart-container/chart-container.json b/docs/translations/api-docs/charts/chart-container/chart-container.json index 1f9d070e16d5..8080d75cf213 100644 --- a/docs/translations/api-docs/charts/chart-container/chart-container.json +++ b/docs/translations/api-docs/charts/chart-container/chart-container.json @@ -17,10 +17,10 @@ }, "width": { "description": "The width of the chart in px." }, "xAxis": { - "description": "The configuration of the x-axes. If not provided, a default axis config is used with id set to DEFAULT_X_AXIS_KEY." + "description": "The configuration of the x-axes. If not provided, a default axis config is used." }, "yAxis": { - "description": "The configuration of the y-axes. If not provided, a default axis config is used with id set to DEFAULT_Y_AXIS_KEY." + "description": "The configuration of the y-axes. If not provided, a default axis config is used." } }, "classDescriptions": {} diff --git a/docs/translations/api-docs/charts/default-charts-item-tooltip-content/default-charts-item-tooltip-content.json b/docs/translations/api-docs/charts/default-charts-item-tooltip-content/default-charts-item-tooltip-content.json index a1b46326cba0..36fe7b5f92e5 100644 --- a/docs/translations/api-docs/charts/default-charts-item-tooltip-content/default-charts-item-tooltip-content.json +++ b/docs/translations/api-docs/charts/default-charts-item-tooltip-content/default-charts-item-tooltip-content.json @@ -2,6 +2,13 @@ "componentDescription": "", "propDescriptions": { "classes": { "description": "Override or extend the styles applied to the component." }, + "getColor": { + "description": "Get the color of the item with index dataIndex.", + "typeDescriptions": { + "dataIndex": "The data index of the item.", + "string": "The color to display." + } + }, "itemData": { "description": "The data used to identify the triggered item." }, "series": { "description": "The series linked to the triggered axis." } }, diff --git a/docs/translations/api-docs/charts/line-chart/line-chart.json b/docs/translations/api-docs/charts/line-chart/line-chart.json index 8da425ba637d..1c0594fce5e7 100644 --- a/docs/translations/api-docs/charts/line-chart/line-chart.json +++ b/docs/translations/api-docs/charts/line-chart/line-chart.json @@ -56,10 +56,10 @@ "description": "The width of the chart in px. If not defined, it takes the width of the parent element." }, "xAxis": { - "description": "The configuration of the x-axes. If not provided, a default axis config is used with id set to DEFAULT_X_AXIS_KEY." + "description": "The configuration of the x-axes. If not provided, a default axis config is used." }, "yAxis": { - "description": "The configuration of the y-axes. If not provided, a default axis config is used with id set to DEFAULT_Y_AXIS_KEY." + "description": "The configuration of the y-axes. If not provided, a default axis config is used." } }, "classDescriptions": {}, diff --git a/docs/translations/api-docs/charts/pie-chart/pie-chart.json b/docs/translations/api-docs/charts/pie-chart/pie-chart.json index 8293d8d7baaa..a968d77da360 100644 --- a/docs/translations/api-docs/charts/pie-chart/pie-chart.json +++ b/docs/translations/api-docs/charts/pie-chart/pie-chart.json @@ -44,10 +44,10 @@ "description": "The width of the chart in px. If not defined, it takes the width of the parent element." }, "xAxis": { - "description": "The configuration of the x-axes. If not provided, a default axis config is used with id set to DEFAULT_X_AXIS_KEY." + "description": "The configuration of the x-axes. If not provided, a default axis config is used." }, "yAxis": { - "description": "The configuration of the y-axes. If not provided, a default axis config is used with id set to DEFAULT_Y_AXIS_KEY." + "description": "The configuration of the y-axes. If not provided, a default axis config is used." } }, "classDescriptions": {}, diff --git a/docs/translations/api-docs/charts/responsive-chart-container/responsive-chart-container.json b/docs/translations/api-docs/charts/responsive-chart-container/responsive-chart-container.json index b27956f7f741..c2d27fe6a10a 100644 --- a/docs/translations/api-docs/charts/responsive-chart-container/responsive-chart-container.json +++ b/docs/translations/api-docs/charts/responsive-chart-container/responsive-chart-container.json @@ -21,10 +21,10 @@ "description": "The width of the chart in px. If not defined, it takes the width of the parent element." }, "xAxis": { - "description": "The configuration of the x-axes. If not provided, a default axis config is used with id set to DEFAULT_X_AXIS_KEY." + "description": "The configuration of the x-axes. If not provided, a default axis config is used." }, "yAxis": { - "description": "The configuration of the y-axes. If not provided, a default axis config is used with id set to DEFAULT_Y_AXIS_KEY." + "description": "The configuration of the y-axes. If not provided, a default axis config is used." } }, "classDescriptions": {} diff --git a/docs/translations/api-docs/charts/scatter-chart/scatter-chart.json b/docs/translations/api-docs/charts/scatter-chart/scatter-chart.json index 4aa4a64d3ea6..ad851d29ce89 100644 --- a/docs/translations/api-docs/charts/scatter-chart/scatter-chart.json +++ b/docs/translations/api-docs/charts/scatter-chart/scatter-chart.json @@ -55,10 +55,10 @@ "description": "The width of the chart in px. If not defined, it takes the width of the parent element." }, "xAxis": { - "description": "The configuration of the x-axes. If not provided, a default axis config is used with id set to DEFAULT_X_AXIS_KEY." + "description": "The configuration of the x-axes. If not provided, a default axis config is used." }, "yAxis": { - "description": "The configuration of the y-axes. If not provided, a default axis config is used with id set to DEFAULT_Y_AXIS_KEY." + "description": "The configuration of the y-axes. If not provided, a default axis config is used." } }, "classDescriptions": {}, diff --git a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json index e4650258c745..db2671217fab 100644 --- a/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json +++ b/docs/translations/api-docs/data-grid/data-grid-premium/data-grid-premium.json @@ -179,6 +179,7 @@ } }, "groupingColDef": { "description": "The grouping column used by the tree data." }, + "headerFilterHeight": { "description": "Override the height of the header filters." }, "headerFilters": { "description": "If true, enables the data grid filtering on header feature." }, @@ -801,6 +802,10 @@ "nodeName": "the column header", "conditions": "the column has a filter applied to it" }, + "columnHeader--last": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the last column header element" + }, "columnHeader--moving": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the column header", diff --git a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json index 44babc11d794..d95a4ffd05e3 100644 --- a/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json +++ b/docs/translations/api-docs/data-grid/data-grid-pro/data-grid-pro.json @@ -160,6 +160,7 @@ } }, "groupingColDef": { "description": "The grouping column used by the tree data." }, + "headerFilterHeight": { "description": "Override the height of the header filters." }, "headerFilters": { "description": "If true, enables the data grid filtering on header feature." }, @@ -739,6 +740,10 @@ "nodeName": "the column header", "conditions": "the column has a filter applied to it" }, + "columnHeader--last": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the last column header element" + }, "columnHeader--moving": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the column header", diff --git a/docs/translations/api-docs/data-grid/data-grid/data-grid.json b/docs/translations/api-docs/data-grid/data-grid/data-grid.json index 957595b1a9a8..4f472991918d 100644 --- a/docs/translations/api-docs/data-grid/data-grid/data-grid.json +++ b/docs/translations/api-docs/data-grid/data-grid/data-grid.json @@ -629,6 +629,10 @@ "nodeName": "the column header", "conditions": "the column has a filter applied to it" }, + "columnHeader--last": { + "description": "Styles applied to {{nodeName}}.", + "nodeName": "the last column header element" + }, "columnHeader--moving": { "description": "Styles applied to {{nodeName}} if {{conditions}}.", "nodeName": "the column header", diff --git a/package.json b/package.json index a316b44687fd..ec11d449caa8 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "version": "7.1.1", + "version": "7.2.0", "private": true, "scripts": { "start": "yarn && yarn docs:dev", diff --git a/packages/x-charts/package.json b/packages/x-charts/package.json index 17ed659a7446..3068015415e2 100644 --- a/packages/x-charts/package.json +++ b/packages/x-charts/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-charts", - "version": "7.1.1", + "version": "7.2.0", "description": "The community edition of the Charts components (MUI X).", "author": "MUI Team", "main": "./src/index.js", diff --git a/packages/x-charts/src/BarChart/BarChart.tsx b/packages/x-charts/src/BarChart/BarChart.tsx index 4ffa35775888..066d34f3c752 100644 --- a/packages/x-charts/src/BarChart/BarChart.tsx +++ b/packages/x-charts/src/BarChart/BarChart.tsx @@ -485,12 +485,39 @@ BarChart.propTypes = { width: PropTypes.number, /** * The configuration of the x-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_X_AXIS_KEY`. + * If not provided, a default axis config is used. */ xAxis: PropTypes.arrayOf( PropTypes.shape({ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, + colorMap: PropTypes.oneOfType([ + PropTypes.shape({ + color: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string.isRequired), + PropTypes.func, + ]).isRequired, + max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + type: PropTypes.oneOf(['continuous']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + thresholds: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, + ).isRequired, + type: PropTypes.oneOf(['piecewise']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + type: PropTypes.oneOf(['ordinal']).isRequired, + unknownColor: PropTypes.string, + values: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) + .isRequired, + ), + }), + ]), data: PropTypes.array, dataKey: PropTypes.string, disableLine: PropTypes.bool, @@ -528,12 +555,39 @@ BarChart.propTypes = { ), /** * The configuration of the y-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_Y_AXIS_KEY`. + * If not provided, a default axis config is used. */ yAxis: PropTypes.arrayOf( PropTypes.shape({ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, + colorMap: PropTypes.oneOfType([ + PropTypes.shape({ + color: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string.isRequired), + PropTypes.func, + ]).isRequired, + max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + type: PropTypes.oneOf(['continuous']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + thresholds: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, + ).isRequired, + type: PropTypes.oneOf(['piecewise']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + type: PropTypes.oneOf(['ordinal']).isRequired, + unknownColor: PropTypes.string, + values: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) + .isRequired, + ), + }), + ]), data: PropTypes.array, dataKey: PropTypes.string, disableLine: PropTypes.bool, diff --git a/packages/x-charts/src/BarChart/BarPlot.tsx b/packages/x-charts/src/BarChart/BarPlot.tsx index ed56665c69ad..627993b0931f 100644 --- a/packages/x-charts/src/BarChart/BarPlot.tsx +++ b/packages/x-charts/src/BarChart/BarPlot.tsx @@ -4,12 +4,13 @@ import { useTransition } from '@react-spring/web'; import { SeriesContext } from '../context/SeriesContextProvider'; import { CartesianContext } from '../context/CartesianContextProvider'; import { BarElement, BarElementProps, BarElementSlotProps, BarElementSlots } from './BarElement'; -import { isBandScaleConfig } from '../models/axis'; +import { AxisDefaultized, isBandScaleConfig, isPointScaleConfig } from '../models/axis'; import { FormatterResult } from '../models/seriesType/config'; import { HighlightScope } from '../context/HighlightProvider'; import { BarItemIdentifier, BarSeriesType } from '../models'; import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '../constants'; import { SeriesId } from '../models/seriesType/common'; +import getColor from './getColor'; /** * Solution of the equations @@ -98,7 +99,8 @@ const useAggregatedData = (): CompletedBarData[] => { const yAxisConfig = yAxis[yAxisKey]; const verticalLayout = series[seriesId].layout === 'vertical'; - let baseScaleConfig; + let baseScaleConfig: AxisDefaultized<'band'>; + if (verticalLayout) { if (!isBandScaleConfig(xAxisConfig)) { throw new Error( @@ -118,7 +120,16 @@ const useAggregatedData = (): CompletedBarData[] => { } shoud have data property.`, ); } - baseScaleConfig = xAxisConfig; + baseScaleConfig = xAxisConfig as AxisDefaultized<'band'>; + if (isBandScaleConfig(yAxisConfig) || isPointScaleConfig(yAxisConfig)) { + throw new Error( + `MUI X Charts: ${ + yAxisKey === DEFAULT_Y_AXIS_KEY + ? 'The first `yAxis`' + : `The y-axis with id "${yAxisKey}"` + } shoud be a continuous type to display the bar series of id "${seriesId}".`, + ); + } } else { if (!isBandScaleConfig(yAxisConfig)) { throw new Error( @@ -139,12 +150,22 @@ const useAggregatedData = (): CompletedBarData[] => { } shoud have data property.`, ); } - baseScaleConfig = yAxisConfig; + baseScaleConfig = yAxisConfig as AxisDefaultized<'band'>; + if (isBandScaleConfig(xAxisConfig) || isPointScaleConfig(xAxisConfig)) { + throw new Error( + `MUI X Charts: ${ + xAxisKey === DEFAULT_X_AXIS_KEY + ? 'The first `xAxis`' + : `The x-axis with id "${xAxisKey}"` + } shoud be a continuous type to display the bar series of id "${seriesId}".`, + ); + } } const xScale = xAxisConfig.scale; const yScale = yAxisConfig.scale; + const colorGetter = getColor(series[seriesId], xAxis[xAxisKey], yAxis[yAxisKey]); const bandWidth = baseScaleConfig.scale.bandwidth(); const { barWidth, offset } = getBandSize({ @@ -154,7 +175,7 @@ const useAggregatedData = (): CompletedBarData[] => { }); const barOffset = groupIndex * (barWidth + offset); - const { stackedData, color } = series[seriesId]; + const { stackedData } = series[seriesId]; return stackedData.map((values, dataIndex: number) => { const valueCoordinates = values.map((v) => (verticalLayout ? yScale(v)! : xScale(v)!)); @@ -176,7 +197,7 @@ const useAggregatedData = (): CompletedBarData[] => { yOrigin: yScale(0)!, height: verticalLayout ? maxValueCoord - minValueCoord : barWidth, width: verticalLayout ? barWidth : maxValueCoord - minValueCoord, - color, + color: colorGetter(dataIndex), highlightScope: series[seriesId].highlightScope, }; }); diff --git a/packages/x-charts/src/BarChart/getColor.ts b/packages/x-charts/src/BarChart/getColor.ts new file mode 100644 index 000000000000..31a06feac93b --- /dev/null +++ b/packages/x-charts/src/BarChart/getColor.ts @@ -0,0 +1,36 @@ +import { AxisDefaultized } from '../models/axis'; +import { DefaultizedBarSeriesType } from '../models/seriesType/bar'; + +export default function getColor( + series: DefaultizedBarSeriesType, + xAxis: AxisDefaultized, + yAxis: AxisDefaultized, +) { + const verticalLayout = series.layout === 'vertical'; + + const bandColorScale = verticalLayout ? xAxis.colorScale : yAxis.colorScale; + const valueColorScale = verticalLayout ? yAxis.colorScale : xAxis.colorScale; + const bandValues = verticalLayout ? xAxis.data! : yAxis.data!; + + if (valueColorScale) { + return (dataIndex: number) => { + const value = series.data[dataIndex]; + const color = value === null ? series.color : valueColorScale(value); + if (color === null) { + return series.color; + } + return color; + }; + } + if (bandColorScale) { + return (dataIndex: number) => { + const value = bandValues[dataIndex]; + const color = value === null ? series.color : bandColorScale(value); + if (color === null) { + return series.color; + } + return color; + }; + } + return () => series.color; +} diff --git a/packages/x-charts/src/ChartContainer/ChartContainer.tsx b/packages/x-charts/src/ChartContainer/ChartContainer.tsx index 8a129a397954..9eacd20c1684 100644 --- a/packages/x-charts/src/ChartContainer/ChartContainer.tsx +++ b/packages/x-charts/src/ChartContainer/ChartContainer.tsx @@ -14,6 +14,7 @@ import { CartesianContextProviderProps, } from '../context/CartesianContextProvider'; import { HighlightProvider } from '../context/HighlightProvider'; +import { ChartsAxesGradients } from '../internals/components/ChartsAxesGradients'; export type ChartContainerProps = Omit< ChartsSurfaceProps & @@ -61,6 +62,7 @@ const ChartContainer = React.forwardRef(function ChartContainer(props: ChartCont desc={desc} disableAxisListener={disableAxisListener} > + {children} @@ -134,12 +136,39 @@ ChartContainer.propTypes = { width: PropTypes.number.isRequired, /** * The configuration of the x-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_X_AXIS_KEY`. + * If not provided, a default axis config is used. */ xAxis: PropTypes.arrayOf( PropTypes.shape({ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, + colorMap: PropTypes.oneOfType([ + PropTypes.shape({ + color: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string.isRequired), + PropTypes.func, + ]).isRequired, + max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + type: PropTypes.oneOf(['continuous']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + thresholds: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, + ).isRequired, + type: PropTypes.oneOf(['piecewise']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + type: PropTypes.oneOf(['ordinal']).isRequired, + unknownColor: PropTypes.string, + values: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) + .isRequired, + ), + }), + ]), data: PropTypes.array, dataKey: PropTypes.string, disableLine: PropTypes.bool, @@ -177,12 +206,39 @@ ChartContainer.propTypes = { ), /** * The configuration of the y-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_Y_AXIS_KEY`. + * If not provided, a default axis config is used. */ yAxis: PropTypes.arrayOf( PropTypes.shape({ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, + colorMap: PropTypes.oneOfType([ + PropTypes.shape({ + color: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string.isRequired), + PropTypes.func, + ]).isRequired, + max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + type: PropTypes.oneOf(['continuous']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + thresholds: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, + ).isRequired, + type: PropTypes.oneOf(['piecewise']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + type: PropTypes.oneOf(['ordinal']).isRequired, + unknownColor: PropTypes.string, + values: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) + .isRequired, + ), + }), + ]), data: PropTypes.array, dataKey: PropTypes.string, disableLine: PropTypes.bool, diff --git a/packages/x-charts/src/ChartsAxis/ChartsAxis.tsx b/packages/x-charts/src/ChartsAxis/ChartsAxis.tsx index 463256ba2939..8e1111814712 100644 --- a/packages/x-charts/src/ChartsAxis/ChartsAxis.tsx +++ b/packages/x-charts/src/ChartsAxis/ChartsAxis.tsx @@ -50,12 +50,13 @@ export interface ChartsAxisProps { const getAxisId = ( propsValue: undefined | null | string | ChartsXAxisProps | ChartsYAxisProps, + defaultAxisId?: string, ): AxisId | null => { if (propsValue == null) { return null; } if (typeof propsValue === 'object') { - return propsValue.axisId ?? null; + return propsValue.axisId ?? defaultAxisId ?? null; } return propsValue; }; @@ -92,8 +93,8 @@ function ChartsAxis(props: ChartsAxisProps) { const leftId = getAxisId(leftAxis === undefined ? yAxisIds[0] : leftAxis); const bottomId = getAxisId(bottomAxis === undefined ? xAxisIds[0] : bottomAxis); - const topId = getAxisId(topAxis); - const rightId = getAxisId(rightAxis); + const topId = getAxisId(topAxis, xAxisIds[0]); + const rightId = getAxisId(rightAxis, yAxisIds[0]); if (topId !== null && !xAxis[topId]) { throw Error( diff --git a/packages/x-charts/src/ChartsClipPath/ChartsClipPath.tsx b/packages/x-charts/src/ChartsClipPath/ChartsClipPath.tsx index fc674f7038c0..cce380818f1d 100644 --- a/packages/x-charts/src/ChartsClipPath/ChartsClipPath.tsx +++ b/packages/x-charts/src/ChartsClipPath/ChartsClipPath.tsx @@ -1,6 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; -import { DrawingContext } from '../context/DrawingProvider'; +import { useDrawingArea } from '../hooks/useDrawingArea'; export type ChartsClipPathProps = { id: string; @@ -14,7 +14,7 @@ export type ChartsClipPathProps = { */ function ChartsClipPath(props: ChartsClipPathProps) { const { id, offset: offsetProps } = props; - const { left, top, width, height } = React.useContext(DrawingContext); + const { left, top, width, height } = useDrawingArea(); const offset = { top: 0, right: 0, bottom: 0, left: 0, ...offsetProps }; return ( diff --git a/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx b/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx index 291c13be609e..854bf0db8935 100644 --- a/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx +++ b/packages/x-charts/src/ChartsLegend/ChartsLegend.tsx @@ -3,12 +3,12 @@ import PropTypes from 'prop-types'; import { useSlotProps } from '@mui/base/utils'; import { unstable_composeClasses as composeClasses } from '@mui/utils'; import { useThemeProps, useTheme, Theme } from '@mui/material/styles'; -import { DrawingContext } from '../context/DrawingProvider'; import { AnchorPosition, Direction, getSeriesToDisplay } from './utils'; import { SeriesContext } from '../context/SeriesContextProvider'; import { ChartsLegendClasses, getLegendUtilityClass } from './chartsLegendClasses'; import { DefaultizedProps } from '../models/helpers'; import { DefaultChartsLegend, LegendRendererProps } from './DefaultChartsLegend'; +import { useDrawingArea } from '../hooks'; export interface ChartsLegendSlots { /** @@ -82,7 +82,7 @@ function ChartsLegend(inProps: ChartsLegendProps) { const theme = useTheme(); const classes = useUtilityClasses({ ...props, theme }); - const drawingArea = React.useContext(DrawingContext); + const drawingArea = useDrawingArea(); const series = React.useContext(SeriesContext); const seriesToDisplay = getSeriesToDisplay(series); diff --git a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx index 1ea632481856..3ec415c3409d 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx @@ -10,6 +10,11 @@ import { AxisDefaultized } from '../models/axis'; import { ChartsTooltipClasses } from './chartsTooltipClasses'; import { DefaultChartsAxisTooltipContent } from './DefaultChartsAxisTooltipContent'; import { isCartesianSeriesType } from './utils'; +import colorGetter from '../internals/colorGetter'; + +type ChartSeriesDefaultizedWithColorGetter = ChartSeriesDefaultized & { + getColor: (dataIndex: number) => string; +}; export type ChartsAxisContentProps = { /** @@ -19,7 +24,7 @@ export type ChartsAxisContentProps = { /** * The series linked to the triggered axis. */ - series: ChartSeriesDefaultized[]; + series: ChartSeriesDefaultizedWithColorGetter[]; /** * The properties of the triggered axis. */ @@ -67,12 +72,19 @@ function ChartsAxisTooltipContent(props: { const item = series[seriesType]!.series[seriesId]; const axisKey = isXaxis ? item.xAxisKey : item.yAxisKey; if (axisKey === undefined || axisKey === USED_AXIS_ID) { - rep.push(series[seriesType]!.series[seriesId]); + const seriesToAdd = series[seriesType]!.series[seriesId]; + + const color = colorGetter( + seriesToAdd, + xAxis[seriesToAdd.xAxisKey ?? xAxisIds[0]], + yAxis[seriesToAdd.yAxisKey ?? yAxisIds[0]], + ); + rep.push({ ...seriesToAdd, getColor: color }); } }); }); return rep; - }, [USED_AXIS_ID, isXaxis, series]); + }, [USED_AXIS_ID, isXaxis, series, xAxis, xAxisIds, yAxis, yAxisIds]); const relevantAxis = React.useMemo(() => { return isXaxis ? xAxis[USED_AXIS_ID] : yAxis[USED_AXIS_ID]; diff --git a/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx index aa01fc563374..cdf2ff3a0105 100644 --- a/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx @@ -7,6 +7,8 @@ import { SeriesContext } from '../context/SeriesContextProvider'; import { ChartSeriesDefaultized, ChartSeriesType } from '../models/seriesType/config'; import { ChartsTooltipClasses } from './chartsTooltipClasses'; import { DefaultChartsItemTooltipContent } from './DefaultChartsItemTooltipContent'; +import { CartesianContext } from '../context/CartesianContextProvider'; +import colorGetter from '../internals/colorGetter'; export type ChartsItemContentProps = { /** @@ -21,6 +23,12 @@ export type ChartsItemContentProps * Override or extend the styles applied to the component. */ classes: ChartsTooltipClasses; + /** + * Get the color of the item with index `dataIndex`. + * @param {number} dataIndex The data index of the item. + * @returns {string} The color to display. + */ + getColor: (dataIndex: number) => string; sx?: SxProps; }; @@ -37,6 +45,21 @@ function ChartsItemTooltipContent(props: { itemData.seriesId ] as ChartSeriesDefaultized; + const axisData = React.useContext(CartesianContext); + + const { xAxis, yAxis, xAxisIds, yAxisIds } = axisData; + const defaultXAxisId = xAxisIds[0]; + const defaultYAxisId = yAxisIds[0]; + + const getColor = + series.type === 'pie' + ? colorGetter(series) + : colorGetter( + series, + xAxis[series.xAxisKey ?? defaultXAxisId], + yAxis[series.yAxisKey ?? defaultYAxisId], + ); + const Content = content ?? DefaultChartsItemTooltipContent; const chartTooltipContentProps = useSlotProps({ elementType: Content, @@ -46,6 +69,7 @@ function ChartsItemTooltipContent(props: { series, sx, classes, + getColor, }, ownerState: {}, }); @@ -61,6 +85,7 @@ ChartsItemTooltipContent.propTypes = { content: PropTypes.elementType, contentProps: PropTypes.shape({ classes: PropTypes.object, + getColor: PropTypes.func, itemData: PropTypes.shape({ dataIndex: PropTypes.number, seriesId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, diff --git a/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx index 856a45da8138..c59d6a0d5ac3 100644 --- a/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/DefaultChartsAxisTooltipContent.tsx @@ -38,32 +38,34 @@ function DefaultChartsAxisTooltipContent(props: ChartsAxisContentProps) { )} - {series.filter(isCartesianSeries).map(({ color, id, label, valueFormatter, data }) => { - // @ts-ignore - const formattedValue = valueFormatter(data[dataIndex] ?? null); - if (formattedValue == null) { - return null; - } - return ( - - - - + {series + .filter(isCartesianSeries) + .map(({ color, id, label, valueFormatter, data, getColor }) => { + // @ts-ignore + const formattedValue = valueFormatter(data[dataIndex] ?? null, { dataIndex }); + if (formattedValue == null) { + return null; + } + return ( + + + + - - {label ? {label} : null} - + + {label ? {label} : null} + - - {formattedValue} - - - ); - })} + + {formattedValue} + + + ); + })} diff --git a/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx b/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx index 4cb6774720c1..50ae8cae130a 100644 --- a/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx +++ b/packages/x-charts/src/ChartsTooltip/DefaultChartsItemTooltipContent.tsx @@ -15,7 +15,7 @@ import { CommonSeriesType } from '../models/seriesType/common'; function DefaultChartsItemTooltipContent( props: ChartsItemContentProps, ) { - const { series, itemData, sx, classes } = props; + const { series, itemData, sx, classes, getColor } = props; if (itemData.dataIndex === undefined || !series.data[itemData.dataIndex]) { return null; @@ -23,18 +23,18 @@ function DefaultChartsItemTooltipContent['valueFormatter'] - )?.(value); + )?.(value, { dataIndex: itemData.dataIndex }); return ( @@ -67,6 +67,12 @@ DefaultChartsItemTooltipContent.propTypes = { * Override or extend the styles applied to the component. */ classes: PropTypes.object.isRequired, + /** + * Get the color of the item with index `dataIndex`. + * @param {number} dataIndex The data index of the item. + * @returns {string} The color to display. + */ + getColor: PropTypes.func.isRequired, /** * The data used to identify the triggered item. */ diff --git a/packages/x-charts/src/ChartsTooltip/utils.tsx b/packages/x-charts/src/ChartsTooltip/utils.tsx index 816a587840d9..ed6f6d4714e6 100644 --- a/packages/x-charts/src/ChartsTooltip/utils.tsx +++ b/packages/x-charts/src/ChartsTooltip/utils.tsx @@ -94,6 +94,11 @@ export function isCartesianSeriesType(seriesType: string): seriesType is Cartesi return ['bar', 'line', 'scatter'].includes(seriesType); } +export function isCartesianSeries( + series: ChartSeriesDefaultized & { getColor: (dataIndex: number) => string }, +): series is ChartSeriesDefaultized & { + getColor: (dataIndex: number) => string; +}; export function isCartesianSeries( series: ChartSeriesDefaultized, ): series is ChartSeriesDefaultized { diff --git a/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx index 015229f63570..48f0fec8417d 100644 --- a/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx +++ b/packages/x-charts/src/ChartsVoronoiHandler/ChartsVoronoiHandler.tsx @@ -4,12 +4,12 @@ import { Delaunay } from 'd3-delaunay'; import useEnhancedEffect from '@mui/utils/useEnhancedEffect'; import { InteractionContext } from '../context/InteractionProvider'; import { CartesianContext } from '../context/CartesianContextProvider'; -import { SvgContext, DrawingContext } from '../context/DrawingProvider'; import { SeriesContext } from '../context/SeriesContextProvider'; import { getValueToPositionMapper } from '../hooks/useScale'; import { getSVGPoint } from '../internals/utils'; import { ScatterItemIdentifier } from '../models'; import { SeriesId } from '../models/seriesType/common'; +import { useDrawingArea, useSvgRef } from '../hooks'; export type ChartsVoronoiHandlerProps = { /** @@ -29,8 +29,8 @@ type VoronoiSeries = { seriesId: SeriesId; startIndex: number; endIndex: number function ChartsVoronoiHandler(props: ChartsVoronoiHandlerProps) { const { voronoiMaxRadius, onItemClick } = props; - const svgRef = React.useContext(SvgContext); - const { width, height, top, left } = React.useContext(DrawingContext); + const svgRef = useSvgRef(); + const { left, top, width, height } = useDrawingArea(); const { xAxis, yAxis, xAxisIds, yAxisIds } = React.useContext(CartesianContext); const { dispatch } = React.useContext(InteractionContext); diff --git a/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx b/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx index 967e95415b9a..1bd054d7e6e3 100644 --- a/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx +++ b/packages/x-charts/src/ChartsXAxis/ChartsXAxis.tsx @@ -4,7 +4,6 @@ import { useSlotProps } from '@mui/base/utils'; import { unstable_composeClasses as composeClasses } from '@mui/utils'; import { useThemeProps, useTheme, Theme } from '@mui/material/styles'; import { CartesianContext } from '../context/CartesianContextProvider'; -import { DrawingContext } from '../context/DrawingProvider'; import { useTicks, TickItemType } from '../hooks/useTicks'; import { AxisDefaultized, ChartsXAxisProps } from '../models/axis'; import { getAxisUtilityClass } from '../ChartsAxis/axisClasses'; @@ -12,6 +11,7 @@ import { AxisRoot } from '../internals/components/AxisSharedComponents'; import { ChartsText, ChartsTextProps } from '../ChartsText'; import { getMinXTranslation } from '../internals/geometry'; import { useMounted } from '../hooks/useMounted'; +import { useDrawingArea } from '../hooks/useDrawingArea'; import { getWordsByLines } from '../internals/getWordsByLines'; const useUtilityClasses = (ownerState: ChartsXAxisProps & { theme: Theme }) => { @@ -61,7 +61,7 @@ function addLabelDimension( // Filter label to avoid overlap let currentTextLimit = 0; - let previouseTextLimit = 0; + let previousTextLimit = 0; const direction = reverse ? -1 : 1; return withDimension.map((item, labelIndex) => { const { width, offset, labelOffset, height } = item; @@ -71,12 +71,12 @@ function addLabelDimension( const gapRatio = 1.2; // Ratio applied to the minimal distance to add some margin. currentTextLimit = textPosition - (direction * (gapRatio * distance)) / 2; - if (labelIndex > 0 && direction * currentTextLimit < direction * previouseTextLimit) { + if (labelIndex > 0 && direction * currentTextLimit < direction * previousTextLimit) { // Except for the first label, we skip all label that overlap with the last accepted. - // Notice that the early return prevents `previouseTextLimit` from being updated. + // Notice that the early return prevents `previousTextLimit` from being updated. return { ...item, skipLabel: true }; } - previouseTextLimit = textPosition + (direction * (gapRatio * distance)) / 2; + previousTextLimit = textPosition + (direction * (gapRatio * distance)) / 2; return item; }); } @@ -130,8 +130,7 @@ function ChartsXAxis(inProps: ChartsXAxisProps) { const theme = useTheme(); const classes = useUtilityClasses({ ...defaultizedProps, theme }); - - const { left, top, width, height } = React.useContext(DrawingContext); + const { left, top, width, height } = useDrawingArea(); const tickSize = disableTicks ? 4 : tickSizeProp; @@ -347,7 +346,7 @@ ChartsXAxis.propTypes = { */ tickMinStep: PropTypes.number, /** - * The number of ticks. This number is not guaranted. + * The number of ticks. This number is not guaranteed. * Not supported by categorical axis (band, points). */ tickNumber: PropTypes.number, diff --git a/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx b/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx index e74ad4ab9cca..81fb0caa843d 100644 --- a/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx +++ b/packages/x-charts/src/ChartsYAxis/ChartsYAxis.tsx @@ -4,8 +4,8 @@ import { useSlotProps } from '@mui/base/utils'; import { unstable_composeClasses as composeClasses } from '@mui/utils'; import { useThemeProps, useTheme, Theme } from '@mui/material/styles'; import { CartesianContext } from '../context/CartesianContextProvider'; -import { DrawingContext } from '../context/DrawingProvider'; import { useTicks } from '../hooks/useTicks'; +import { useDrawingArea } from '../hooks/useDrawingArea'; import { ChartsYAxisProps } from '../models/axis'; import { AxisRoot } from '../internals/components/AxisSharedComponents'; import { ChartsText, ChartsTextProps } from '../ChartsText'; @@ -69,12 +69,13 @@ function ChartsYAxis(inProps: ChartsYAxisProps) { tickPlacement, tickLabelPlacement, tickInterval, + tickLabelInterval, } = defaultizedProps; const theme = useTheme(); const classes = useUtilityClasses({ ...defaultizedProps, theme }); - const { left, top, width, height } = React.useContext(DrawingContext); + const { left, top, width, height } = useDrawingArea(); const tickSize = disableTicks ? 4 : tickSizeProp; @@ -143,9 +144,11 @@ function ChartsYAxis(inProps: ChartsYAxisProps) { /> )} - {yTicks.map(({ formattedValue, offset, labelOffset }, index) => { + {yTicks.map(({ formattedValue, offset, labelOffset, value }, index) => { const xTickLabel = positionSign * (tickSize + 2); const yTickLabel = labelOffset; + const skipLabel = + typeof tickLabelInterval === 'function' && !tickLabelInterval?.(value, index); return ( {!disableTicks && ( @@ -156,7 +159,7 @@ function ChartsYAxis(inProps: ChartsYAxisProps) { /> )} - {formattedValue !== undefined && ( + {formattedValue !== undefined && !skipLabel && ( styles.root, })<{ ownerState: AreaElementOwnerState }>(({ ownerState }) => ({ stroke: 'none', - fill: ownerState.isHighlighted - ? d3Color(ownerState.color)!.brighter(1).formatHex() - : d3Color(ownerState.color)!.brighter(0.5).formatHex(), + fill: + (ownerState.gradientId && `url(#${ownerState.gradientId})`) || + (ownerState.isHighlighted && d3Color(ownerState.color)!.brighter(1).formatHex()) || + d3Color(ownerState.color)!.brighter(0.5).formatHex(), transition: 'opacity 0.2s ease-in, fill 0.2s ease-in', opacity: ownerState.isFaded ? 0.3 : 1, })); @@ -43,7 +44,8 @@ export interface AnimatedAreaProps extends React.ComponentPropsWithoutRef<'path' */ function AnimatedArea(props: AnimatedAreaProps) { const { d, skipAnimation, ownerState, ...other } = props; - const { left, top, right, bottom, width, height, chartId } = React.useContext(DrawingContext); + const { left, top, right, bottom, width, height } = useDrawingArea(); + const chartId = useChartId(); const path = useAnimatedPath(d!, skipAnimation); @@ -76,6 +78,7 @@ AnimatedArea.propTypes = { ownerState: PropTypes.shape({ classes: PropTypes.object, color: PropTypes.string.isRequired, + gradientId: PropTypes.string, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, isFaded: PropTypes.bool.isRequired, isHighlighted: PropTypes.bool.isRequired, diff --git a/packages/x-charts/src/LineChart/AnimatedLine.tsx b/packages/x-charts/src/LineChart/AnimatedLine.tsx index 411e31142a76..ceb5fc9456c3 100644 --- a/packages/x-charts/src/LineChart/AnimatedLine.tsx +++ b/packages/x-charts/src/LineChart/AnimatedLine.tsx @@ -4,9 +4,10 @@ import { animated, useSpring } from '@react-spring/web'; import { color as d3Color } from 'd3-color'; import { styled } from '@mui/material/styles'; import { useAnimatedPath } from '../internals/useAnimatedPath'; -import { DrawingContext } from '../context/DrawingProvider'; import { cleanId } from '../internals/utils'; import type { LineElementOwnerState } from './LineElement'; +import { useChartId } from '../hooks/useChartId'; +import { useDrawingArea } from '../hooks/useDrawingArea'; export const LineElementPath = styled(animated.path, { name: 'MuiLineElement', @@ -16,9 +17,10 @@ export const LineElementPath = styled(animated.path, { strokeWidth: 2, strokeLinejoin: 'round', fill: 'none', - stroke: ownerState.isHighlighted - ? d3Color(ownerState.color)!.brighter(0.5).formatHex() - : ownerState.color, + stroke: + (ownerState.gradientId && `url(#${ownerState.gradientId})`) || + (ownerState.isHighlighted && d3Color(ownerState.color)!.brighter(0.5).formatHex()) || + ownerState.color, transition: 'opacity 0.2s ease-in, stroke 0.2s ease-in', opacity: ownerState.isFaded ? 0.3 : 1, })); @@ -45,7 +47,8 @@ export interface AnimatedLineProps extends React.ComponentPropsWithoutRef<'path' */ function AnimatedLine(props: AnimatedLineProps) { const { d, skipAnimation, ownerState, ...other } = props; - const { left, top, bottom, width, height, right, chartId } = React.useContext(DrawingContext); + const { left, top, bottom, width, height, right } = useDrawingArea(); + const chartId = useChartId(); const path = useAnimatedPath(d, skipAnimation); @@ -78,6 +81,7 @@ AnimatedLine.propTypes = { ownerState: PropTypes.shape({ classes: PropTypes.object, color: PropTypes.string.isRequired, + gradientId: PropTypes.string, id: PropTypes.oneOfType([PropTypes.number, PropTypes.string]).isRequired, isFaded: PropTypes.bool.isRequired, isHighlighted: PropTypes.bool.isRequired, diff --git a/packages/x-charts/src/LineChart/AreaElement.tsx b/packages/x-charts/src/LineChart/AreaElement.tsx index 8536941dd886..bd62f4ab8b28 100644 --- a/packages/x-charts/src/LineChart/AreaElement.tsx +++ b/packages/x-charts/src/LineChart/AreaElement.tsx @@ -28,6 +28,7 @@ export type AreaElementClassKey = keyof AreaElementClasses; export interface AreaElementOwnerState { id: SeriesId; color: string; + gradientId?: string; isFaded: boolean; isHighlighted: boolean; classes?: Partial; @@ -97,6 +98,7 @@ function AreaElement(props: AreaElementProps) { id, classes: innerClasses, color, + gradientId, highlightScope, slots, slotProps, @@ -116,6 +118,7 @@ function AreaElement(props: AreaElementProps) { id, classes: innerClasses, color, + gradientId, isFaded, isHighlighted, }; @@ -146,6 +149,7 @@ AreaElement.propTypes = { classes: PropTypes.object, color: PropTypes.string.isRequired, d: PropTypes.string.isRequired, + gradientId: PropTypes.string, highlightScope: PropTypes.shape({ faded: PropTypes.oneOf(['global', 'none', 'series']), highlighted: PropTypes.oneOf(['item', 'none', 'series']), diff --git a/packages/x-charts/src/LineChart/AreaPlot.tsx b/packages/x-charts/src/LineChart/AreaPlot.tsx index f441e340e118..d704a0dd33ce 100644 --- a/packages/x-charts/src/LineChart/AreaPlot.tsx +++ b/packages/x-charts/src/LineChart/AreaPlot.tsx @@ -13,6 +13,7 @@ import { getValueToPositionMapper } from '../hooks/useScale'; import getCurveFactory from '../internals/getCurve'; import { DEFAULT_X_AXIS_KEY } from '../constants'; import { LineItemIdentifier } from '../models/seriesType/line'; +import { useChartGradient } from '../internals/components/ChartsAxesGradients'; export interface AreaPlotSlots extends AreaElementSlots {} @@ -59,6 +60,11 @@ const useAggregatedData = () => { const yScale = yAxis[yAxisKey].scale; const xData = xAxis[xAxisKey].data; + const gradientUsed: [string, 'x' | 'y'] | undefined = + (yAxis[yAxisKey].colorScale && [yAxisKey, 'y']) || + (xAxis[xAxisKey].colorScale && [xAxisKey, 'x']) || + undefined; + if (process.env.NODE_ENV !== 'production') { if (xData === undefined) { throw new Error( @@ -92,6 +98,7 @@ const useAggregatedData = () => { const d = areaPath.curve(curve)(d3Data) || ''; return { ...series[seriesId], + gradientUsed, d, seriesId, }; @@ -113,6 +120,7 @@ const useAggregatedData = () => { function AreaPlot(props: AreaPlotProps) { const { slots, slotProps, onItemClick, skipAnimation, ...other } = props; + const getGradientId = useChartGradient(); const completedData = useAggregatedData(); return ( @@ -120,13 +128,14 @@ function AreaPlot(props: AreaPlotProps) { {completedData .reverse() .map( - ({ d, seriesId, color, highlightScope, area }) => + ({ d, seriesId, color, highlightScope, area, gradientUsed }) => !!area && ( ; @@ -97,6 +98,7 @@ function LineElement(props: LineElementProps) { id, classes: innerClasses, color, + gradientId, highlightScope, slots, slotProps, @@ -115,6 +117,7 @@ function LineElement(props: LineElementProps) { id, classes: innerClasses, color, + gradientId, isFaded, isHighlighted, }; @@ -145,6 +148,7 @@ LineElement.propTypes = { classes: PropTypes.object, color: PropTypes.string.isRequired, d: PropTypes.string.isRequired, + gradientId: PropTypes.string, highlightScope: PropTypes.shape({ faded: PropTypes.oneOf(['global', 'none', 'series']), highlighted: PropTypes.oneOf(['item', 'none', 'series']), diff --git a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx index 05fb06dca704..6dd72d93d680 100644 --- a/packages/x-charts/src/LineChart/LineHighlightPlot.tsx +++ b/packages/x-charts/src/LineChart/LineHighlightPlot.tsx @@ -6,6 +6,7 @@ import { LineHighlightElement, LineHighlightElementProps } from './LineHighlight import { getValueToPositionMapper } from '../hooks/useScale'; import { InteractionContext } from '../context/InteractionProvider'; import { DEFAULT_X_AXIS_KEY } from '../constants'; +import getColor from './getColor'; export interface LineHighlightPlotSlots { lineHighlight?: React.JSXElementConstructor; @@ -88,13 +89,16 @@ function LineHighlightPlot(props: LineHighlightPlotProps) { } should have data property to be able to display a line plot.`, ); } + const x = xScale(xData[highlightedIndex]); const y = yScale(stackedData[highlightedIndex][1])!; // This should not be undefined since y should not be a band scale + + const colorGetter = getColor(series[seriesId], xAxis[xAxisKey], yAxis[yAxisKey]); return ( { const yScale = yAxis[yAxisKey].scale; const xData = xAxis[xAxisKey].data; + const gradientUsed: [string, 'x' | 'y'] | undefined = + (yAxis[yAxisKey].colorScale && [yAxisKey, 'y']) || + (xAxis[xAxisKey].colorScale && [xAxisKey, 'x']) || + undefined; + if (process.env.NODE_ENV !== 'production') { if (xData === undefined) { throw new Error( @@ -90,6 +96,7 @@ const useAggregatedData = () => { const d = linePath.curve(getCurveFactory(series[seriesId].curve))(d3Data) || ''; return { ...series[seriesId], + gradientUsed, d, seriesId, }; @@ -110,17 +117,18 @@ const useAggregatedData = () => { function LinePlot(props: LinePlotProps) { const { slots, slotProps, skipAnimation, onItemClick, ...other } = props; + const getGradientId = useChartGradient(); const completedData = useAggregatedData(); - return ( - {completedData.map(({ d, seriesId, color, highlightScope }) => { + {completedData.map(({ d, seriesId, color, highlightScope, gradientUsed }) => { return ( ; @@ -56,7 +57,7 @@ function MarkPlot(props: MarkPlotProps) { const seriesData = React.useContext(SeriesContext).line; const axisData = React.useContext(CartesianContext); - const { chartId } = React.useContext(DrawingContext); + const chartId = useChartId(); const Mark = slots?.mark ?? MarkElement; @@ -113,6 +114,8 @@ function MarkPlot(props: MarkPlotProps) { const clipId = cleanId(`${chartId}-${seriesId}-line-clip`); // We assume that if displaying line mark, the line will also be rendered + const colorGetter = getColor(series[seriesId], xAxis[xAxisKey], yAxis[yAxisKey]); + return ( {xData @@ -153,7 +156,7 @@ function MarkPlot(props: MarkPlotProps) { id={seriesId} dataIndex={index} shape="circle" - color={series[seriesId].color} + color={colorGetter(index)} x={x} y={y!} // Don't know why TS doesn't get from the filter that y can't be null highlightScope={series[seriesId].highlightScope} diff --git a/packages/x-charts/src/LineChart/getColor.ts b/packages/x-charts/src/LineChart/getColor.ts new file mode 100644 index 000000000000..af924c4fc574 --- /dev/null +++ b/packages/x-charts/src/LineChart/getColor.ts @@ -0,0 +1,34 @@ +import { AxisDefaultized } from '../models/axis'; +import { DefaultizedLineSeriesType } from '../models/seriesType/line'; + +export default function getColor( + series: DefaultizedLineSeriesType, + xAxis: AxisDefaultized, + yAxis: AxisDefaultized, +) { + const yColorScale = yAxis.colorScale; + const xColorScale = xAxis.colorScale; + + if (yColorScale) { + return (dataIndex: number) => { + const value = series.data[dataIndex]; + const color = value === null ? series.color : yColorScale(value); + if (color === null) { + return series.color; + } + return color; + }; + } + if (xColorScale) { + return (dataIndex: number) => { + const value = xAxis.data?.[dataIndex]; + const color = value === null ? series.color : xColorScale(value); + if (color === null) { + return series.color; + } + return color; + }; + } + + return () => series.color; +} diff --git a/packages/x-charts/src/PieChart/PieChart.tsx b/packages/x-charts/src/PieChart/PieChart.tsx index 2100b6f194a9..b582925577c4 100644 --- a/packages/x-charts/src/PieChart/PieChart.tsx +++ b/packages/x-charts/src/PieChart/PieChart.tsx @@ -450,12 +450,39 @@ PieChart.propTypes = { width: PropTypes.number, /** * The configuration of the x-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_X_AXIS_KEY`. + * If not provided, a default axis config is used. */ xAxis: PropTypes.arrayOf( PropTypes.shape({ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, + colorMap: PropTypes.oneOfType([ + PropTypes.shape({ + color: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string.isRequired), + PropTypes.func, + ]).isRequired, + max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + type: PropTypes.oneOf(['continuous']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + thresholds: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, + ).isRequired, + type: PropTypes.oneOf(['piecewise']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + type: PropTypes.oneOf(['ordinal']).isRequired, + unknownColor: PropTypes.string, + values: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) + .isRequired, + ), + }), + ]), data: PropTypes.array, dataKey: PropTypes.string, disableLine: PropTypes.bool, @@ -493,12 +520,39 @@ PieChart.propTypes = { ), /** * The configuration of the y-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_Y_AXIS_KEY`. + * If not provided, a default axis config is used. */ yAxis: PropTypes.arrayOf( PropTypes.shape({ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, + colorMap: PropTypes.oneOfType([ + PropTypes.shape({ + color: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string.isRequired), + PropTypes.func, + ]).isRequired, + max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + type: PropTypes.oneOf(['continuous']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + thresholds: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, + ).isRequired, + type: PropTypes.oneOf(['piecewise']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + type: PropTypes.oneOf(['ordinal']).isRequired, + unknownColor: PropTypes.string, + values: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) + .isRequired, + ), + }), + ]), data: PropTypes.array, dataKey: PropTypes.string, disableLine: PropTypes.bool, diff --git a/packages/x-charts/src/PieChart/PiePlot.tsx b/packages/x-charts/src/PieChart/PiePlot.tsx index 9b05bc59ef49..5ed37cb0fcec 100644 --- a/packages/x-charts/src/PieChart/PiePlot.tsx +++ b/packages/x-charts/src/PieChart/PiePlot.tsx @@ -5,6 +5,7 @@ import { DrawingContext } from '../context/DrawingProvider'; import { PieArcPlot, PieArcPlotProps, PieArcPlotSlotProps, PieArcPlotSlots } from './PieArcPlot'; import { PieArcLabelPlotSlots, PieArcLabelPlotSlotProps, PieArcLabelPlot } from './PieArcLabelPlot'; import { getPercentageValue } from '../internals/utils'; +import { getPieCoordinates } from './getPieCoordinates'; export interface PiePlotSlots extends PieArcPlotSlots, PieArcLabelPlotSlots {} @@ -41,7 +42,6 @@ function PiePlot(props: PiePlotProps) { if (seriesData === undefined) { return null; } - const availableRadius = Math.min(width, height) / 2; const { series, seriesOrder } = seriesData; @@ -61,13 +61,16 @@ function PiePlot(props: PiePlotProps) { highlightScope, } = series[seriesId]; + const { cx, cy, availableRadius } = getPieCoordinates( + { cx: cxParam, cy: cyParam }, + { width, height }, + ); + const outerRadius = getPercentageValue( outerRadiusParam ?? availableRadius, availableRadius, ); const innerRadius = getPercentageValue(innerRadiusParam ?? 0, availableRadius); - const cx = getPercentageValue(cxParam ?? '50%', width); - const cy = getPercentageValue(cyParam ?? '50%', height); return ( ); diff --git a/packages/x-charts/src/PieChart/formatter.ts b/packages/x-charts/src/PieChart/formatter.ts index 6df6aa01072b..0b4115b39aed 100644 --- a/packages/x-charts/src/PieChart/formatter.ts +++ b/packages/x-charts/src/PieChart/formatter.ts @@ -40,9 +40,11 @@ const formatter: Formatter<'pie'> = (params) => { id: item.id ?? `auto-generated-pie-id-${seriesId}-${index}`, ...arcs[index], })) - .map((item) => ({ + .map((item, index) => ({ ...item, - formattedValue: series[seriesId].valueFormatter?.(item) ?? item.value.toLocaleString(), + formattedValue: + series[seriesId].valueFormatter?.(item, { dataIndex: index }) ?? + item.value.toLocaleString(), })), }; }); diff --git a/packages/x-charts/src/PieChart/getColor.ts b/packages/x-charts/src/PieChart/getColor.ts new file mode 100644 index 000000000000..937bd6d45407 --- /dev/null +++ b/packages/x-charts/src/PieChart/getColor.ts @@ -0,0 +1,7 @@ +import { DefaultizedPieSeriesType } from '../models/seriesType/pie'; + +export default function getColor(series: DefaultizedPieSeriesType) { + return (dataIndex: number) => { + return series.data[dataIndex].color; + }; +} diff --git a/packages/x-charts/src/PieChart/getPieCoordinates.ts b/packages/x-charts/src/PieChart/getPieCoordinates.ts new file mode 100644 index 000000000000..0f5d843c52a1 --- /dev/null +++ b/packages/x-charts/src/PieChart/getPieCoordinates.ts @@ -0,0 +1,17 @@ +import { DrawingArea } from '../context/DrawingProvider'; +import { getPercentageValue } from '../internals/utils'; +import { DefaultizedPieSeriesType } from '../models/seriesType/pie'; + +export function getPieCoordinates( + series: Pick, + drawing: Pick, +): { cx: number; cy: number; availableRadius: number } { + const { height, width } = drawing; + const { cx: cxParam, cy: cyParam } = series; + + const availableRadius = Math.min(width, height) / 2; + const cx = getPercentageValue(cxParam ?? '50%', width); + const cy = getPercentageValue(cyParam ?? '50%', height); + + return { cx, cy, availableRadius }; +} diff --git a/packages/x-charts/src/PieChart/index.tsx b/packages/x-charts/src/PieChart/index.tsx index fcf167a4a57f..68d23ddbe290 100644 --- a/packages/x-charts/src/PieChart/index.tsx +++ b/packages/x-charts/src/PieChart/index.tsx @@ -6,3 +6,5 @@ export * from './PieArcLabelPlot'; export * from './PieArc'; export * from './PieArcLabel'; + +export * from './getPieCoordinates'; diff --git a/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx b/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx index 332bfbe77e1d..4319acdb555a 100644 --- a/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx +++ b/packages/x-charts/src/ResponsiveChartContainer/ResponsiveChartContainer.tsx @@ -114,12 +114,39 @@ ResponsiveChartContainer.propTypes = { width: PropTypes.number, /** * The configuration of the x-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_X_AXIS_KEY`. + * If not provided, a default axis config is used. */ xAxis: PropTypes.arrayOf( PropTypes.shape({ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, + colorMap: PropTypes.oneOfType([ + PropTypes.shape({ + color: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string.isRequired), + PropTypes.func, + ]).isRequired, + max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + type: PropTypes.oneOf(['continuous']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + thresholds: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, + ).isRequired, + type: PropTypes.oneOf(['piecewise']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + type: PropTypes.oneOf(['ordinal']).isRequired, + unknownColor: PropTypes.string, + values: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) + .isRequired, + ), + }), + ]), data: PropTypes.array, dataKey: PropTypes.string, disableLine: PropTypes.bool, @@ -157,12 +184,39 @@ ResponsiveChartContainer.propTypes = { ), /** * The configuration of the y-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_Y_AXIS_KEY`. + * If not provided, a default axis config is used. */ yAxis: PropTypes.arrayOf( PropTypes.shape({ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, + colorMap: PropTypes.oneOfType([ + PropTypes.shape({ + color: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string.isRequired), + PropTypes.func, + ]).isRequired, + max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + type: PropTypes.oneOf(['continuous']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + thresholds: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, + ).isRequired, + type: PropTypes.oneOf(['piecewise']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + type: PropTypes.oneOf(['ordinal']).isRequired, + unknownColor: PropTypes.string, + values: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) + .isRequired, + ), + }), + ]), data: PropTypes.array, dataKey: PropTypes.string, disableLine: PropTypes.bool, diff --git a/packages/x-charts/src/ScatterChart/Scatter.tsx b/packages/x-charts/src/ScatterChart/Scatter.tsx index a931e81c1406..e5884bccaf3e 100644 --- a/packages/x-charts/src/ScatterChart/Scatter.tsx +++ b/packages/x-charts/src/ScatterChart/Scatter.tsx @@ -21,6 +21,7 @@ export interface ScatterProps { yScale: D3Scale; markerSize: number; color: string; + colorGetter?: (dataIndex: number) => string; /** * Callback fired when clicking on a scatter item. * @param {MouseEvent} event Mouse event recorded on the `` element. @@ -43,7 +44,7 @@ export interface ScatterProps { * - [Scatter API](https://mui.com/x/api/charts/scatter/) */ function Scatter(props: ScatterProps) { - const { series, xScale, yScale, color, markerSize, onItemClick } = props; + const { series, xScale, yScale, color, colorGetter, markerSize, onItemClick } = props; const highlightScope: HighlightScope = React.useMemo( () => ({ highlighted: 'item', faded: 'global', ...series.highlightScope }), @@ -68,6 +69,7 @@ function Scatter(props: ScatterProps) { const temp: (ScatterValueType & { dataIndex: number; + color: string; isHighlighted: boolean; isFaded: boolean; interactionProps: ReturnType; @@ -93,12 +95,23 @@ function Scatter(props: ScatterProps) { interactionProps: getInteractionItemProps(pointCtx), id: scatterPoint.id, dataIndex: i, + color: colorGetter ? colorGetter(i) : color, }); } } return temp; - }, [xScale, yScale, series.data, series.id, item, highlightScope, getInteractionItemProps]); + }, [ + xScale, + yScale, + series.data, + series.id, + item, + highlightScope, + getInteractionItemProps, + color, + colorGetter, + ]); return ( @@ -109,7 +122,7 @@ function Scatter(props: ScatterProps) { cy={0} r={(dataPoint.isHighlighted ? 1.2 : 1) * markerSize} transform={`translate(${dataPoint.x}, ${dataPoint.y})`} - fill={color} + fill={dataPoint.color} opacity={(dataPoint.isFaded && 0.3) || 1} onClick={ onItemClick && @@ -134,6 +147,7 @@ Scatter.propTypes = { // | To update them edit the TypeScript types and run "yarn proptypes" | // ---------------------------------------------------------------------- color: PropTypes.string.isRequired, + colorGetter: PropTypes.func, markerSize: PropTypes.number.isRequired, /** * Callback fired when clicking on a scatter item. diff --git a/packages/x-charts/src/ScatterChart/ScatterChart.tsx b/packages/x-charts/src/ScatterChart/ScatterChart.tsx index 6ed8c16779db..9f14ccef431e 100644 --- a/packages/x-charts/src/ScatterChart/ScatterChart.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterChart.tsx @@ -445,12 +445,39 @@ ScatterChart.propTypes = { width: PropTypes.number, /** * The configuration of the x-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_X_AXIS_KEY`. + * If not provided, a default axis config is used. */ xAxis: PropTypes.arrayOf( PropTypes.shape({ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, + colorMap: PropTypes.oneOfType([ + PropTypes.shape({ + color: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string.isRequired), + PropTypes.func, + ]).isRequired, + max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + type: PropTypes.oneOf(['continuous']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + thresholds: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, + ).isRequired, + type: PropTypes.oneOf(['piecewise']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + type: PropTypes.oneOf(['ordinal']).isRequired, + unknownColor: PropTypes.string, + values: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) + .isRequired, + ), + }), + ]), data: PropTypes.array, dataKey: PropTypes.string, disableLine: PropTypes.bool, @@ -488,12 +515,39 @@ ScatterChart.propTypes = { ), /** * The configuration of the y-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_Y_AXIS_KEY`. + * If not provided, a default axis config is used. */ yAxis: PropTypes.arrayOf( PropTypes.shape({ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, + colorMap: PropTypes.oneOfType([ + PropTypes.shape({ + color: PropTypes.oneOfType([ + PropTypes.arrayOf(PropTypes.string.isRequired), + PropTypes.func, + ]).isRequired, + max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + type: PropTypes.oneOf(['continuous']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + thresholds: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, + ).isRequired, + type: PropTypes.oneOf(['piecewise']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + type: PropTypes.oneOf(['ordinal']).isRequired, + unknownColor: PropTypes.string, + values: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) + .isRequired, + ), + }), + ]), data: PropTypes.array, dataKey: PropTypes.string, disableLine: PropTypes.bool, diff --git a/packages/x-charts/src/ScatterChart/ScatterPlot.tsx b/packages/x-charts/src/ScatterChart/ScatterPlot.tsx index 4f8298474103..5c19c2cca9c6 100644 --- a/packages/x-charts/src/ScatterChart/ScatterPlot.tsx +++ b/packages/x-charts/src/ScatterChart/ScatterPlot.tsx @@ -3,6 +3,7 @@ import PropTypes from 'prop-types'; import { Scatter, ScatterProps } from './Scatter'; import { SeriesContext } from '../context/SeriesContextProvider'; import { CartesianContext } from '../context/CartesianContextProvider'; +import getColor from './getColor'; export interface ScatterPlotSlots { scatter?: React.JSXElementConstructor; @@ -55,6 +56,11 @@ function ScatterPlot(props: ScatterPlotProps) { {seriesOrder.map((seriesId) => { const { id, xAxisKey, yAxisKey, markerSize, color } = series[seriesId]; + const colorGetter = getColor( + series[seriesId], + xAxis[xAxisKey ?? defaultXAxisId], + yAxis[yAxisKey ?? defaultYAxisId], + ); const xScale = xAxis[xAxisKey ?? defaultXAxisId].scale; const yScale = yAxis[yAxisKey ?? defaultYAxisId].scale; return ( @@ -63,6 +69,7 @@ function ScatterPlot(props: ScatterPlotProps) { xScale={xScale} yScale={yScale} color={color} + colorGetter={colorGetter} markerSize={markerSize ?? 4} series={series[seriesId]} onItemClick={onItemClick} diff --git a/packages/x-charts/src/ScatterChart/getColor.ts b/packages/x-charts/src/ScatterChart/getColor.ts new file mode 100644 index 000000000000..38cb1dc07f41 --- /dev/null +++ b/packages/x-charts/src/ScatterChart/getColor.ts @@ -0,0 +1,34 @@ +import { AxisDefaultized } from '../models/axis'; +import { DefaultizedScatterSeriesType } from '../models/seriesType/scatter'; + +export default function getColor( + series: DefaultizedScatterSeriesType, + xAxis: AxisDefaultized, + yAxis: AxisDefaultized, +) { + const yColorScale = yAxis.colorScale; + const xColorScale = xAxis.colorScale; + + if (yColorScale) { + return (dataIndex: number) => { + const value = series.data[dataIndex]; + const color = value === null ? series.color : yColorScale(value.y); + if (color === null) { + return series.color; + } + return color; + }; + } + if (xColorScale) { + return (dataIndex: number) => { + const value = series.data[dataIndex]; + const color = value === null ? series.color : xColorScale(value.x); + if (color === null) { + return series.color; + } + return color; + }; + } + + return () => series.color; +} diff --git a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx index d9c036d02185..977dc7510855 100644 --- a/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx +++ b/packages/x-charts/src/SparkLineChart/SparkLineChart.tsx @@ -347,6 +347,31 @@ SparkLineChart.propTypes = { xAxis: PropTypes.shape({ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), classes: PropTypes.object, + colorMap: PropTypes.oneOfType([ + PropTypes.shape({ + color: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.string.isRequired), PropTypes.func]) + .isRequired, + max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]), + type: PropTypes.oneOf(['continuous']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + thresholds: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]).isRequired, + ).isRequired, + type: PropTypes.oneOf(['piecewise']).isRequired, + }), + PropTypes.shape({ + colors: PropTypes.arrayOf(PropTypes.string).isRequired, + type: PropTypes.oneOf(['ordinal']).isRequired, + unknownColor: PropTypes.string, + values: PropTypes.arrayOf( + PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number, PropTypes.string]) + .isRequired, + ), + }), + ]), data: PropTypes.array, dataKey: PropTypes.string, disableLine: PropTypes.bool, diff --git a/packages/x-charts/src/context/CartesianContextProvider.tsx b/packages/x-charts/src/context/CartesianContextProvider.tsx index 9a9cb62b8852..2cc81afd655f 100644 --- a/packages/x-charts/src/context/CartesianContextProvider.tsx +++ b/packages/x-charts/src/context/CartesianContextProvider.tsx @@ -14,7 +14,6 @@ import { } from '../LineChart/extremums'; import { AxisConfig, AxisDefaultized, isBandScaleConfig, isPointScaleConfig } from '../models/axis'; import { getScale } from '../internals/getScale'; -import { DrawingContext } from './DrawingProvider'; import { SeriesContext } from './SeriesContextProvider'; import { DEFAULT_X_AXIS_KEY, DEFAULT_Y_AXIS_KEY } from '../constants'; import { @@ -26,17 +25,19 @@ import { } from '../models/seriesType/config'; import { MakeOptional } from '../models/helpers'; import { getTickNumber } from '../hooks/useTicks'; +import { useDrawingArea } from '../hooks/useDrawingArea'; import { SeriesId } from '../models/seriesType/common'; +import { getColorScale, getOrdinalColorScale } from '../internals/colorScale'; export type CartesianContextProviderProps = { /** * The configuration of the x-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_X_AXIS_KEY`. + * If not provided, a default axis config is used. */ xAxis?: MakeOptional[]; /** * The configuration of the y-axes. - * If not provided, a default axis config is used with id set to `DEFAULT_Y_AXIS_KEY`. + * If not provided, a default axis config is used. */ yAxis?: MakeOptional[]; /** @@ -97,7 +98,7 @@ if (process.env.NODE_ENV !== 'production') { function CartesianContextProvider(props: CartesianContextProviderProps) { const { xAxis: inXAxis, yAxis: inYAxis, dataset, children } = props; const formattedSeries = React.useContext(SeriesContext); - const drawingArea = React.useContext(DrawingContext); + const drawingArea = useDrawingArea(); const xAxis = React.useMemo( () => @@ -206,6 +207,11 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { .paddingInner(categoryGapRatio) .paddingOuter(categoryGapRatio / 2), tickNumber: axis.data!.length, + colorScale: + axis.colorMap && + (axis.colorMap.type === 'ordinal' + ? getOrdinalColorScale({ values: axis.data, ...axis.colorMap }) + : getColorScale(axis.colorMap)), }; } if (isPointScaleConfig(axis)) { @@ -213,6 +219,11 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { ...axis, scale: scalePoint(axis.data!, range), tickNumber: axis.data!.length, + colorScale: + axis.colorMap && + (axis.colorMap.type === 'ordinal' + ? getOrdinalColorScale({ values: axis.data, ...axis.colorMap }) + : getColorScale(axis.colorMap)), }; } if (axis.scaleType === 'band' || axis.scaleType === 'point') { @@ -234,6 +245,7 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { scaleType, scale: niceScale.domain(domain), tickNumber, + colorScale: axis.colorMap && getColorScale(axis.colorMap), } as AxisDefaultized; }); @@ -262,6 +274,11 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { .paddingInner(categoryGapRatio) .paddingOuter(categoryGapRatio / 2), tickNumber: axis.data!.length, + colorScale: + axis.colorMap && + (axis.colorMap.type === 'ordinal' + ? getOrdinalColorScale({ values: axis.data, ...axis.colorMap }) + : getColorScale(axis.colorMap)), }; } if (isPointScaleConfig(axis)) { @@ -269,6 +286,11 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { ...axis, scale: scalePoint(axis.data!, [range[1], range[0]]), tickNumber: axis.data!.length, + colorScale: + axis.colorMap && + (axis.colorMap.type === 'ordinal' + ? getOrdinalColorScale({ values: axis.data, ...axis.colorMap }) + : getColorScale(axis.colorMap)), }; } if (axis.scaleType === 'band' || axis.scaleType === 'point') { @@ -290,6 +312,7 @@ function CartesianContextProvider(props: CartesianContextProviderProps) { scaleType, scale: niceScale.domain(domain), tickNumber, + colorScale: axis.colorMap && getColorScale(axis.colorMap), } as AxisDefaultized; }); diff --git a/packages/x-charts/src/hooks/index.ts b/packages/x-charts/src/hooks/index.ts index 36309a114c3b..c052b357e379 100644 --- a/packages/x-charts/src/hooks/index.ts +++ b/packages/x-charts/src/hooks/index.ts @@ -1,2 +1,11 @@ export * from './useDrawingArea'; +export * from './useChartId'; export * from './useScale'; +export * from './useSvgRef'; +export { + useSeries as unstable_useSeries, + usePieSeries as unstable_usePieSeries, + useLineSeries as unstable_useLineSeries, + useBarSeries as unstable_useBarSeries, + useScatterSeries as unstable_useScatterSeries, +} from './useSeries'; diff --git a/packages/x-charts/src/hooks/useAxisEvents.ts b/packages/x-charts/src/hooks/useAxisEvents.ts index d31dc834ec7f..5c9a6fe4c94c 100644 --- a/packages/x-charts/src/hooks/useAxisEvents.ts +++ b/packages/x-charts/src/hooks/useAxisEvents.ts @@ -1,17 +1,18 @@ import * as React from 'react'; import { InteractionContext } from '../context/InteractionProvider'; import { CartesianContext } from '../context/CartesianContextProvider'; -import { SvgContext, DrawingContext } from '../context/DrawingProvider'; import { isBandScale } from '../internals/isBandScale'; import { AxisDefaultized } from '../models/axis'; import { getSVGPoint } from '../internals/utils'; +import { useSvgRef } from './useSvgRef'; +import { useDrawingArea } from './useDrawingArea'; function getAsANumber(value: number | Date) { return value instanceof Date ? value.getTime() : value; } export const useAxisEvents = (disableAxisListener: boolean) => { - const svgRef = React.useContext(SvgContext); - const { width, height, top, left } = React.useContext(DrawingContext); + const svgRef = useSvgRef(); + const { left, top, width, height } = useDrawingArea(); const { xAxis, yAxis, xAxisIds, yAxisIds } = React.useContext(CartesianContext); const { dispatch } = React.useContext(InteractionContext); diff --git a/packages/x-charts/src/hooks/useChartId.ts b/packages/x-charts/src/hooks/useChartId.ts new file mode 100644 index 000000000000..0107a8e8df58 --- /dev/null +++ b/packages/x-charts/src/hooks/useChartId.ts @@ -0,0 +1,8 @@ +import * as React from 'react'; +import { DrawingContext } from '../context/DrawingProvider'; + +export function useChartId() { + const { chartId } = React.useContext(DrawingContext); + + return React.useMemo(() => chartId, [chartId]); +} diff --git a/packages/x-charts/src/hooks/useSeries.ts b/packages/x-charts/src/hooks/useSeries.ts new file mode 100644 index 000000000000..12817cb944eb --- /dev/null +++ b/packages/x-charts/src/hooks/useSeries.ts @@ -0,0 +1,75 @@ +import * as React from 'react'; +import { SeriesContext } from '../context/SeriesContextProvider'; + +/** + * Get access to the internal state of series. + * Structured by type of series: + * { seriesType?: { series: { id1: precessedValue, ... }, seriesOrder: [id1, ...] } } + * @returns FormattedSeries series + */ +export function useSeries() { + const series = React.useContext(SeriesContext); + + if (series === undefined) { + throw new Error( + [ + 'MUI X: Could not find the series ref context.', + 'It looks like you rendered your component outside of a ChartsContainer parent component.', + ].join('\n'), + ); + } + + return series; +} + +/** + * Get access to the internal state of pie series. + * The returned object contains: + * - series: a mapping from ids to series attributes. + * - seriesOrder: the array of series ids. + * @returns { series: Record; seriesOrder: SeriesId[]; } | undefined pieSeries + */ +export function usePieSeries() { + const series = useSeries(); + + return React.useMemo(() => series.pie, [series.pie]); +} + +/** + * Get access to the internal state of line series. + * The returned object contains: + * - series: a mapping from ids to series attributes. + * - seriesOrder: the array of series ids. + * @returns { series: Record; seriesOrder: SeriesId[]; } | undefined lineSeries + */ +export function useLineSeries() { + const series = useSeries(); + + return React.useMemo(() => series.line, [series.line]); +} + +/** + * Get access to the internal state of bar series. + * The returned object contains: + * - series: a mapping from ids to series attributes. + * - seriesOrder: the array of series ids. + * @returns { series: Record; seriesOrder: SeriesId[]; } | undefined barSeries + */ +export function useBarSeries() { + const series = useSeries(); + + return React.useMemo(() => series.bar, [series.bar]); +} + +/** + * Get access to the internal state of scatter series. + * The returned object contains: + * - series: a mapping from ids to series attributes. + * - seriesOrder: the array of series ids. + * @returns { series: Record; seriesOrder: SeriesId[]; } | undefined scatterSeries + */ +export function useScatterSeries() { + const series = useSeries(); + + return React.useMemo(() => series.scatter, [series.scatter]); +} diff --git a/packages/x-charts/src/hooks/useSvgRef.ts b/packages/x-charts/src/hooks/useSvgRef.ts new file mode 100644 index 000000000000..268222985047 --- /dev/null +++ b/packages/x-charts/src/hooks/useSvgRef.ts @@ -0,0 +1,17 @@ +import * as React from 'react'; +import { SvgContext } from '../context/DrawingProvider'; + +export function useSvgRef(): React.MutableRefObject { + const svgRef = React.useContext(SvgContext); + + if (svgRef === undefined) { + throw new Error( + [ + 'MUI X: Could not find the svg ref context.', + 'It looks like you rendered your component outside of a ChartsContainer parent component.', + ].join('\n'), + ); + } + + return svgRef as React.MutableRefObject; +} diff --git a/packages/x-charts/src/hooks/useTicks.ts b/packages/x-charts/src/hooks/useTicks.ts index 25d4a808ae7a..fe8f84f3f39e 100644 --- a/packages/x-charts/src/hooks/useTicks.ts +++ b/packages/x-charts/src/hooks/useTicks.ts @@ -16,7 +16,7 @@ export interface TickParams { */ tickMinStep?: number; /** - * The number of ticks. This number is not guaranted. + * The number of ticks. This number is not guaranteed. * Not supported by categorical axis (band, points). */ tickNumber?: number; diff --git a/packages/x-charts/src/internals/colorGetter.ts b/packages/x-charts/src/internals/colorGetter.ts new file mode 100644 index 000000000000..27aa84b3c1a1 --- /dev/null +++ b/packages/x-charts/src/internals/colorGetter.ts @@ -0,0 +1,54 @@ +import getBarColor from '../BarChart/getColor'; +import getLineColor from '../LineChart/getColor'; +import getScatterColor from '../ScatterChart/getColor'; +import getPieColor from '../PieChart/getColor'; +import { + DefaultizedBarSeriesType, + DefaultizedLineSeriesType, + DefaultizedPieSeriesType, + DefaultizedScatterSeriesType, +} from '../models'; +import { AxisDefaultized } from '../models/axis'; + +function getColor(series: DefaultizedPieSeriesType): (dataIndex: number) => string; +function getColor( + series: + | DefaultizedBarSeriesType + | DefaultizedLineSeriesType + | DefaultizedScatterSeriesType + | DefaultizedPieSeriesType, + xAxis: AxisDefaultized, + yAxis: AxisDefaultized, +): (dataIndex: number) => string; +function getColor( + series: + | DefaultizedBarSeriesType + | DefaultizedLineSeriesType + | DefaultizedScatterSeriesType + | DefaultizedPieSeriesType, + xAxis?: AxisDefaultized, + yAxis?: AxisDefaultized, +): (dataIndex: number) => string { + if (xAxis !== undefined && yAxis !== undefined) { + if (series.type === 'bar') { + return getBarColor(series, xAxis, yAxis); + } + + if (series.type === 'line') { + return getLineColor(series, xAxis, yAxis); + } + + if (series.type === 'scatter') { + return getScatterColor(series, xAxis, yAxis); + } + } + if (series.type === 'pie') { + return getPieColor(series); + } + + throw Error( + `MUI X Charts: getColor called with unexpected arguments for series with id "${series.id}"`, + ); +} + +export default getColor; diff --git a/packages/x-charts/src/internals/colorScale.ts b/packages/x-charts/src/internals/colorScale.ts new file mode 100644 index 000000000000..a0c2807e9dfd --- /dev/null +++ b/packages/x-charts/src/internals/colorScale.ts @@ -0,0 +1,34 @@ +import { scaleOrdinal, scaleThreshold, scaleSequential, ScaleOrdinal } from 'd3-scale'; +import { + ContinuousColorConfig, + PiecewiseColorConfig, + OrdinalColorConfig, +} from '../models/colorMapping'; + +export function getSequentialColorScale( + config: ContinuousColorConfig | PiecewiseColorConfig, +) { + if (config.type === 'piecewise') { + return scaleThreshold(config.thresholds, config.colors); + } + + return scaleSequential([config.min ?? 0, config.max ?? 100], config.color); +} + +export function getOrdinalColorScale( + config: OrdinalColorConfig, +): ScaleOrdinal | ScaleOrdinal { + if (config.values) { + return scaleOrdinal(config.values, config.colors).unknown(config.unknownColor ?? null); + } + return scaleOrdinal( + config.colors.map((_, index) => index), + config.colors, + ).unknown(config.unknownColor ?? null); +} + +export function getColorScale( + config: ContinuousColorConfig | PiecewiseColorConfig | OrdinalColorConfig, +) { + return config.type === 'ordinal' ? getOrdinalColorScale(config) : getSequentialColorScale(config); +} diff --git a/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsAxesGradients.tsx b/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsAxesGradients.tsx new file mode 100644 index 000000000000..ce79c1fb89b0 --- /dev/null +++ b/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsAxesGradients.tsx @@ -0,0 +1,96 @@ +import * as React from 'react'; +import { CartesianContext } from '../../../context/CartesianContextProvider'; +import { DrawingContext } from '../../../context/DrawingProvider'; +import { useDrawingArea } from '../../../hooks'; +import ChartsPiecewiseGradient from './ChartsPiecewiseGradient'; +import ChartsContinuousGradient from './ChartsContinuousGradient'; + +export function useChartGradient() { + const { chartId } = React.useContext(DrawingContext); + return React.useCallback( + (axisId: string, direction: 'x' | 'y') => `${chartId}-graient-${direction}-${axisId}`, + [chartId], + ); +} + +export function ChartsAxesGradients() { + const { top, height, bottom, left, width, right } = useDrawingArea(); + + const svgHeight = top + height + bottom; + const svgWidth = left + width + right; + const getGradientId = useChartGradient(); + const { xAxisIds, xAxis, yAxisIds, yAxis } = React.useContext(CartesianContext); + + return ( + + {yAxisIds + .filter((axisId) => yAxis[axisId].colorMap !== undefined) + .map((axisId) => { + const gradientId = getGradientId(axisId, 'y'); + const { colorMap, scale, colorScale, reverse } = yAxis[axisId]; + if (colorMap?.type === 'piecewise') { + return ( + + ); + } + if (colorMap?.type === 'continuous') { + return ( + + ); + } + return null; + })} + {xAxisIds + .filter((axisId) => xAxis[axisId].colorMap !== undefined) + .map((axisId) => { + const gradientId = getGradientId(axisId, 'x'); + const { colorMap, scale, reverse, colorScale } = xAxis[axisId]; + if (colorMap?.type === 'piecewise') { + return ( + + ); + } + if (colorMap?.type === 'continuous') { + return ( + + ); + } + return null; + })} + + ); +} diff --git a/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsContinuousGradient.tsx b/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsContinuousGradient.tsx new file mode 100644 index 000000000000..55d7e9d45657 --- /dev/null +++ b/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsContinuousGradient.tsx @@ -0,0 +1,65 @@ +import * as React from 'react'; +import { interpolateDate, interpolateNumber } from 'd3-interpolate'; +import { ContinuousColorConfig } from '../../../models/colorMapping'; + +const PX_PRECISION = 10; + +type ChartsContinuousGradientProps = { + isReveresed?: boolean; + gradientId: string; + size: number; + direction: 'x' | 'y'; + scale: (value: any) => number | undefined; + colorMap: ContinuousColorConfig; + colorScale: (value: any) => string | null; +}; + +export default function ChartsContinuousGradient(props: ChartsContinuousGradientProps) { + const { isReveresed, gradientId, size, direction, scale, colorScale, colorMap } = props; + + const extremValues = [colorMap.min ?? 0, colorMap.max ?? 100] as [number, number] | [Date, Date]; + const extremPositions = extremValues.map(scale).filter((p): p is number => p !== undefined); + + if (extremPositions.length !== 2) { + return null; + } + + const interpolator = + typeof extremValues[0] === 'number' + ? interpolateNumber(extremValues[0], extremValues[1]) + : interpolateDate(extremValues[0], extremValues[1] as Date); + const numberOfPoints = Math.round( + (Math.max(...extremPositions) - Math.min(...extremPositions)) / PX_PRECISION, + ); + + const keyPrefix = `${extremValues[0]}-${extremValues[1]}-`; + return ( + + {Array.from({ length: numberOfPoints + 1 }, (_, index) => { + const value = interpolator(index / numberOfPoints); + if (value === undefined) { + return null; + } + const x = scale(value); + if (x === undefined) { + return null; + } + const offset = isReveresed ? 1 - x / size : x / size; + const color = colorScale(value); + + if (color === null) { + return null; + } + return ; + })} + + ); +} diff --git a/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsPiecewiseGradient.tsx b/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsPiecewiseGradient.tsx new file mode 100644 index 000000000000..d2c27c35dc5d --- /dev/null +++ b/packages/x-charts/src/internals/components/ChartsAxesGradients/ChartsPiecewiseGradient.tsx @@ -0,0 +1,43 @@ +import * as React from 'react'; +import { PiecewiseColorConfig } from '../../../models/colorMapping'; + +type ChartsPiecewiseGradientProps = { + isReveresed?: boolean; + gradientId: string; + size: number; + direction: 'x' | 'y'; + scale: (value: any) => number | undefined; + colorMap: PiecewiseColorConfig; +}; + +export default function ChartsPiecewiseGradient(props: ChartsPiecewiseGradientProps) { + const { isReveresed, gradientId, size, direction, scale, colorMap } = props; + + return ( + + {colorMap.thresholds.map((threshold, index) => { + const x = scale(threshold); + + if (x === undefined) { + return null; + } + const offset = isReveresed ? 1 - x / size : x / size; + + return ( + + + + + ); + })} + + ); +} diff --git a/packages/x-charts/src/internals/components/ChartsAxesGradients/index.tsx b/packages/x-charts/src/internals/components/ChartsAxesGradients/index.tsx new file mode 100644 index 000000000000..4ba9bf5f96ca --- /dev/null +++ b/packages/x-charts/src/internals/components/ChartsAxesGradients/index.tsx @@ -0,0 +1 @@ +export * from './ChartsAxesGradients'; diff --git a/packages/x-charts/src/internals/defaultizeValueFormatter.ts b/packages/x-charts/src/internals/defaultizeValueFormatter.ts index b3ce7c4e7196..b9fa91ba8810 100644 --- a/packages/x-charts/src/internals/defaultizeValueFormatter.ts +++ b/packages/x-charts/src/internals/defaultizeValueFormatter.ts @@ -1,13 +1,16 @@ -import { SeriesId } from '../models/seriesType/common'; +import { SeriesId, SeriesValueFormatter } from '../models/seriesType/common'; function defaultizeValueFormatter< - ISeries extends { valueFormatter?: IFormatter }, - IFormatter extends (v: any) => string, + TValue, + ISeries extends { valueFormatter?: SeriesValueFormatter }, >( series: Record, - defaultValueFormatter: IFormatter, -): Record { - const defaultizedSeries: Record = {}; + defaultValueFormatter: SeriesValueFormatter, +): Record }> { + const defaultizedSeries: Record< + SeriesId, + ISeries & { valueFormatter: SeriesValueFormatter } + > = {}; Object.keys(series).forEach((seriesId) => { defaultizedSeries[seriesId] = { ...series[seriesId], diff --git a/packages/x-charts/src/internals/getScale.ts b/packages/x-charts/src/internals/getScale.ts index 16f9073e0233..68ab1d5e4fbc 100644 --- a/packages/x-charts/src/internals/getScale.ts +++ b/packages/x-charts/src/internals/getScale.ts @@ -1,11 +1,11 @@ import { scaleLog, scalePow, scaleSqrt, scaleTime, scaleUtc, scaleLinear } from 'd3-scale'; -import { ContinuouseScaleName, D3ContinuouseScale } from '../models/axis'; +import { ContinuousScaleName, D3ContinuousScale } from '../models/axis'; export function getScale( - scaleType: ContinuouseScaleName, + scaleType: ContinuousScaleName, domain: any[], range: any[], -): D3ContinuouseScale { +): D3ContinuousScale { switch (scaleType) { case 'log': return scaleLog(domain, range); diff --git a/packages/x-charts/src/models/axis.ts b/packages/x-charts/src/models/axis.ts index 060ff07b87f1..fe9bba7b5109 100644 --- a/packages/x-charts/src/models/axis.ts +++ b/packages/x-charts/src/models/axis.ts @@ -5,10 +5,14 @@ import type { ScaleTime, ScaleLinear, ScalePoint, + ScaleOrdinal, + ScaleSequential, + ScaleThreshold, } from 'd3-scale'; import { ChartsAxisClasses } from '../ChartsAxis/axisClasses'; import type { TickParams } from '../hooks/useTicks'; import { ChartsTextProps } from '../ChartsText'; +import { ContinuousColorConfig, OrdinalColorConfig, PiecewiseColorConfig } from './colorMapping'; export type AxisId = string | number; @@ -24,7 +28,7 @@ export type D3Scale< | ScaleTime | ScaleLinear; -export type D3ContinuouseScale = +export type D3ContinuousScale = | ScaleLogarithmic | ScalePower | ScaleTime @@ -153,7 +157,7 @@ export interface ChartsXAxisProps extends ChartsAxisProps { } export type ScaleName = 'linear' | 'band' | 'point' | 'log' | 'pow' | 'sqrt' | 'time' | 'utc'; -export type ContinuouseScaleName = 'linear' | 'log' | 'pow' | 'sqrt' | 'time' | 'utc'; +export type ContinuousScaleName = 'linear' | 'log' | 'pow' | 'sqrt' | 'time' | 'utc'; interface AxisScaleConfig { band: { @@ -171,38 +175,89 @@ interface AxisScaleConfig { * @default 0.1 */ barGapRatio: number; + colorMap?: OrdinalColorConfig | ContinuousColorConfig | PiecewiseColorConfig; } & Pick; point: { scaleType: 'point'; scale: ScalePoint; + colorMap?: OrdinalColorConfig | ContinuousColorConfig | PiecewiseColorConfig; }; log: { scaleType: 'log'; scale: ScaleLogarithmic; + colorMap?: ContinuousColorConfig | PiecewiseColorConfig; }; pow: { scaleType: 'pow'; scale: ScalePower; + colorMap?: ContinuousColorConfig | PiecewiseColorConfig; }; sqrt: { scaleType: 'sqrt'; scale: ScalePower; + colorMap?: ContinuousColorConfig | PiecewiseColorConfig; }; time: { scaleType: 'time'; scale: ScaleTime; + colorMap?: ContinuousColorConfig | PiecewiseColorConfig; }; utc: { scaleType: 'utc'; scale: ScaleTime; + colorMap?: ContinuousColorConfig | PiecewiseColorConfig; }; linear: { scaleType: 'linear'; scale: ScaleLinear; + colorMap?: ContinuousColorConfig | PiecewiseColorConfig; }; } +interface AxisScaleComputedConfig { + band: { + colorScale?: + | ScaleOrdinal + | ScaleOrdinal + | ScaleSequential + | ScaleThreshold; + }; + point: { + colorScale?: + | ScaleOrdinal + | ScaleOrdinal + | ScaleSequential + | ScaleThreshold; + }; + log: { + colorScale?: ScaleSequential | ScaleThreshold; + }; + pow: { + colorScale?: ScaleSequential | ScaleThreshold; + }; + sqrt: { + colorScale?: ScaleSequential | ScaleThreshold; + }; + time: { + colorScale?: + | ScaleSequential + | ScaleThreshold; + }; + utc: { + colorScale?: + | ScaleSequential + | ScaleThreshold; + }; + linear: { + colorScale?: ScaleSequential | ScaleThreshold; + }; +} export type AxisValueFormatterContext = { + /** + * Location indicates where the value will be displayed. + * - `'tick'` The value is displayed on the axis ticks. + * - `'tooltip'` The value is displayed in the tooltip when hovering the chart. + */ location: 'tick' | 'tooltip'; }; @@ -252,7 +307,8 @@ export type AxisDefaultized = Omit< AxisConfig, 'scaleType' > & - AxisScaleConfig[S] & { + AxisScaleConfig[S] & + AxisScaleComputedConfig[S] & { /** * An indication of the expected number of ticks. */ diff --git a/packages/x-charts/src/models/colorMapping.ts b/packages/x-charts/src/models/colorMapping.ts new file mode 100644 index 000000000000..045de15faa73 --- /dev/null +++ b/packages/x-charts/src/models/colorMapping.ts @@ -0,0 +1,47 @@ +export interface ContinuousColorConfig { + type: 'continuous'; + /** + * The minimal value of the color scale. + * @default 0 + */ + min?: Value; + /** + * The maximal value of the color scale. + * @default 100 + */ + max?: Value; + /** + * The colors to render. Can either be and array with the extrem colors, or an interpolation function. + */ + color: [string, string] | ((t: number) => string); +} + +export interface PiecewiseColorConfig { + type: 'piecewise'; + /** + * The thresholds where color should change from one category to another. + */ + thresholds: Value[]; + /** + * The colors used for each band defined by `thresholds`. + * Should contain N+1 colors with N the number of thresholds. + */ + colors: string[]; +} + +export interface OrdinalColorConfig { + type: 'ordinal'; + /** + * The value to map. + * If undefined, it will be integers from 0 to the number of colors. + */ + values?: Value[]; + /** + * The color palette. + */ + colors: string[]; + /** + * The color to use when an element is not part of the values. + */ + unknownColor?: string; +} diff --git a/packages/x-charts/src/models/index.ts b/packages/x-charts/src/models/index.ts index 9dd7d43710d0..abb5d36002f6 100644 --- a/packages/x-charts/src/models/index.ts +++ b/packages/x-charts/src/models/index.ts @@ -6,5 +6,5 @@ export type { ChartsYAxisProps, ChartsXAxisProps, ScaleName, - ContinuouseScaleName, + ContinuousScaleName, } from './axis'; diff --git a/packages/x-charts/src/models/seriesType/common.ts b/packages/x-charts/src/models/seriesType/common.ts index 97a51b204792..830d3e7add0c 100644 --- a/packages/x-charts/src/models/seriesType/common.ts +++ b/packages/x-charts/src/models/seriesType/common.ts @@ -3,15 +3,28 @@ import type { StackOffsetType, StackOrderType } from '../stacking'; export type SeriesId = number | string; +export type SeriesValueFormatterContext = { + /** + * The index of the value in the data array. + */ + dataIndex: number; +}; + +export type SeriesValueFormatter = ( + value: TValue, + context: SeriesValueFormatterContext, +) => string; + export type CommonSeriesType = { id?: SeriesId; color?: string; /** * Formatter used to render values in tooltip or other data display. * @param {TValue} value The series' value to render. - * @returns {string} The string to dispaly. + * @param {SeriesValueFormatterContext} context The rendering context of the value. + * @returns {string} The string to display. */ - valueFormatter?: (value: V) => string; + valueFormatter?: SeriesValueFormatter; highlightScope?: Partial; }; diff --git a/packages/x-charts/src/models/seriesType/index.ts b/packages/x-charts/src/models/seriesType/index.ts index fc960e50f729..c6c848eb9165 100644 --- a/packages/x-charts/src/models/seriesType/index.ts +++ b/packages/x-charts/src/models/seriesType/index.ts @@ -38,3 +38,13 @@ export type { DefaultizedCartesianSeriesType, StackableSeriesType, }; + +export function isDefaultizedBarSeries( + series: DefaultizedSeriesType, +): series is DefaultizedBarSeriesType { + return series.type === 'bar'; +} + +export function isBarSeries(series: AllSeriesType): series is BarSeriesType { + return series.type === 'bar'; +} diff --git a/packages/x-data-grid-generator/package.json b/packages/x-data-grid-generator/package.json index 3c8e2381d077..085a21ed33c3 100644 --- a/packages/x-data-grid-generator/package.json +++ b/packages/x-data-grid-generator/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-data-grid-generator", - "version": "7.1.1", + "version": "7.2.0", "description": "Generate fake data for demo purposes only.", "author": "MUI Team", "main": "src/index.ts", @@ -34,7 +34,7 @@ "dependencies": { "@babel/runtime": "^7.24.0", "@mui/base": "^5.0.0-beta.40", - "@mui/x-data-grid-premium": "7.1.1", + "@mui/x-data-grid-premium": "7.2.0", "chance": "^1.1.11", "clsx": "^2.1.0", "lru-cache": "^7.18.3" diff --git a/packages/x-data-grid-premium/package.json b/packages/x-data-grid-premium/package.json index c2eebed31acd..81ca5e76cd2d 100644 --- a/packages/x-data-grid-premium/package.json +++ b/packages/x-data-grid-premium/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-data-grid-premium", - "version": "7.1.1", + "version": "7.2.0", "description": "The Premium plan edition of the Data Grid Components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -45,9 +45,9 @@ "@babel/runtime": "^7.24.0", "@mui/system": "^5.15.14", "@mui/utils": "^5.15.14", - "@mui/x-data-grid": "7.1.1", - "@mui/x-data-grid-pro": "7.1.1", - "@mui/x-license": "7.1.1", + "@mui/x-data-grid": "7.2.0", + "@mui/x-data-grid-pro": "7.2.0", + "@mui/x-license": "7.2.0", "@types/format-util": "^1.0.4", "clsx": "^2.1.0", "exceljs": "^4.4.0", diff --git a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx index 1f3caece0f9a..ed9e568c6f46 100644 --- a/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx +++ b/packages/x-data-grid-premium/src/DataGridPremium/DataGridPremium.tsx @@ -422,6 +422,10 @@ DataGridPremiumRaw.propTypes = { * The grouping column used by the tree data. */ groupingColDef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + /** + * Override the height of the header filters. + */ + headerFilterHeight: PropTypes.number, /** * If `true`, enables the data grid filtering on header feature. * @default false diff --git a/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts b/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts index ad700ae7eca1..e83cd6834b8b 100644 --- a/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts +++ b/packages/x-data-grid-premium/src/hooks/features/cellSelection/useGridCellSelection.ts @@ -68,7 +68,7 @@ export const useGridCellSelection = ( const autoScrollRAF = React.useRef(); const sortedRowIds = useGridSelector(apiRef, gridSortedRowIdsSelector); const dimensions = useGridSelector(apiRef, gridDimensionsSelector); - const totalHeaderHeight = getTotalHeaderHeight(apiRef, props.columnHeaderHeight); + const totalHeaderHeight = getTotalHeaderHeight(apiRef, props); const ignoreValueFormatterProp = props.ignoreValueFormatterDuringExport; const ignoreValueFormatter = diff --git a/packages/x-data-grid-pro/package.json b/packages/x-data-grid-pro/package.json index a5692779a6b5..5abf622bcac1 100644 --- a/packages/x-data-grid-pro/package.json +++ b/packages/x-data-grid-pro/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-data-grid-pro", - "version": "7.1.1", + "version": "7.2.0", "description": "The Pro plan edition of the Data Grid components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -45,8 +45,8 @@ "@babel/runtime": "^7.24.0", "@mui/system": "^5.15.14", "@mui/utils": "^5.15.14", - "@mui/x-data-grid": "7.1.1", - "@mui/x-license": "7.1.1", + "@mui/x-data-grid": "7.2.0", + "@mui/x-license": "7.2.0", "@types/format-util": "^1.0.4", "clsx": "^2.1.0", "prop-types": "^15.8.1", diff --git a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx index 1b9dce1dbcf4..272316b357e7 100644 --- a/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx +++ b/packages/x-data-grid-pro/src/DataGridPro/DataGridPro.tsx @@ -363,6 +363,10 @@ DataGridProRaw.propTypes = { * The grouping column used by the tree data. */ groupingColDef: PropTypes.oneOfType([PropTypes.func, PropTypes.object]), + /** + * Override the height of the header filters. + */ + headerFilterHeight: PropTypes.number, /** * If `true`, enables the data grid filtering on header feature. * @default false diff --git a/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx b/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx index 68833b01e116..3f2b92db7a10 100644 --- a/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx +++ b/packages/x-data-grid-pro/src/components/headerFiltering/GridHeaderFilterCell.tsx @@ -53,6 +53,7 @@ export interface GridHeaderFilterCellProps extends Pick { const rootProps = useGridRootProps(); const classes = useUtilityClasses(rootProps); const disableHeaderFiltering = !rootProps.headerFilters; - const dimensions = apiRef.current.getRootDimensions(); + const dimensions = useGridSelector(apiRef, gridDimensionsSelector); const filterModel = useGridSelector(apiRef, gridFilterModelSelector); + const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width; const columnHeaderFilterFocus = useGridSelector(apiRef, gridFocusColumnHeaderFilterSelector); @@ -125,7 +127,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { { style={style} indexInSection={i} sectionLength={renderedColumns.length} + gridHasFiller={gridHasFiller} {...rootProps.slotProps?.headerFilterCell} />, ); diff --git a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanelCache.ts b/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanelCache.ts deleted file mode 100644 index e628d22aa460..000000000000 --- a/packages/x-data-grid-pro/src/hooks/features/detailPanel/useGridDetailPanelCache.ts +++ /dev/null @@ -1,79 +0,0 @@ -import * as React from 'react'; -import { useGridApiEventHandler, gridDataRowIdsSelector, GridRowId } from '@mui/x-data-grid'; -import { GridApiPro } from '../../../models/gridApiPro'; -import { DataGridProProcessedProps } from '../../../models/dataGridProProps'; -import { GridDetailPanelState } from './gridDetailPanelInterface'; - -function cacheContentAndHeight( - apiRef: React.MutableRefObject, - getDetailPanelContent: DataGridProProcessedProps['getDetailPanelContent'], - getDetailPanelHeight: DataGridProProcessedProps['getDetailPanelHeight'], - previousHeightCache: GridDetailPanelState['heightCache'], -) { - if (typeof getDetailPanelContent !== 'function') { - return {}; - } - - // TODO change to lazy approach using a Proxy - // only call getDetailPanelContent when asked for an id - const rowIds = gridDataRowIdsSelector(apiRef); - const contentCache = rowIds.reduce>>( - (acc, id) => { - const params = apiRef.current.getRowParams(id); - acc[id] = getDetailPanelContent(params); - return acc; - }, - {}, - ); - - const heightCache = rowIds.reduce((acc, id) => { - if (contentCache[id] == null) { - return acc; - } - const params = apiRef.current.getRowParams(id); - const height = getDetailPanelHeight(params); - const autoHeight = height === 'auto'; - acc[id] = { autoHeight, height: !autoHeight ? height : previousHeightCache[id]?.height }; - return acc; - }, {}); - - return { contentCache, heightCache }; -} - -export const useGridDetailPanelCache = ( - apiRef: React.MutableRefObject, - props: Pick, -) => { - const updateCaches = React.useCallback(() => { - apiRef.current.setState((state) => { - return { - ...state, - detailPanel: { - ...state.detailPanel, - ...cacheContentAndHeight( - apiRef, - props.getDetailPanelContent, - props.getDetailPanelHeight, - state.detailPanel.heightCache, - ), - }, - }; - }); - apiRef.current.forceUpdate(); - }, [apiRef, props.getDetailPanelContent, props.getDetailPanelHeight]); - - useGridApiEventHandler(apiRef, 'sortedRowsSet', updateCaches); - - const isFirstRender = React.useRef(true); - if (isFirstRender.current) { - isFirstRender.current = false; - updateCaches(); - } - - React.useEffect(() => { - if (isFirstRender.current) { - return; - } - updateCaches(); - }, [updateCaches]); -}; diff --git a/packages/x-data-grid-pro/src/models/dataGridProProps.ts b/packages/x-data-grid-pro/src/models/dataGridProProps.ts index b7a7430e4b3d..9ffc76113476 100644 --- a/packages/x-data-grid-pro/src/models/dataGridProProps.ts +++ b/packages/x-data-grid-pro/src/models/dataGridProProps.ts @@ -16,6 +16,7 @@ import { DataGridPropsWithComplexDefaultValueBeforeProcessing, GridPinnedColumnFields, DataGridProSharedPropsWithDefaultValue, + DataGridProSharedPropsWithoutDefaultValue, } from '@mui/x-data-grid/internals'; import type { GridPinnedRowsProp } from '../hooks/features/rowPinning'; import { GridApiPro } from './gridApiPro'; @@ -138,9 +139,10 @@ export interface DataGridProPropsWithDefaultValue extends Omit< - DataGridPropsWithoutDefaultValue, - 'initialState' | 'componentsProps' | 'slotProps' - > { + DataGridPropsWithoutDefaultValue, + 'initialState' | 'componentsProps' | 'slotProps' + >, + DataGridProSharedPropsWithoutDefaultValue { /** * The ref object that allows grid manipulation. Can be instantiated with `useGridApiRef()`. */ diff --git a/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx index 6c5ccec2e339..0e495c379482 100644 --- a/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/cellEditing.DataGridPro.test.tsx @@ -190,6 +190,33 @@ describe(' - Cell editing', () => { }); }); + it('should not publish onCellEditStop if field has error', async () => { + columnProps.preProcessEditCellProps = spy(({ props }: GridPreProcessEditCellProps) => ({ + ...props, + error: true, + })); + + const handleEditCellStop = spy(); + + render(); + act(() => apiRef.current.startCellEditMode({ id: 0, field: 'currencyPair' })); + await act(() => + apiRef.current.setEditCellValue({ + id: 0, + field: 'currencyPair', + value: 'USD GBP', + }), + ); + const cell = getCell(0, 1); + cell.focus(); + + await act(() => { + fireEvent.keyDown(cell, { key: 'Enter' }); + }); + + expect(handleEditCellStop.callCount).to.equal(0); + }); + it('should pass to renderEditCell the props returned by preProcessEditCellProps', async () => { columnProps.preProcessEditCellProps = ({ props }: GridPreProcessEditCellProps) => ({ ...props, diff --git a/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx index 3471efe273e6..dc631df9fe10 100644 --- a/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/layout.DataGridPro.test.tsx @@ -143,6 +143,22 @@ describe(' - Layout', () => { }); }); + it('should work with `headerFilterHeight` prop', () => { + render( +
+ +
, + ); + expect(grid('main')!.clientHeight).to.equal(baselineProps.rows.length * 20 + 20 + 32); + }); + it('should support translations in the theme', () => { render( diff --git a/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx b/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx index 091f51a8697c..85880a295f63 100644 --- a/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx +++ b/packages/x-data-grid-pro/src/tests/statePersistence.DataGridPro.test.tsx @@ -78,6 +78,7 @@ const FULL_INITIAL_STATE: GridInitialState = { sorting: { sortModel: [{ field: 'id', sort: 'desc' }], }, + density: 'compact', }; describe(' - State persistence', () => { @@ -135,6 +136,7 @@ describe(' - State persistence', () => { sorting: { sortModel: [], }, + density: 'standard', }); }); @@ -165,6 +167,7 @@ describe(' - State persistence', () => { paginationMode="server" rowCount={FULL_INITIAL_STATE.pagination?.rowCount} pinnedColumns={FULL_INITIAL_STATE.pinnedColumns} + density={FULL_INITIAL_STATE.density} // Some portable states don't have a controllable model initialState={{ columns: { @@ -191,6 +194,7 @@ describe(' - State persistence', () => { paginationMode="server" rowCount={FULL_INITIAL_STATE.pagination?.rowCount} pinnedColumns={FULL_INITIAL_STATE.pinnedColumns} + density={FULL_INITIAL_STATE.density} // Some portable states don't have a controllable model initialState={{ columns: { @@ -226,6 +230,7 @@ describe(' - State persistence', () => { apiRef.current.setColumnIndex('category', 1); apiRef.current.setColumnWidth('category', 75); apiRef.current.setColumnVisibilityModel({ idBis: false }); + apiRef.current.setDensity('compact'); }); expect(apiRef.current.exportState()).to.deep.equal(FULL_INITIAL_STATE); }); diff --git a/packages/x-data-grid/package.json b/packages/x-data-grid/package.json index acd366c96f3b..d8f9cb3c6d59 100644 --- a/packages/x-data-grid/package.json +++ b/packages/x-data-grid/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-data-grid", - "version": "7.1.1", + "version": "7.2.0", "description": "The Community plan edition of the Data Grid components (MUI X).", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-data-grid/src/components/GridRow.tsx b/packages/x-data-grid/src/components/GridRow.tsx index 6763b3c41f13..5e9854587a4e 100644 --- a/packages/x-data-grid/src/components/GridRow.tsx +++ b/packages/x-data-grid/src/components/GridRow.tsx @@ -151,6 +151,7 @@ const GridRow = React.forwardRef(function GridRow( const handleRef = useForkRef(ref, refProp); const rowNode = apiRef.current.getRowNode(rowId); const scrollbarWidth = dimensions.hasScrollY ? dimensions.scrollbarSize : 0; + const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width; const hasFocusCell = focusedColumnIndex !== undefined; const hasVirtualFocusCellLeft = @@ -420,6 +421,7 @@ const GridRow = React.forwardRef(function GridRow( pinnedPosition={pinnedPosition} sectionIndex={indexInSection} sectionLength={sectionLength} + gridHasFiller={gridHasFiller} {...slotProps?.cell} /> ); @@ -543,6 +545,7 @@ GridRow.propTypes = { }).isRequired, hasScrollX: PropTypes.bool.isRequired, hasScrollY: PropTypes.bool.isRequired, + headerFilterHeight: PropTypes.number.isRequired, headerHeight: PropTypes.number.isRequired, headersTotalHeight: PropTypes.number.isRequired, isReady: PropTypes.bool.isRequired, diff --git a/packages/x-data-grid/src/components/GridScrollArea.tsx b/packages/x-data-grid/src/components/GridScrollArea.tsx index 8abd78dfb49d..59752fd21305 100644 --- a/packages/x-data-grid/src/components/GridScrollArea.tsx +++ b/packages/x-data-grid/src/components/GridScrollArea.tsx @@ -97,7 +97,7 @@ function GridScrollAreaRaw(props: ScrollAreaProps) { const rootProps = useGridRootProps(); const ownerState = { ...rootProps, scrollDirection }; const classes = useUtilityClasses(ownerState); - const totalHeaderHeight = getTotalHeaderHeight(apiRef, rootProps.columnHeaderHeight); + const totalHeaderHeight = getTotalHeaderHeight(apiRef, rootProps); const headerHeight = Math.floor(rootProps.columnHeaderHeight * densityFactor); const style: React.CSSProperties = { diff --git a/packages/x-data-grid/src/components/cell/GridCell.tsx b/packages/x-data-grid/src/components/cell/GridCell.tsx index a62c1aa9ace2..0a84b3eb627e 100644 --- a/packages/x-data-grid/src/components/cell/GridCell.tsx +++ b/packages/x-data-grid/src/components/cell/GridCell.tsx @@ -31,10 +31,7 @@ import { useGridApiContext } from '../../hooks/utils/useGridApiContext'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { gridFocusCellSelector } from '../../hooks/features/focus/gridFocusStateSelector'; import { MissingRowIdError } from '../../hooks/features/rows/useGridParamsApi'; -import type { - DataGridProcessedProps, - DataGridProcessedPropsWithShared, -} from '../../models/props/DataGridProps'; +import type { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { shouldCellShowLeftBorder, shouldCellShowRightBorder } from '../../utils/cellBorderUtils'; import { GridPinnedColumnPosition } from '../../hooks/features/columns/gridColumnsInterfaces'; @@ -67,6 +64,7 @@ export type GridCellProps = { pinnedPosition: PinnedPosition; sectionIndex: number; sectionLength: number; + gridHasFiller: boolean; onClick?: React.MouseEventHandler; onDoubleClick?: React.MouseEventHandler; onMouseDown?: React.MouseEventHandler; @@ -167,6 +165,7 @@ const GridCell = React.forwardRef((props, ref) => pinnedPosition, sectionIndex, sectionLength, + gridHasFiller, onClick, onDoubleClick, onMouseDown, @@ -180,7 +179,7 @@ const GridCell = React.forwardRef((props, ref) => } = props; const apiRef = useGridApiContext(); - const rootProps = useGridRootProps() as DataGridProcessedPropsWithShared; + const rootProps = useGridRootProps(); const field = column.field; @@ -270,6 +269,7 @@ const GridCell = React.forwardRef((props, ref) => sectionIndex, sectionLength, rootProps.showCellVerticalBorder, + gridHasFiller, ); const ownerState = { @@ -493,6 +493,7 @@ GridCell.propTypes = { isValidating: PropTypes.bool, value: PropTypes.any, }), + gridHasFiller: PropTypes.bool.isRequired, isNotVisible: PropTypes.bool.isRequired, onClick: PropTypes.func, onDoubleClick: PropTypes.func, diff --git a/packages/x-data-grid/src/components/columnHeaders/GridColumnGroupHeader.tsx b/packages/x-data-grid/src/components/columnHeaders/GridColumnGroupHeader.tsx index cc4ba677c225..f7329b9380f9 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridColumnGroupHeader.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridColumnGroupHeader.tsx @@ -30,6 +30,7 @@ interface GridColumnGroupHeaderProps { style?: React.CSSProperties; indexInSection: number; sectionLength: number; + gridHasFiller: boolean; } type OwnerState = { @@ -91,6 +92,7 @@ function GridColumnGroupHeader(props: GridColumnGroupHeaderProps) { style, indexInSection, sectionLength, + gridHasFiller, } = props; const rootProps = useGridRootProps(); @@ -129,6 +131,7 @@ function GridColumnGroupHeader(props: GridColumnGroupHeaderProps) { indexInSection, sectionLength, rootProps.showCellVerticalBorder, + gridHasFiller, ); const ownerState = { diff --git a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx index 671f546d6017..e11e6b273345 100644 --- a/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx +++ b/packages/x-data-grid/src/components/columnHeaders/GridColumnHeaderItem.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; import { unstable_composeClasses as composeClasses, unstable_useId as useId } from '@mui/utils'; import { fastMemo } from '../../utils/fastMemo'; import { GridStateColDef } from '../../models/colDef/gridColDef'; @@ -9,7 +10,7 @@ import { GridColumnHeaderSortIcon } from './GridColumnHeaderSortIcon'; import { GridColumnHeaderSeparatorProps } from './GridColumnHeaderSeparator'; import { ColumnHeaderMenuIcon } from './ColumnHeaderMenuIcon'; import { GridColumnHeaderMenu } from '../menu/columnMenu/GridColumnHeaderMenu'; -import { getDataGridUtilityClass } from '../../constants/gridClasses'; +import { gridClasses, getDataGridUtilityClass } from '../../constants/gridClasses'; import { useGridRootProps } from '../../hooks/utils/useGridRootProps'; import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import { GridGenericColumnHeaderItem } from './GridGenericColumnHeaderItem'; @@ -25,6 +26,7 @@ interface GridColumnHeaderItemProps { headerHeight: number; isDragging: boolean; isResizing: boolean; + isLast: boolean; sortDirection: GridSortDirection; sortIndex?: number; filterItemsCounter?: number; @@ -36,6 +38,7 @@ interface GridColumnHeaderItemProps { style?: React.CSSProperties; indexInSection: number; sectionLength: number; + gridHasFiller: boolean; } type OwnerState = GridColumnHeaderItemProps & { @@ -93,6 +96,7 @@ function GridColumnHeaderItem(props: GridColumnHeaderItemProps) { colIndex, headerHeight, isResizing, + isLast, sortDirection, sortIndex, filterItemsCounter, @@ -104,6 +108,7 @@ function GridColumnHeaderItem(props: GridColumnHeaderItemProps) { pinnedPosition, indexInSection, sectionLength, + gridHasFiller, } = props; const apiRef = useGridPrivateApiContext(); const rootProps = useGridRootProps(); @@ -129,6 +134,7 @@ function GridColumnHeaderItem(props: GridColumnHeaderItemProps) { indexInSection, sectionLength, rootProps.showCellVerticalBorder, + gridHasFiller, ); const ownerState = { @@ -290,7 +296,7 @@ function GridColumnHeaderItem(props: GridColumnHeaderItemProps) { width={colDef.computedWidth} columnMenuIconButton={columnMenuIconButton} columnTitleIconButtons={columnTitleIconButtons} - headerClassName={headerClassName} + headerClassName={clsx(headerClassName, isLast && gridClasses['columnHeader--last'])} label={label} resizable={!rootProps.disableColumnResize && !!colDef.resizable} data-field={colDef.field} @@ -313,10 +319,12 @@ GridColumnHeaderItem.propTypes = { columnMenuOpen: PropTypes.bool.isRequired, disableReorder: PropTypes.bool, filterItemsCounter: PropTypes.number, + gridHasFiller: PropTypes.bool.isRequired, hasFocus: PropTypes.bool, headerHeight: PropTypes.number.isRequired, indexInSection: PropTypes.number.isRequired, isDragging: PropTypes.bool.isRequired, + isLast: PropTypes.bool.isRequired, isResizing: PropTypes.bool.isRequired, pinnedPosition: PropTypes.oneOf(['left', 'right']), sectionLength: PropTypes.number.isRequired, diff --git a/packages/x-data-grid/src/components/containers/GridRootStyles.ts b/packages/x-data-grid/src/components/containers/GridRootStyles.ts index a6f5075d3d3f..0d5e88de51f6 100644 --- a/packages/x-data-grid/src/components/containers/GridRootStyles.ts +++ b/packages/x-data-grid/src/components/containers/GridRootStyles.ts @@ -279,6 +279,8 @@ export const GridRootStyles = styled('div', { position: 'relative', display: 'flex', alignItems: 'center', + }, + [`& .${c['columnHeader--last']}`]: { overflow: 'hidden', }, [`& .${c['columnHeader--sorted']} .${c.iconButtonContainer}, & .${c['columnHeader--filtered']} .${c.iconButtonContainer}`]: @@ -646,6 +648,9 @@ export const GridRootStyles = styled('div', { [`& .${c.filler}`]: { flex: 1, }, + [`& .${c['filler--borderTop']}`]: { + borderTop: '1px solid var(--DataGrid-rowBorderColor)', + }, }; return gridStyle; diff --git a/packages/x-data-grid/src/components/panel/GridPanel.tsx b/packages/x-data-grid/src/components/panel/GridPanel.tsx index b6f549609514..a169fd4ea6c1 100644 --- a/packages/x-data-grid/src/components/panel/GridPanel.tsx +++ b/packages/x-data-grid/src/components/panel/GridPanel.tsx @@ -51,6 +51,8 @@ const GridPaperRoot = styled(Paper, { minWidth: 300, maxHeight: 450, display: 'flex', + maxWidth: `calc(100vw - ${theme.spacing(0.5)})`, + overflow: 'auto', })); const GridPanel = React.forwardRef((props, ref) => { diff --git a/packages/x-data-grid/src/constants/gridClasses.ts b/packages/x-data-grid/src/constants/gridClasses.ts index 1d1e43e31457..c795ccda8336 100644 --- a/packages/x-data-grid/src/constants/gridClasses.ts +++ b/packages/x-data-grid/src/constants/gridClasses.ts @@ -117,6 +117,10 @@ export interface GridClasses { * Styles applied to the selection checkbox element. */ checkboxInput: string; + /** + * Styles applied to the column header element. + */ + columnHeader: string; /** * Styles applied to the column header if `headerAlign="center"`. */ @@ -156,9 +160,9 @@ export interface GridClasses { 'columnHeader--pinnedLeft': string; 'columnHeader--pinnedRight': string; /** - * Styles applied to the column header element. + * Styles applied to the last column header element. */ - columnHeader: string; + 'columnHeader--last': string; /** * Styles applied to the header checkbox cell element. */ @@ -292,6 +296,11 @@ export interface GridClasses { * @ignore - do not document. */ filler: string; + /** + * Styles applied to the filler row with top border. + * @ignore - do not document. + */ + 'filler--borderTop': string; /** * Styles applied to the filler row pinned left section. * @ignore - do not document. @@ -630,6 +639,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [ 'cellSkeleton', 'cellOffsetLeft', 'checkboxInput', + 'columnHeader', 'columnHeader--alignCenter', 'columnHeader--alignLeft', 'columnHeader--alignRight', @@ -641,7 +651,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [ 'columnHeader--filtered', 'columnHeader--pinnedLeft', 'columnHeader--pinnedRight', - 'columnHeader', + 'columnHeader--last', 'columnHeaderCheckbox', 'columnHeaderDraggableContainer', 'columnHeaderTitle', @@ -675,6 +685,7 @@ export const gridClasses = generateUtilityClasses('MuiDataGrid', [ 'editBooleanCell', 'editInputCell', 'filler', + 'filler--borderTop', 'filler--pinnedLeft', 'filler--pinnedRight', 'filterForm', diff --git a/packages/x-data-grid/src/hooks/core/useGridInitialization.ts b/packages/x-data-grid/src/hooks/core/useGridInitialization.ts index 326c90829889..104363f8ba80 100644 --- a/packages/x-data-grid/src/hooks/core/useGridInitialization.ts +++ b/packages/x-data-grid/src/hooks/core/useGridInitialization.ts @@ -25,7 +25,7 @@ export const useGridInitialization = < useGridRefs(privateApiRef); useGridTheme(privateApiRef); useGridLoggerFactory(privateApiRef, props); - useGridStateInitialization(privateApiRef, props); + useGridStateInitialization(privateApiRef); useGridPipeProcessing(privateApiRef); useGridStrategyProcessing(privateApiRef); useGridLocaleText(privateApiRef, props); diff --git a/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts b/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts index 860d4d505578..b33affee0f65 100644 --- a/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts +++ b/packages/x-data-grid/src/hooks/core/useGridStateInitialization.ts @@ -1,15 +1,12 @@ import * as React from 'react'; -import { DataGridProcessedProps } from '../../models/props/DataGridProps'; import type { GridPrivateApiCommon } from '../../models/api/gridApiCommon'; import { GridStateApi, GridStatePrivateApi } from '../../models/api/gridStateApi'; import { GridControlStateItem } from '../../models/controlStateItem'; -import { GridSignature } from '../utils/useGridApiEventHandler'; import { useGridApiMethod } from '../utils'; import { isFunction } from '../../utils/utils'; export const useGridStateInitialization = ( apiRef: React.MutableRefObject, - props: Pick, ) => { const controlStateMapRef = React.useRef< Record> @@ -92,11 +89,10 @@ export const useGridStateInitialization = styles.columnHeaderRow, })<{ ownerState: OwnerState }>({ display: 'flex', - height: 'var(--DataGrid-headerHeight)', }); export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { @@ -110,6 +110,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { theme.direction, pinnedColumns.left.length, ); + const gridHasFiller = dimensions.columnsTotalWidth < dimensions.viewportOuterSize.width; React.useEffect(() => { apiRef.current.columnHeadersContainerRef!.current!.scrollLeft = 0; @@ -194,7 +195,12 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { {isNotPinned &&
} {children} - {isNotPinned &&
} + {isNotPinned && ( +
+ )} {hasScrollbarFiller && ( )} @@ -269,12 +275,14 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { colDef={colDef} colIndex={columnIndex} isResizing={resizeCol === colDef.field} + isLast={columnIndex === columnPositions.length - 1} hasFocus={hasFocus} tabIndex={tabIndex} pinnedPosition={pinnedPosition} style={style} indexInSection={i} sectionLength={renderedColumns.length} + gridHasFiller={gridHasFiller} {...other} />, ); @@ -435,6 +443,7 @@ export const useGridColumnHeaders = (props: UseGridColumnHeadersProps) => { style={style} indexInSection={indexInSection} sectionLength={renderedColumns.length} + gridHasFiller={gridHasFiller} /> ); }); diff --git a/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx b/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx index c547c07b93e7..7595d08380aa 100644 --- a/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx +++ b/packages/x-data-grid/src/hooks/features/columnResize/useGridColumnResize.tsx @@ -3,6 +3,7 @@ import { unstable_ownerDocument as ownerDocument, unstable_useEventCallback as useEventCallback, } from '@mui/utils'; +import useLazyRef from '@mui/utils/useLazyRef'; import { useTheme, Direction } from '@mui/material/styles'; import { findGridCellElementsFromCol, @@ -264,6 +265,26 @@ export const columnResizeStateInitializer: GridStateInitializer = (state) => ({ ...state, columnResize: { resizingColumnField: '' }, }); + +function createResizeRefs() { + return { + colDef: undefined as undefined | GridStateColDef, + initialColWidth: 0, + initialTotalWidth: 0, + previousMouseClickEvent: undefined as undefined | MouseEvent, + columnHeaderElement: undefined as undefined | HTMLDivElement, + headerFilterElement: undefined as undefined | HTMLDivElement, + groupHeaderElements: [] as Element[], + cellElements: [] as Element[], + leftPinnedCellsAfter: [] as HTMLElement[], + rightPinnedCellsBefore: [] as HTMLElement[], + fillerLeft: undefined as undefined | HTMLElement, + fillerRight: undefined as undefined | HTMLElement, + leftPinnedHeadersAfter: [] as HTMLElement[], + rightPinnedHeadersBefore: [] as HTMLElement[], + }; +} + /** * @requires useGridColumns (method, event) * TODO: improve experience for last column @@ -282,18 +303,7 @@ export const useGridColumnResize = ( const theme = useTheme(); const logger = useGridLogger(apiRef, 'useGridColumnResize'); - const colDefRef = React.useRef(); - const previousMouseClickEvent = React.useRef(); - const columnHeaderElementRef = React.useRef(); - const headerFilterElementRef = React.useRef(); - const groupHeaderElementsRef = React.useRef([]); - const cellElementsRef = React.useRef([]); - const leftPinnedCellsAfterRef = React.useRef([]); - const rightPinnedCellsBeforeRef = React.useRef([]); - const fillerLeftRef = React.useRef(); - const fillerRightRef = React.useRef(); - const leftPinnedHeadersAfterRef = React.useRef([]); - const rightPinnedHeadersBeforeRef = React.useRef([]); + const refs = useLazyRef(createResizeRefs).current; // To improve accessibility, the separator has padding on both sides. // Clicking inside the padding area should be treated as a click in the separator. @@ -305,27 +315,34 @@ export const useGridColumnResize = ( const touchId = React.useRef(); const updateWidth = (newWidth: number) => { - logger.debug(`Updating width to ${newWidth} for col ${colDefRef.current!.field}`); + logger.debug(`Updating width to ${newWidth} for col ${refs.colDef!.field}`); - const prevWidth = columnHeaderElementRef.current!.offsetWidth; + const prevWidth = refs.columnHeaderElement!.offsetWidth; const widthDiff = newWidth - prevWidth; + const columnWidthDiff = newWidth - refs.initialColWidth; + const newTotalWidth = refs.initialTotalWidth + columnWidthDiff; - colDefRef.current!.computedWidth = newWidth; - colDefRef.current!.width = newWidth; - colDefRef.current!.flex = 0; + apiRef.current.rootElementRef?.current?.style.setProperty( + '--DataGrid-rowWidth', + `${newTotalWidth}px`, + ); + + refs.colDef!.computedWidth = newWidth; + refs.colDef!.width = newWidth; + refs.colDef!.flex = 0; - columnHeaderElementRef.current!.style.width = `${newWidth}px`; - columnHeaderElementRef.current!.style.minWidth = `${newWidth}px`; - columnHeaderElementRef.current!.style.maxWidth = `${newWidth}px`; + refs.columnHeaderElement!.style.width = `${newWidth}px`; + refs.columnHeaderElement!.style.minWidth = `${newWidth}px`; + refs.columnHeaderElement!.style.maxWidth = `${newWidth}px`; - const headerFilterElement = headerFilterElementRef.current; + const headerFilterElement = refs.headerFilterElement; if (headerFilterElement) { headerFilterElement.style.width = `${newWidth}px`; headerFilterElement.style.minWidth = `${newWidth}px`; headerFilterElement.style.maxWidth = `${newWidth}px`; } - groupHeaderElementsRef.current!.forEach((element) => { + refs.groupHeaderElements!.forEach((element) => { const div = element as HTMLDivElement; let finalWidth: `${number}px`; @@ -342,7 +359,7 @@ export const useGridColumnResize = ( div.style.maxWidth = finalWidth; }); - cellElementsRef.current!.forEach((element) => { + refs.cellElements!.forEach((element) => { const div = element as HTMLDivElement; let finalWidth: `${number}px`; @@ -360,27 +377,27 @@ export const useGridColumnResize = ( const pinnedPosition = apiRef.current.unstable_applyPipeProcessors( 'isColumnPinned', false, - colDefRef.current!.field, + refs.colDef!.field, ); if (pinnedPosition === GridPinnedColumnPosition.LEFT) { - updateProperty(fillerLeftRef.current, 'width', widthDiff); + updateProperty(refs.fillerLeft, 'width', widthDiff); - leftPinnedCellsAfterRef.current.forEach((cell) => { + refs.leftPinnedCellsAfter.forEach((cell) => { updateProperty(cell, 'left', widthDiff); }); - leftPinnedHeadersAfterRef.current.forEach((header) => { + refs.leftPinnedHeadersAfter.forEach((header) => { updateProperty(header, 'left', widthDiff); }); } if (pinnedPosition === GridPinnedColumnPosition.RIGHT) { - updateProperty(fillerRightRef.current, 'width', widthDiff); + updateProperty(refs.fillerRight, 'width', widthDiff); - rightPinnedCellsBeforeRef.current.forEach((cell) => { + refs.rightPinnedCellsBefore.forEach((cell) => { updateProperty(cell, 'right', widthDiff); }); - rightPinnedHeadersBeforeRef.current.forEach((header) => { + refs.rightPinnedHeadersBefore.forEach((header) => { updateProperty(header, 'right', widthDiff); }); } @@ -391,8 +408,8 @@ export const useGridColumnResize = ( stopListening(); // Prevent double-clicks from being interpreted as two separate clicks - if (previousMouseClickEvent.current) { - const prevEvent = previousMouseClickEvent.current; + if (refs.previousMouseClickEvent) { + const prevEvent = refs.previousMouseClickEvent; const prevTimeStamp = prevEvent.timeStamp; const prevClientX = prevEvent.clientX; const prevClientY = prevEvent.clientY; @@ -403,16 +420,14 @@ export const useGridColumnResize = ( nativeEvent.clientX === prevClientX && nativeEvent.clientY === prevClientY ) { - previousMouseClickEvent.current = undefined; + refs.previousMouseClickEvent = undefined; return; } } - if (colDefRef.current) { - apiRef.current.setColumnWidth(colDefRef.current.field, colDefRef.current.width!); - logger.debug( - `Updating col ${colDefRef.current.field} with new width: ${colDefRef.current.width}`, - ); + if (refs.colDef) { + apiRef.current.setColumnWidth(refs.colDef.field, refs.colDef.width!); + logger.debug(`Updating col ${refs.colDef.field} with new width: ${refs.colDef.width}`); } stopResizeEventTimeout.start(0, () => { @@ -423,9 +438,12 @@ export const useGridColumnResize = ( const storeReferences = (colDef: GridStateColDef, separator: HTMLElement, xStart: number) => { const root = apiRef.current.rootElementRef.current!; - colDefRef.current = colDef as GridStateColDef; + refs.initialColWidth = colDef.computedWidth; + refs.initialTotalWidth = apiRef.current.getRootDimensions().rowWidth; + + refs.colDef = colDef as GridStateColDef; - columnHeaderElementRef.current = findHeaderElementFromField( + refs.columnHeaderElement = findHeaderElementFromField( apiRef.current.columnHeadersContainerRef!.current!, colDef.field, ); @@ -434,51 +452,48 @@ export const useGridColumnResize = ( `.${gridClasses.headerFilterRow} [data-field="${colDef.field}"]`, ); if (headerFilterElement) { - headerFilterElementRef.current = headerFilterElement as HTMLDivElement; + refs.headerFilterElement = headerFilterElement as HTMLDivElement; } - groupHeaderElementsRef.current = findGroupHeaderElementsFromField( + refs.groupHeaderElements = findGroupHeaderElementsFromField( apiRef.current.columnHeadersContainerRef?.current!, colDef.field, ); - cellElementsRef.current = findGridCellElementsFromCol( - columnHeaderElementRef.current, - apiRef.current, - ); + refs.cellElements = findGridCellElementsFromCol(refs.columnHeaderElement, apiRef.current); - fillerLeftRef.current = findGridElement(apiRef.current, 'filler--pinnedLeft'); - fillerRightRef.current = findGridElement(apiRef.current, 'filler--pinnedRight'); + refs.fillerLeft = findGridElement(apiRef.current, 'filler--pinnedLeft'); + refs.fillerRight = findGridElement(apiRef.current, 'filler--pinnedRight'); const pinnedPosition = apiRef.current.unstable_applyPipeProcessors( 'isColumnPinned', false, - colDefRef.current!.field, + refs.colDef!.field, ); - leftPinnedCellsAfterRef.current = + refs.leftPinnedCellsAfter = pinnedPosition !== GridPinnedColumnPosition.LEFT ? [] - : findLeftPinnedCellsAfterCol(apiRef.current, columnHeaderElementRef.current); - rightPinnedCellsBeforeRef.current = + : findLeftPinnedCellsAfterCol(apiRef.current, refs.columnHeaderElement); + refs.rightPinnedCellsBefore = pinnedPosition !== GridPinnedColumnPosition.RIGHT ? [] - : findRightPinnedCellsBeforeCol(apiRef.current, columnHeaderElementRef.current); + : findRightPinnedCellsBeforeCol(apiRef.current, refs.columnHeaderElement); - leftPinnedHeadersAfterRef.current = + refs.leftPinnedHeadersAfter = pinnedPosition !== GridPinnedColumnPosition.LEFT ? [] - : findLeftPinnedHeadersAfterCol(apiRef.current, columnHeaderElementRef.current); - rightPinnedHeadersBeforeRef.current = + : findLeftPinnedHeadersAfterCol(apiRef.current, refs.columnHeaderElement); + refs.rightPinnedHeadersBefore = pinnedPosition !== GridPinnedColumnPosition.RIGHT ? [] - : findRightPinnedHeadersBeforeCol(apiRef.current, columnHeaderElementRef.current); + : findRightPinnedHeadersBeforeCol(apiRef.current, refs.columnHeaderElement); resizeDirection.current = getResizeDirection(separator, theme.direction); initialOffsetToSeparator.current = computeOffsetToSeparator( xStart, - columnHeaderElementRef.current!.getBoundingClientRect(), + refs.columnHeaderElement!.getBoundingClientRect(), resizeDirection.current, ); }; @@ -495,16 +510,16 @@ export const useGridColumnResize = ( let newWidth = computeNewWidth( initialOffsetToSeparator.current!, nativeEvent.clientX, - columnHeaderElementRef.current!.getBoundingClientRect(), + refs.columnHeaderElement!.getBoundingClientRect(), resizeDirection.current!, ); - newWidth = clamp(newWidth, colDefRef.current!.minWidth!, colDefRef.current!.maxWidth!); + newWidth = clamp(newWidth, refs.colDef!.minWidth!, refs.colDef!.maxWidth!); updateWidth(newWidth); const params: GridColumnResizeParams = { - element: columnHeaderElementRef.current, - colDef: colDefRef.current!, + element: refs.columnHeaderElement, + colDef: refs.colDef!, width: newWidth, }; apiRef.current.publishEvent('columnResize', params, nativeEvent); @@ -535,16 +550,16 @@ export const useGridColumnResize = ( let newWidth = computeNewWidth( initialOffsetToSeparator.current!, (finger as CursorCoordinates).x, - columnHeaderElementRef.current!.getBoundingClientRect(), + refs.columnHeaderElement!.getBoundingClientRect(), resizeDirection.current!, ); - newWidth = clamp(newWidth, colDefRef.current!.minWidth!, colDefRef.current!.maxWidth!); + newWidth = clamp(newWidth, refs.colDef!.minWidth!, refs.colDef!.maxWidth!); updateWidth(newWidth); const params: GridColumnResizeParams = { - element: columnHeaderElementRef.current, - colDef: colDefRef.current!, + element: refs.columnHeaderElement, + colDef: refs.colDef!, width: newWidth, }; apiRef.current.publishEvent('columnResize', params, nativeEvent); @@ -599,17 +614,10 @@ export const useGridColumnResize = ( setTimeout(() => { doc.removeEventListener('click', preventClick, true); }, 100); - if (columnHeaderElementRef.current) { - columnHeaderElementRef.current!.style.pointerEvents = 'unset'; + if (refs.columnHeaderElement) { + refs.columnHeaderElement!.style.pointerEvents = 'unset'; } - }, [ - apiRef, - columnHeaderElementRef, - handleResizeMouseMove, - handleResizeMouseUp, - handleTouchMove, - handleTouchEnd, - ]); + }, [apiRef, refs, handleResizeMouseMove, handleResizeMouseUp, handleTouchMove, handleTouchEnd]); const handleResizeStart = React.useCallback>( ({ field }) => { @@ -653,7 +661,7 @@ export const useGridColumnResize = ( const doc = ownerDocument(apiRef.current.rootElementRef!.current); doc.body.style.cursor = 'col-resize'; - previousMouseClickEvent.current = event.nativeEvent; + refs.previousMouseClickEvent = event.nativeEvent; doc.addEventListener('mousemove', handleResizeMouseMove); doc.addEventListener('mouseup', handleResizeMouseUp); diff --git a/packages/x-data-grid/src/hooks/features/columns/gridColumnsUtils.ts b/packages/x-data-grid/src/hooks/features/columns/gridColumnsUtils.ts index 7da63d617fef..94ec07bb341a 100644 --- a/packages/x-data-grid/src/hooks/features/columns/gridColumnsUtils.ts +++ b/packages/x-data-grid/src/hooks/features/columns/gridColumnsUtils.ts @@ -12,6 +12,7 @@ import { GRID_STRING_COL_DEF, getGridDefaultColumnTypes, } from '../../../colDef'; +import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridApiCommunity } from '../../../models/api/gridApiCommunity'; import { GridColDef, GridStateColDef } from '../../../models/colDef/gridColDef'; import { gridColumnsStateSelector, gridColumnVisibilityModelSelector } from './gridColumnsSelector'; @@ -431,11 +432,16 @@ export function getFirstNonSpannedColumnToRender({ export function getTotalHeaderHeight( apiRef: React.MutableRefObject, - headerHeight: number, + props: Pick, ) { const densityFactor = gridDensityFactorSelector(apiRef); const maxDepth = gridColumnGroupsHeaderMaxDepthSelector(apiRef); const isHeaderFilteringEnabled = gridHeaderFilteringEnabledSelector(apiRef); - const multiplicationFactor = isHeaderFilteringEnabled ? 2 : 1; - return Math.floor(headerHeight * densityFactor) * ((maxDepth ?? 0) + multiplicationFactor); + + const columnHeadersHeight = Math.floor(props.columnHeaderHeight * densityFactor); + const filterHeadersHeight = isHeaderFilteringEnabled + ? Math.floor((props.headerFilterHeight ?? props.columnHeaderHeight) * densityFactor) + : 0; + + return columnHeadersHeight * (1 + (maxDepth ?? 0)) + filterHeadersHeight; } diff --git a/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx b/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx index 172b161c2cdb..0d21ec3a071c 100644 --- a/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx +++ b/packages/x-data-grid/src/hooks/features/density/useGridDensity.tsx @@ -7,6 +7,7 @@ import { GridDensityApi } from '../../../models/api/gridDensityApi'; import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { gridDensitySelector } from './densitySelector'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; +import { GridPipeProcessor, useGridRegisterPipeProcessor } from '../../core/pipeProcessing'; export const densityStateInitializer: GridStateInitializer< Pick @@ -17,7 +18,7 @@ export const densityStateInitializer: GridStateInitializer< export const useGridDensity = ( apiRef: React.MutableRefObject, - props: Pick, + props: Pick, ): void => { const logger = useGridLogger(apiRef, 'useDensity'); @@ -43,15 +44,56 @@ export const useGridDensity = ( })); }); - React.useEffect(() => { - if (props.density) { - apiRef.current.setDensity(props.density); - } - }, [apiRef, props.density]); - const densityApi: GridDensityApi = { setDensity, }; useGridApiMethod(apiRef, densityApi, 'public'); + + const stateExportPreProcessing = React.useCallback>( + (prevState, context) => { + const exportedDensity = gridDensitySelector(apiRef.current.state); + + const shouldExportRowCount = + // Always export if the `exportOnlyDirtyModels` property is not activated + !context.exportOnlyDirtyModels || + // Always export if the `density` is controlled + props.density != null || + // Always export if the `density` has been initialized + props.initialState?.density != null; + + if (!shouldExportRowCount) { + return prevState; + } + + return { + ...prevState, + density: exportedDensity, + }; + }, + [apiRef, props.density, props.initialState?.density], + ); + + const stateRestorePreProcessing = React.useCallback>( + (params, context) => { + const restoredDensity = context.stateToRestore?.density + ? context.stateToRestore.density + : gridDensitySelector(apiRef.current.state); + apiRef.current.setState((state) => ({ + ...state, + density: restoredDensity, + })); + return params; + }, + [apiRef], + ); + + useGridRegisterPipeProcessor(apiRef, 'exportState', stateExportPreProcessing); + useGridRegisterPipeProcessor(apiRef, 'restoreState', stateRestorePreProcessing); + + React.useEffect(() => { + if (props.density) { + apiRef.current.setDensity(props.density); + } + }, [apiRef, props.density]); }; diff --git a/packages/x-data-grid/src/hooks/features/dimensions/gridDimensionsApi.ts b/packages/x-data-grid/src/hooks/features/dimensions/gridDimensionsApi.ts index 277b71145688..c7b5bdd3815c 100644 --- a/packages/x-data-grid/src/hooks/features/dimensions/gridDimensionsApi.ts +++ b/packages/x-data-grid/src/hooks/features/dimensions/gridDimensionsApi.ts @@ -59,9 +59,13 @@ export interface GridDimensions { */ rightPinnedWidth: number; /** - * Height of one headers. + * Height of one column header. */ headerHeight: number; + /** + * Height of header filters. + */ + headerFilterHeight: number; /** * Height of all the column headers. */ diff --git a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts index 86c21a6195dc..1c3a96e97712 100644 --- a/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts +++ b/packages/x-data-grid/src/hooks/features/dimensions/useGridDimensions.ts @@ -42,6 +42,7 @@ type RootProps = Pick< | 'rowHeight' | 'resizeThrottleMs' | 'columnHeaderHeight' + | 'headerFilterHeight' >; export type GridDimensionsState = GridDimensions; @@ -58,6 +59,7 @@ const EMPTY_DIMENSIONS: GridDimensions = { hasScrollY: false, scrollbarSize: 0, headerHeight: 0, + headerFilterHeight: 0, rowWidth: 0, rowHeight: 0, columnsTotalWidth: 0, @@ -89,8 +91,11 @@ export function useGridDimensions( const densityFactor = useGridSelector(apiRef, gridDensityFactorSelector); const rowHeight = Math.floor(props.rowHeight * densityFactor); const headerHeight = Math.floor(props.columnHeaderHeight * densityFactor); + const headerFilterHeight = Math.floor( + (props.headerFilterHeight ?? props.columnHeaderHeight) * densityFactor, + ); const columnsTotalWidth = roundToDecimalPlaces(gridColumnsTotalWidthSelector(apiRef), 6); - const headersTotalHeight = getTotalHeaderHeight(apiRef, props.columnHeaderHeight); + const headersTotalHeight = getTotalHeaderHeight(apiRef, props); const leftPinnedWidth = pinnedColumns.left.reduce((w, col) => w + col.computedWidth, 0); const rightPinnedWidth = pinnedColumns.right.reduce((w, col) => w + col.computedWidth, 0); @@ -244,6 +249,7 @@ export function useGridDimensions( hasScrollY, scrollbarSize, headerHeight, + headerFilterHeight, rowWidth, rowHeight, columnsTotalWidth, @@ -273,6 +279,7 @@ export function useGridDimensions( rowsMeta.currentPageTotalHeight, rowHeight, headerHeight, + headerFilterHeight, columnsTotalWidth, headersTotalHeight, leftPinnedWidth, diff --git a/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts b/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts index 954b96ba8ee0..8220068b7d35 100644 --- a/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts +++ b/packages/x-data-grid/src/hooks/features/editing/useGridCellEditing.ts @@ -234,6 +234,21 @@ export const useGridCellEditing = ( [apiRef], ); + const runIfNoFieldErrors = + >>( + callback?: (...args: Args) => void, + ) => + async (...args: Args) => { + if (callback) { + const { id, field } = args[0]; + const editRowsState = apiRef.current.state.editRows; + const hasFieldErrors = editRowsState[id][field]?.error; + if (!hasFieldErrors) { + callback(...args); + } + } + }; + useGridApiEventHandler(apiRef, 'cellDoubleClick', runIfEditModeIsCell(handleCellDoubleClick)); useGridApiEventHandler(apiRef, 'cellFocusOut', runIfEditModeIsCell(handleCellFocusOut)); useGridApiEventHandler(apiRef, 'cellKeyDown', runIfEditModeIsCell(handleCellKeyDown)); @@ -242,7 +257,7 @@ export const useGridCellEditing = ( useGridApiEventHandler(apiRef, 'cellEditStop', runIfEditModeIsCell(handleCellEditStop)); useGridApiOptionHandler(apiRef, 'cellEditStart', props.onCellEditStart); - useGridApiOptionHandler(apiRef, 'cellEditStop', props.onCellEditStop); + useGridApiOptionHandler(apiRef, 'cellEditStop', runIfNoFieldErrors(props.onCellEditStop)); const getCellMode = React.useCallback( (id, field) => { @@ -257,7 +272,9 @@ export const useGridCellEditing = ( const isNewModelDifferentFromProp = newModel !== props.cellModesModel; if (onCellModesModelChange && isNewModelDifferentFromProp) { - onCellModesModelChange(newModel, {}); + onCellModesModelChange(newModel, { + api: apiRef.current, + }); } if (props.cellModesModel && isNewModelDifferentFromProp) { diff --git a/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts b/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts index ce2e3d5ffd84..95882331db34 100644 --- a/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts +++ b/packages/x-data-grid/src/hooks/features/editing/useGridRowEditing.ts @@ -349,7 +349,9 @@ export const useGridRowEditing = ( const isNewModelDifferentFromProp = newModel !== props.rowModesModel; if (onRowModesModelChange && isNewModelDifferentFromProp) { - onRowModesModelChange(newModel, {}); + onRowModesModelChange(newModel, { + api: apiRef.current, + }); } if (props.rowModesModel && isNewModelDifferentFromProp) { diff --git a/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx b/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx index 68957d6884cb..909ecae28e8c 100644 --- a/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx +++ b/packages/x-data-grid/src/hooks/features/export/useGridPrintExport.tsx @@ -64,7 +64,7 @@ function buildPrintWindow(title?: string): HTMLIFrameElement { */ export const useGridPrintExport = ( apiRef: React.MutableRefObject, - props: Pick, + props: Pick, ): void => { const logger = useGridLogger(apiRef, 'useGridPrintExport'); const doc = React.useRef(null); @@ -160,7 +160,7 @@ export const useGridPrintExport = ( // Expand container height to accommodate all rows const computedTotalHeight = rowsMeta.currentPageTotalHeight + - getTotalHeaderHeight(apiRef, props.columnHeaderHeight) + + getTotalHeaderHeight(apiRef, props) + gridToolbarElementHeight + gridFooterElementHeight; gridClone.style.height = `${computedTotalHeight}px`; @@ -256,7 +256,7 @@ export const useGridPrintExport = ( }); } }, - [apiRef, doc, props.columnHeaderHeight], + [apiRef, doc, props], ); const handlePrintWindowAfterPrint = React.useCallback( diff --git a/packages/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts b/packages/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts index fccdbb154952..dae3a8542487 100644 --- a/packages/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts +++ b/packages/x-data-grid/src/hooks/features/headerFiltering/useGridHeaderFiltering.ts @@ -1,6 +1,6 @@ import * as React from 'react'; import { GridPrivateApiCommunity } from '../../../models/api/gridApiCommunity'; -import { DataGridProcessedPropsWithShared } from '../../../models/props/DataGridProps'; +import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { GridHeaderFilteringState } from '../../../models/gridHeaderFilteringModel'; import { useGridApiMethod } from '../../utils/useGridApiMethod'; import { GridStateInitializer } from '../../utils/useGridInitializeState'; @@ -17,7 +17,7 @@ import { export const headerFilteringStateInitializer: GridStateInitializer = ( state, - props: DataGridProcessedPropsWithShared, + props: DataGridProcessedProps, ) => ({ ...state, headerFiltering: { enabled: props.headerFilters ?? false, editing: null, menuOpen: null }, @@ -25,7 +25,7 @@ export const headerFilteringStateInitializer: GridStateInitializer = ( export const useGridHeaderFiltering = ( apiRef: React.MutableRefObject, - props: Pick, + props: Pick, ) => { const logger = useGridLogger(apiRef, 'useGridHeaderFiltering'); const setHeaderFilterState = React.useCallback( diff --git a/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts b/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts index 1a2d4d606823..1854e8a134b0 100644 --- a/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts +++ b/packages/x-data-grid/src/hooks/features/keyboardNavigation/useGridKeyboardNavigation.ts @@ -6,7 +6,7 @@ import { GridCellParams } from '../../../models/params/gridCellParams'; import { gridVisibleColumnDefinitionsSelector } from '../columns/gridColumnsSelector'; import { useGridLogger } from '../../utils/useGridLogger'; import { useGridApiEventHandler } from '../../utils/useGridApiEventHandler'; -import { DataGridProcessedPropsWithShared } from '../../../models/props/DataGridProps'; +import { DataGridProcessedProps } from '../../../models/props/DataGridProps'; import { gridExpandedSortedRowEntriesSelector } from '../filter/gridFilterSelector'; import { useGridVisibleRows } from '../../utils/useGridVisibleRows'; import { GRID_CHECKBOX_SELECTION_COL_DEF } from '../../../colDef/gridCheckboxSelectionColDef'; @@ -92,7 +92,7 @@ const getRightColumnIndex = ({ export const useGridKeyboardNavigation = ( apiRef: React.MutableRefObject, props: Pick< - DataGridProcessedPropsWithShared, + DataGridProcessedProps, | 'pagination' | 'paginationMode' | 'getRowId' diff --git a/packages/x-data-grid/src/internals/index.ts b/packages/x-data-grid/src/internals/index.ts index bee9fac100d2..88b4d71bf80f 100644 --- a/packages/x-data-grid/src/internals/index.ts +++ b/packages/x-data-grid/src/internals/index.ts @@ -129,15 +129,7 @@ export { useGridVisibleRows, getVisibleRows } from '../hooks/utils/useGridVisibl export { useGridInitializeState } from '../hooks/utils/useGridInitializeState'; export type { GridStateInitializer } from '../hooks/utils/useGridInitializeState'; -export type { - DataGridProSharedPropsWithDefaultValue, - DataGridPremiumSharedPropsWithDefaultValue, - GridExperimentalFeatures, - DataGridPropsWithoutDefaultValue, - DataGridPropsWithDefaultValues, - DataGridPropsWithComplexDefaultValueAfterProcessing, - DataGridPropsWithComplexDefaultValueBeforeProcessing, -} from '../models/props/DataGridProps'; +export type * from '../models/props/DataGridProps'; export { getColumnsToExport, defaultGetRowsToExport } from '../hooks/features/export/utils'; export * from '../utils/createControllablePromise'; diff --git a/packages/x-data-grid/src/locales/daDK.ts b/packages/x-data-grid/src/locales/daDK.ts index 2632d030210d..577fc80f2d5c 100644 --- a/packages/x-data-grid/src/locales/daDK.ts +++ b/packages/x-data-grid/src/locales/daDK.ts @@ -39,9 +39,9 @@ const daDKGrid: Partial = { toolbarExportExcel: 'Download som Excel', // Columns management text - // columnsManagementSearchTitle: 'Search', - // columnsManagementNoColumns: 'No columns', - // columnsManagementShowHideAllText: 'Show/Hide All', + columnsManagementSearchTitle: 'Søg', + columnsManagementNoColumns: 'Ingen søjler', + columnsManagementShowHideAllText: 'Vis/Shjul Alle', // Filter panel text filterPanelAddFilter: 'Tilføj filter', diff --git a/packages/x-data-grid/src/locales/deDE.ts b/packages/x-data-grid/src/locales/deDE.ts index 2d8affd4cdef..6df5801d716d 100644 --- a/packages/x-data-grid/src/locales/deDE.ts +++ b/packages/x-data-grid/src/locales/deDE.ts @@ -39,9 +39,9 @@ const deDEGrid: Partial = { toolbarExportExcel: 'Download als Excel', // Columns management text - // columnsManagementSearchTitle: 'Search', - // columnsManagementNoColumns: 'No columns', - // columnsManagementShowHideAllText: 'Show/Hide All', + columnsManagementSearchTitle: 'Suche', + columnsManagementNoColumns: 'Keine Spalten', + columnsManagementShowHideAllText: 'Alle anzeigen/verbergen', // Filter panel text filterPanelAddFilter: 'Filter hinzufügen', diff --git a/packages/x-data-grid/src/locales/frFR.ts b/packages/x-data-grid/src/locales/frFR.ts index b7118ef678eb..dc9340fefce1 100644 --- a/packages/x-data-grid/src/locales/frFR.ts +++ b/packages/x-data-grid/src/locales/frFR.ts @@ -21,7 +21,7 @@ const frFRGrid: Partial = { // Filters toolbar button text toolbarFilters: 'Filtres', toolbarFiltersLabel: 'Afficher les filtres', - toolbarFiltersTooltipHide: 'Cacher les filtres', + toolbarFiltersTooltipHide: 'Masquer les filtres', toolbarFiltersTooltipShow: 'Afficher les filtres', toolbarFiltersTooltipActive: (count) => count > 1 ? `${count} filtres actifs` : `${count} filtre actif`, @@ -39,9 +39,9 @@ const frFRGrid: Partial = { toolbarExportExcel: 'Télécharger pour Excel', // Columns management text - // columnsManagementSearchTitle: 'Search', - // columnsManagementNoColumns: 'No columns', - // columnsManagementShowHideAllText: 'Show/Hide All', + columnsManagementSearchTitle: 'Rechercher', + columnsManagementNoColumns: 'Pas de colonnes', + columnsManagementShowHideAllText: 'Afficher/masquer toutes', // Filter panel text filterPanelAddFilter: 'Ajouter un filtre', @@ -107,7 +107,7 @@ const frFRGrid: Partial = { columnMenuShowColumns: 'Afficher les colonnes', columnMenuManageColumns: 'Gérer les colonnes', columnMenuFilter: 'Filtrer', - columnMenuHideColumn: 'Cacher', + columnMenuHideColumn: 'Masquer', columnMenuUnsort: 'Annuler le tri', columnMenuSortAsc: 'Tri ascendant', columnMenuSortDesc: 'Tri descendant', diff --git a/packages/x-data-grid/src/models/api/gridCallbackDetails.ts b/packages/x-data-grid/src/models/api/gridCallbackDetails.ts index bf5bf45b53a4..e4a8f4cb4916 100644 --- a/packages/x-data-grid/src/models/api/gridCallbackDetails.ts +++ b/packages/x-data-grid/src/models/api/gridCallbackDetails.ts @@ -1,4 +1,5 @@ import { GridControlledStateReasonLookup } from '../events/gridEventLookup'; +import { GridApiCommunity } from './gridApiCommunity'; /** * Additional details passed to the callbacks @@ -8,4 +9,8 @@ export interface GridCallbackDetails = Omit< pagination?: true; }; -/** - * The props of the `DataGrid` component after the pre-processing phase. - */ -export interface DataGridProcessedProps - extends DataGridPropsWithDefaultValues, - DataGridPropsWithComplexDefaultValueAfterProcessing, - DataGridPropsWithoutDefaultValue {} - /** * The props of the `DataGrid` component after the pre-processing phase that the user should not be able to override. * Those are usually used in feature-hook for which the pro-plan has more advanced features (eg: multi-sorting, multi-filtering, ...). @@ -799,6 +791,13 @@ export interface DataGridProSharedPropsWithDefaultValue { headerFilters: boolean; } +export interface DataGridProSharedPropsWithoutDefaultValue { + /** + * Override the height of the header filters. + */ + headerFilterHeight?: number; +} + export interface DataGridPremiumSharedPropsWithDefaultValue { /** * If `true`, the cell selection mode is enabled. @@ -808,9 +807,12 @@ export interface DataGridPremiumSharedPropsWithDefaultValue { } /** - * Contains the commercial packages' props shared in the MIT version. + * The props of the `DataGrid` component after the pre-processing phase. */ -export interface DataGridProcessedPropsWithShared - extends DataGridProcessedProps, +export interface DataGridProcessedProps + extends DataGridPropsWithDefaultValues, + DataGridPropsWithComplexDefaultValueAfterProcessing, + DataGridPropsWithoutDefaultValue, + DataGridProSharedPropsWithoutDefaultValue, Partial, Partial {} diff --git a/packages/x-data-grid/src/utils/cellBorderUtils.ts b/packages/x-data-grid/src/utils/cellBorderUtils.ts index 3cf82d37cac3..007e569bf9bd 100644 --- a/packages/x-data-grid/src/utils/cellBorderUtils.ts +++ b/packages/x-data-grid/src/utils/cellBorderUtils.ts @@ -5,14 +5,24 @@ export const shouldCellShowRightBorder = ( indexInSection: number, sectionLength: number, showCellVerticalBorderRootProp: boolean, + gridHasFiller: boolean, ) => { const isSectionLastCell = indexInSection === sectionLength - 1; - return ( - (showCellVerticalBorderRootProp && - (pinnedPosition !== GridPinnedColumnPosition.LEFT ? !isSectionLastCell : true)) || - (pinnedPosition === GridPinnedColumnPosition.LEFT && isSectionLastCell) - ); + if (pinnedPosition === GridPinnedColumnPosition.LEFT && isSectionLastCell) { + return true; + } + if (showCellVerticalBorderRootProp) { + if (pinnedPosition === GridPinnedColumnPosition.LEFT) { + return true; + } + if (pinnedPosition === GridPinnedColumnPosition.RIGHT) { + return !isSectionLastCell; + } + // pinnedPosition === undefined, middle section + return !isSectionLastCell || gridHasFiller; + } + return false; }; export const shouldCellShowLeftBorder = ( diff --git a/packages/x-date-pickers-pro/package.json b/packages/x-date-pickers-pro/package.json index 848f8ed64ac8..c72f516b3bf4 100644 --- a/packages/x-date-pickers-pro/package.json +++ b/packages/x-date-pickers-pro/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-date-pickers-pro", - "version": "7.1.1", + "version": "7.2.0", "description": "The Pro plan edition of the Date and Time Picker components (MUI X).", "author": "MUI Team", "main": "src/index.ts", @@ -45,8 +45,8 @@ "@mui/base": "^5.0.0-beta.40", "@mui/system": "^5.15.14", "@mui/utils": "^5.15.14", - "@mui/x-date-pickers": "7.1.1", - "@mui/x-license": "7.1.1", + "@mui/x-date-pickers": "7.2.0", + "@mui/x-license": "7.2.0", "clsx": "^2.1.0", "prop-types": "^15.8.1", "react-transition-group": "^4.4.5" diff --git a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx index 4d4c6155c38e..196d8c78856d 100644 --- a/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx +++ b/packages/x-date-pickers-pro/src/DateTimeRangePicker/DateTimeRangePickerToolbar.tsx @@ -102,11 +102,8 @@ const DateTimeRangePickerToolbarEnd = styled(DateTimePickerToolbar, { })>({ variants: [ { - props: ({ - ownerState: { toolbarVariant }, - }: { - ownerState: DateTimeRangePickerStartOrEndToolbarProps; - }) => toolbarVariant !== 'desktop', + props: ({ toolbarVariant }: DateTimeRangePickerStartOrEndToolbarProps) => + toolbarVariant !== 'desktop', style: { padding: '12px 8px 12px 12px', }, diff --git a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx index e151affce95e..b22137768f1d 100644 --- a/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx +++ b/packages/x-date-pickers-pro/src/DesktopDateTimeRangePicker/DesktopDateTimeRangePicker.tsx @@ -91,8 +91,9 @@ const rendererInterceptor = function rendererInterceptor< availableRangePositions: [rangePosition], view: !isTimeViewActive ? popperView : 'day', views: rendererProps.views.filter(isDatePickerView), + sx: [{ gridColumn: 1 }, ...finalProps.sx], })} - + } + sx={[{ gridColumn: 3 }, ...finalProps.sx]} /> ); diff --git a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts index c9f7d22249b0..ef1678d4fc68 100644 --- a/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts +++ b/packages/x-date-pickers-pro/src/internals/hooks/useEnrichedRangePickerFieldProps.ts @@ -198,7 +198,7 @@ const useMultiInputFieldSlotProps = < event.stopPropagation(); onRangePositionChange('start'); if (!readOnly && !disableOpenPicker) { - actions.onOpen(); + actions.onOpen(event); } }; @@ -206,7 +206,7 @@ const useMultiInputFieldSlotProps = < event.stopPropagation(); onRangePositionChange('end'); if (!readOnly && !disableOpenPicker) { - actions.onOpen(); + actions.onOpen(event); } }; @@ -400,7 +400,7 @@ const useSingleInputFieldSlotProps = < event.stopPropagation(); if (!readOnly && !disableOpenPicker) { - actions.onOpen(); + actions.onOpen(event); } }; diff --git a/packages/x-date-pickers/package.json b/packages/x-date-pickers/package.json index 95560a7999f9..a2f8f85412e0 100644 --- a/packages/x-date-pickers/package.json +++ b/packages/x-date-pickers/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-date-pickers", - "version": "7.1.1", + "version": "7.2.0", "description": "The community edition of the Date and Time Picker components (MUI X).", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx index 5b3c2977bab4..5ee528306825 100644 --- a/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx +++ b/packages/x-date-pickers/src/DesktopDateTimePicker/DesktopDateTimePicker.tsx @@ -90,16 +90,18 @@ const rendererInterceptor = function rendererInterceptor< view: !isTimeViewActive ? popperView : 'day', focusedView: focusedView && isDatePickerView(focusedView) ? focusedView : null, views: rendererProps.views.filter(isDatePickerView), + sx: [{ gridColumn: 1 }, ...finalProps.sx], })} {timeViewsCount > 0 && ( - + {inViewRenderers[isTimeViewActive ? popperView : 'hours']?.({ ...finalProps, view: isTimeViewActive ? popperView : 'hours', focusedView: focusedView && isInternalTimeView(focusedView) ? focusedView : null, openTo: isInternalTimeView(openTo) ? openTo : 'hours', views: rendererProps.views.filter(isInternalTimeView), + sx: [{ gridColumn: 3 }, ...finalProps.sx], })} )} diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts index e8627c7a1c19..86b72230ffa5 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.ts @@ -357,9 +357,15 @@ export const usePickerValue = < }); }); - const handleOpen = useEventCallback(() => setIsOpen(true)); + const handleOpen = useEventCallback((event: React.UIEvent) => { + event.preventDefault(); + setIsOpen(true); + }); - const handleClose = useEventCallback(() => setIsOpen(false)); + const handleClose = useEventCallback((event?: React.UIEvent) => { + event?.preventDefault(); + setIsOpen(false); + }); const handleChange = useEventCallback( (newValue: TValue, selectionState: PickerSelectionState = 'partial') => diff --git a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts index b7ba4a2fb6fe..7c878c57e613 100644 --- a/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts +++ b/packages/x-date-pickers/src/internals/hooks/usePicker/usePickerValue.types.ts @@ -301,8 +301,8 @@ export interface UsePickerValueActions { onDismiss: () => void; onCancel: () => void; onSetToday: () => void; - onOpen: () => void; - onClose: () => void; + onOpen: (event: React.UIEvent) => void; + onClose: (event?: React.UIEvent) => void; } export type UsePickerValueFieldResponse = Required< @@ -316,7 +316,7 @@ export interface UsePickerValueViewsResponse { value: TValue; onChange: (value: TValue, selectionState?: PickerSelectionState) => void; open: boolean; - onClose: () => void; + onClose: (event?: React.MouseEvent) => void; } /** diff --git a/packages/x-date-pickers/src/locales/deDE.ts b/packages/x-date-pickers/src/locales/deDE.ts index 6f7cc84acad8..185963fd7486 100644 --- a/packages/x-date-pickers/src/locales/deDE.ts +++ b/packages/x-date-pickers/src/locales/deDE.ts @@ -26,10 +26,10 @@ const deDEPickers: Partial> = { // DateRange labels start: 'Beginn', end: 'Ende', - // startDate: 'Start date', - // startTime: 'Start time', - // endDate: 'End date', - // endTime: 'End time', + startDate: 'Startdatum', + startTime: 'Startzeit', + endDate: 'Enddatum', + endTime: 'Endzeit', // Action bar cancelButtonLabel: 'Abbrechen', @@ -85,17 +85,17 @@ const deDEPickers: Partial> = { fieldMeridiemPlaceholder: () => 'aa', // View names - // year: 'Year', - // month: 'Month', - // day: 'Day', - // weekDay: 'Week day', - // hours: 'Hours', - // minutes: 'Minutes', - // seconds: 'Seconds', - // meridiem: 'Meridiem', + year: 'Jahr', + month: 'Monat', + day: 'Tag', + weekDay: 'Wochentag', + hours: 'Stunden', + minutes: 'Minuten', + seconds: 'Sekunden', + meridiem: 'Tageszeit', // Common - // empty: 'Empty', + empty: 'Leer', }; export const deDE = getPickersLocalization(deDEPickers); diff --git a/packages/x-license/package.json b/packages/x-license/package.json index 5f42788179d0..c25f323f11ac 100644 --- a/packages/x-license/package.json +++ b/packages/x-license/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-license", - "version": "7.1.1", + "version": "7.2.0", "description": "MUI X License verification", "author": "MUI Team", "main": "src/index.ts", diff --git a/packages/x-tree-view-pro/package.json b/packages/x-tree-view-pro/package.json index 7c206cb61686..df1cc086a5fe 100644 --- a/packages/x-tree-view-pro/package.json +++ b/packages/x-tree-view-pro/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-tree-view-pro", - "version": "7.1.1", + "version": "7.2.0", "private": true, "description": "The Pro plan edition of the Tree View components (MUI X).", "author": "MUI Team", @@ -47,8 +47,8 @@ "@mui/base": "^5.0.0-beta.40", "@mui/system": "^5.15.14", "@mui/utils": "^5.15.14", - "@mui/x-license": "7.1.1", - "@mui/x-tree-view": "7.1.1", + "@mui/x-license": "7.2.0", + "@mui/x-tree-view": "7.2.0", "@types/react-transition-group": "^4.4.10", "clsx": "^2.1.0", "prop-types": "^15.8.1", diff --git a/packages/x-tree-view/package.json b/packages/x-tree-view/package.json index b3b3f32cccbb..ffc06808a4cd 100644 --- a/packages/x-tree-view/package.json +++ b/packages/x-tree-view/package.json @@ -1,6 +1,6 @@ { "name": "@mui/x-tree-view", - "version": "7.1.1", + "version": "7.2.0", "description": "The community edition of the Tree View components (MUI X).", "author": "MUI Team", "main": "src/index.ts", diff --git a/scripts/x-charts.exports.json b/scripts/x-charts.exports.json index 9e48f1f55049..a58bf1eee63c 100644 --- a/scripts/x-charts.exports.json +++ b/scripts/x-charts.exports.json @@ -107,7 +107,7 @@ { "name": "cheerfulFiestaPaletteDark", "kind": "Variable" }, { "name": "cheerfulFiestaPaletteLight", "kind": "Variable" }, { "name": "ComputedPieRadius", "kind": "Interface" }, - { "name": "ContinuouseScaleName", "kind": "TypeAlias" }, + { "name": "ContinuousScaleName", "kind": "TypeAlias" }, { "name": "CurveType", "kind": "TypeAlias" }, { "name": "DEFAULT_MARGINS", "kind": "Variable" }, { "name": "DEFAULT_X_AXIS_KEY", "kind": "Variable" }, @@ -149,12 +149,15 @@ { "name": "getMarkElementUtilityClass", "kind": "Function" }, { "name": "getPieArcLabelUtilityClass", "kind": "Function" }, { "name": "getPieArcUtilityClass", "kind": "Function" }, + { "name": "getPieCoordinates", "kind": "Function" }, { "name": "getReferenceLineUtilityClass", "kind": "Function" }, { "name": "getSeriesToDisplay", "kind": "Function" }, { "name": "getValueToPositionMapper", "kind": "Function" }, { "name": "HighlightElementClassKey", "kind": "TypeAlias" }, { "name": "HighlightOptions", "kind": "TypeAlias" }, { "name": "HighlightScope", "kind": "TypeAlias" }, + { "name": "isBarSeries", "kind": "Function" }, + { "name": "isDefaultizedBarSeries", "kind": "Function" }, { "name": "LayoutConfig", "kind": "TypeAlias" }, { "name": "legendClasses", "kind": "Variable" }, { "name": "LegendRendererProps", "kind": "Interface" }, @@ -254,8 +257,15 @@ { "name": "StackableSeriesType", "kind": "TypeAlias" }, { "name": "StackOffsetType", "kind": "TypeAlias" }, { "name": "StackOrderType", "kind": "TypeAlias" }, + { "name": "unstable_useBarSeries", "kind": "Function" }, + { "name": "unstable_useLineSeries", "kind": "Function" }, + { "name": "unstable_usePieSeries", "kind": "Function" }, + { "name": "unstable_useScatterSeries", "kind": "Function" }, + { "name": "unstable_useSeries", "kind": "Function" }, + { "name": "useChartId", "kind": "Function" }, { "name": "useDrawingArea", "kind": "Function" }, { "name": "useGaugeState", "kind": "Function" }, + { "name": "useSvgRef", "kind": "Function" }, { "name": "useXScale", "kind": "Function" }, { "name": "useYScale", "kind": "Function" } ] diff --git a/test/e2e/fixtures/DatePicker/BasicDesktopDatePickerV6.tsx b/test/e2e/fixtures/DatePicker/BasicClearableDesktopDatePicker.tsx similarity index 89% rename from test/e2e/fixtures/DatePicker/BasicDesktopDatePickerV6.tsx rename to test/e2e/fixtures/DatePicker/BasicClearableDesktopDatePicker.tsx index f94f56265f81..ccfe656d3c16 100644 --- a/test/e2e/fixtures/DatePicker/BasicDesktopDatePickerV6.tsx +++ b/test/e2e/fixtures/DatePicker/BasicClearableDesktopDatePicker.tsx @@ -3,7 +3,7 @@ import { DesktopDatePicker } from '@mui/x-date-pickers/DesktopDatePicker'; import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider'; import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs'; -export default function BasicDesktopDatePickerV6() { +export default function BasicClearableDesktopDatePicker() { return ( { // A simplified version of https://github.com/testing-library/dom-testing-library/blob/main/src/wait-for.js function waitFor(callback: () => Promise): Promise { return new Promise((resolve, reject) => { - let intervalId: NodeJS.Timer | null = null; - let timeoutId: NodeJS.Timer | null = null; + let intervalId: NodeJS.Timeout | null = null; + let timeoutId: NodeJS.Timeout | null = null; let lastError: any = null; function handleTimeout() { @@ -558,6 +558,23 @@ async function initializeEnvironment( ); }); + // assertion for: https://github.com/mui/mui-x/issues/12652 + it('should allow field editing after opening and closing the picker', async () => { + await renderFixture('DatePicker/BasicClearableDesktopDatePicker'); + // open picker + await page.getByRole('button').click(); + await page.waitForSelector('[role="dialog"]', { state: 'attached' }); + // close picker + await page.getByRole('button', { name: 'Choose date' }).click(); + await page.waitForSelector('[role="dialog"]', { state: 'detached' }); + + // click on the input to focus it + await page.getByRole('textbox').click(); + + // test that the input value is set after focus + expect(await page.getByRole('textbox').inputValue()).to.equal('MM/DD/YYYY'); + }); + it('should allow filling in a value and clearing a value', async () => { await renderFixture('DatePicker/BasicDesktopDatePicker'); @@ -648,7 +665,7 @@ async function initializeEnvironment( }); it('should focus the first field section after clearing a value in v6 input', async () => { - await renderFixture('DatePicker/BasicDesktopDatePickerV6'); + await renderFixture('DatePicker/BasicClearableDesktopDatePicker'); await page.getByRole('textbox').fill('2'); await page.getByRole('button', { name: 'Clear value' }).click(); diff --git a/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx b/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx index 24ef112fc206..d6ac1bcd576c 100644 --- a/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx +++ b/test/utils/pickers/describeValue/testPickerOpenCloseLifeCycle.tsx @@ -180,7 +180,9 @@ export const testPickerOpenCloseLifeCycle: DescribeValueTestSuite expect(onClose.callCount).to.equal(1); }); - it('should not call onClose or onAccept when selecting a date and `props.closeOnSelect` is false', () => { + it('should not call onClose or onAccept when selecting a date and `props.closeOnSelect` is false', function test() { + // increase the timeout of this test as it tends to sometimes fail on CI with `DesktopDateTimeRangePicker` or `MobileDateTimeRangePicker` + this.timeout(10000); const onChange = spy(); const onAccept = spy(); const onClose = spy();