diff --git a/docs/data/charts/composition/PocBaseAndMaterial.js b/docs/data/charts/composition/PocBaseAndMaterial.js new file mode 100644 index 0000000000000..bc952e645bbfd --- /dev/null +++ b/docs/data/charts/composition/PocBaseAndMaterial.js @@ -0,0 +1,5 @@ +import * as React from 'react'; + +export default function PocBaseAndMaterial() { + return
PocBaseAndMaterial
; +} diff --git a/docs/data/charts/composition/PocBaseAndMaterial.tsx b/docs/data/charts/composition/PocBaseAndMaterial.tsx new file mode 100644 index 0000000000000..442d1ad7446e6 --- /dev/null +++ b/docs/data/charts/composition/PocBaseAndMaterial.tsx @@ -0,0 +1,58 @@ +import * as React from 'react'; +import { PieChart as PieChartBase } from '@mui/x-charts-base'; +import { PieChart as PieChartMaterial } from '@mui/x-charts-material'; +import type { PieChartProps } from '@mui/x-charts/PieChart'; + +const data: PieChartProps = { + height: 300, + enableKeyboardNavigation: true, + series: [ + { + arcLabel: 'value', + arcLabelMinAngle: 10, + innerRadius: '70%', + data: [ + { value: 15, label: 'A' }, + { value: 20, label: 'B' }, + ], + }, + { + outerRadius: '70%', + innerRadius: '40%', + cx: '100%', + startAngle: 180, + arcLabel: 'value', + data: [ + { value: 15, label: 'D', color: 'rgb(135, 120, 255)' }, + { value: 25, label: 'E', color: 'rgb(160, 143, 255)' }, + { value: 35, label: 'F', color: 'rgb(185, 166, 255)' }, + ], + }, + { + outerRadius: '70%', + innerRadius: '40%', + cx: '0%', + endAngle: 180, + arcLabel: 'value', + data: [ + { value: 15, label: 'D', color: 'rgb(255, 194, 163)' }, + { value: 25, label: 'E', color: 'rgb(255, 186, 138)' }, + { value: 35, label: 'F', color: 'rgb(255, 177, 112)' }, + ], + }, + ], +}; + +export default function PocBaseAndMaterial() { + return ( +
+ + + + + + + +
+ ); +} diff --git a/docs/data/charts/composition/composition.md b/docs/data/charts/composition/composition.md index 20c374d0dc1c3..27e477bc988d1 100644 --- a/docs/data/charts/composition/composition.md +++ b/docs/data/charts/composition/composition.md @@ -269,3 +269,12 @@ This example demonstrates how to combine scatter and line plots to overlay a nor The bell curve is calculated based on the mean and standard deviation of the data. {{"demo": "BellCurveOverlay.js" }} + +### POC: Base and Material UI packages + +This example demonstrates the proposed architecture where charts functionality is split into two packages: + +- **@mui/x-charts-base**: Framework-agnostic core with inline styles and a custom theme system +- **@mui/x-charts-material**: Material UI integration layer using material components and theming + +{{"demo": "PocBaseAndMaterial.js" }} diff --git a/packages/x-charts-base/package.json b/packages/x-charts-base/package.json new file mode 100644 index 0000000000000..d8f2fd804a441 --- /dev/null +++ b/packages/x-charts-base/package.json @@ -0,0 +1,48 @@ +{ + "name": "@mui/x-charts-base", + "version": "0.0.1", + "description": "Framework-agnostic base charting library for MUI X Charts", + "author": "MUI Team", + "main": "src/index.ts", + "license": "MIT", + "private": true, + "bugs": { + "url": "https://github.com/mui/mui-x/issues" + }, + "homepage": "https://mui.com/x/react-charts/", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "scripts": { + "build": "pnpm build:modern && pnpm build:node && pnpm build:stable && pnpm build:types && pnpm build:copy-files", + "build:modern": "node ../../scripts/build.mjs modern", + "build:node": "node ../../scripts/build.mjs node", + "build:stable": "node ../../scripts/build.mjs stable", + "build:copy-files": "node ../../scripts/copyFiles.mjs", + "build:types": "node ../../scripts/buildTypes.mjs", + "typescript": "tsc -p tsconfig.json" + }, + "dependencies": { + "@babel/runtime": "catalog:", + "@mui/utils": "catalog:", + "@mui/material": "catalog:", + "@mui/x-charts-vendor": "workspace:^", + "@mui/x-internals": "workspace:^", + "@mui/x-charts": "workspace:^", + "clsx": "catalog:" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "devDependencies": {}, + "sideEffects": false, + "publishConfig": { + "access": "public", + "directory": "build" + }, + "engines": { + "node": ">=14.0.0" + } +} diff --git a/packages/x-charts-base/src/ChartsSurface/ChartsSurface.tsx b/packages/x-charts-base/src/ChartsSurface/ChartsSurface.tsx new file mode 100644 index 0000000000000..30df8d2635ada --- /dev/null +++ b/packages/x-charts-base/src/ChartsSurface/ChartsSurface.tsx @@ -0,0 +1,74 @@ +'use client'; +import * as React from 'react'; +import useForkRef from '@mui/utils/useForkRef'; +import { useSvgRef } from '@mui/x-charts'; +import { ChartsAxesGradients } from '@mui/x-charts/internals/components/ChartsAxesGradients'; +import { + selectorChartSvgWidth, + selectorChartSvgHeight, + selectorChartPropsWidth, + selectorChartPropsHeight, +} from '@mui/x-charts/internals/plugins/corePlugins/useChartDimensions'; +import { + selectorChartsIsKeyboardNavigationEnabled, + selectorChartsHasFocusedItem, +} from '@mui/x-charts/internals/plugins/featurePlugins/useChartKeyboardNavigation'; +import { useSelector } from '@mui/x-charts/internals/store/useSelector'; +import clsx from 'clsx'; +import { useStore } from '@mui/x-charts/internals/store/useStore'; + +export interface ChartsSurfaceProps + extends Omit< + React.SVGProps, + 'id' | 'children' | 'className' | 'height' | 'width' | 'cx' | 'cy' | 'viewBox' | 'color' | 'ref' + > { + className?: string; + title?: string; + desc?: string; + children?: React.ReactNode; +} + +const ChartsSurface = React.forwardRef(function ChartsSurface( + inProps: ChartsSurfaceProps, + ref: React.Ref, +) { + const store = useStore(); + + const svgWidth = useSelector(store, selectorChartSvgWidth); + const svgHeight = useSelector(store, selectorChartSvgHeight); + + const propsWidth = useSelector(store, selectorChartPropsWidth); + const propsHeight = useSelector(store, selectorChartPropsHeight); + const isKeyboardNavigationEnabled = useSelector(store, selectorChartsIsKeyboardNavigationEnabled); + const hasFocusedItem = useSelector(store, selectorChartsHasFocusedItem); + const svgRef = useSvgRef(); + const handleRef = useForkRef(svgRef, ref); + + const { children, className, title, desc, ...other } = inProps; + + const hasIntrinsicSize = svgHeight > 0 && svgWidth > 0; + + const styles = { + ...(propsWidth ? { '--chart-surface-width': `${propsWidth}px` } : {}), + ...(propsHeight ? { '--chart-surface-height': `${propsHeight}px` } : {}), + } as React.CSSProperties; + + return ( + + {title && {title}} + {desc && {desc}} + + {hasIntrinsicSize && children} + + ); +}); + +export { ChartsSurface }; diff --git a/packages/x-charts-base/src/ChartsSurface/index.ts b/packages/x-charts-base/src/ChartsSurface/index.ts new file mode 100644 index 0000000000000..488a89f2528fb --- /dev/null +++ b/packages/x-charts-base/src/ChartsSurface/index.ts @@ -0,0 +1 @@ +export * from './ChartsSurface'; diff --git a/packages/x-charts-base/src/FakeCss/index.ts b/packages/x-charts-base/src/FakeCss/index.ts new file mode 100644 index 0000000000000..2dd5959b930f5 --- /dev/null +++ b/packages/x-charts-base/src/FakeCss/index.ts @@ -0,0 +1,49 @@ +import { styled } from '@mui/material'; + +export const FakeCss = styled('div')(({ theme }) => ({ + '& .ChartsSurface-root': { + height: 'var(--chart-surface-height, 100%)', + width: 'var(--chart-surface-width, 100%)', + display: 'flex', + position: 'relative', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + overflow: 'hidden', + /* This prevents default touch actions when using the svg on mobile devices. + For example, prevent page scroll & zoom. */ + touchAction: 'pan-y', + userSelect: 'none', + gridArea: 'chart', + }, + + '& .ChartsSurface-root:focus': { + outline: 'none', // By default don't show focus on the SVG container + }, + + '& .ChartsSurface-root:focus-visible': { + /* Show focus outline on the SVG container only when using keyboard navigation */ + outline: 'var(--mui-palette-text-primary) solid 2px', + }, + + "& .ChartsSurface-root:focus-visible[data-has-focused-item='true']": { + /* But not if the chart has a focused children item */ + outline: 'none', + }, + + "& .ChartsSurface-root [data-focused='true']": { + outline: 'var(--mui-palette-text-primary) solid 2px', + }, + + // @media doesn't work + '@media (prefers-color-scheme: dark)': {}, + + '--Primary-color': 'orange', + + '--PieChart-arc-stroke': 'white', + ...theme.applyStyles('dark', { + '--PieChart-arc-stroke': 'black', + }), + + '--FocusIndicator-color': 'var(--Primary-color)', +})); diff --git a/packages/x-charts-base/src/PieChart/PieArc.tsx b/packages/x-charts-base/src/PieChart/PieArc.tsx new file mode 100644 index 0000000000000..3174f244a3884 --- /dev/null +++ b/packages/x-charts-base/src/PieChart/PieArc.tsx @@ -0,0 +1,95 @@ +'use client'; +import { type PieItemId } from '@mui/x-charts'; +import { useInteractionItemProps } from '@mui/x-charts/hooks/useInteractionItemProps'; +import clsx from 'clsx'; +import * as React from 'react'; +import { arc as d3Arc } from '@mui/x-charts-vendor/d3-shape'; + +export type PieArcProps = Omit, 'ref' | 'id'> & { + cornerRadius: number; + endAngle: number; + innerRadius: number; + onClick?: (event: React.MouseEvent) => void; + outerRadius: number; + paddingAngle: number; + startAngle: number; + /** + * If `true`, the default event handlers are disabled. + * Those are used, for example, to display a tooltip or highlight the arc on hover. + */ + skipInteraction?: boolean; + id: PieItemId; + dataIndex: number; + color: string; + isFaded: boolean; + isHighlighted: boolean; + isFocused: boolean; + stroke?: string; +}; + +const PieArc = React.forwardRef(function PieArc(props, ref) { + const { + className, + color, + dataIndex, + id, + isFaded, + isHighlighted, + isFocused, + onClick, + cornerRadius, + startAngle, + endAngle, + innerRadius, + outerRadius, + paddingAngle, + skipInteraction, + stroke, + ...other + } = props; + + const interactionProps = useInteractionItemProps( + { type: 'pie', seriesId: id, dataIndex }, + skipInteraction, + ); + const p = { + cornerRadius, + startAngle, + endAngle, + innerRadius, + outerRadius, + paddingAngle, + }; + + const d = d3Arc().cornerRadius(p.cornerRadius)({ + padAngle: p.paddingAngle, + innerRadius: p.innerRadius, + outerRadius: p.outerRadius, + startAngle: p.startAngle, + endAngle: p.endAngle, + })!; + const visibility = p.startAngle === p.endAngle ? ('hidden' as const) : ('visible' as const); + + return ( + + ); +}); + +export { PieArc }; diff --git a/packages/x-charts-base/src/PieChart/PieArcLabel.tsx b/packages/x-charts-base/src/PieChart/PieArcLabel.tsx new file mode 100644 index 0000000000000..5f2d8c2082f8e --- /dev/null +++ b/packages/x-charts-base/src/PieChart/PieArcLabel.tsx @@ -0,0 +1,66 @@ +'use client'; +import * as React from 'react'; +import { arc as d3Arc } from '@mui/x-charts-vendor/d3-shape'; +import { type PieItemId } from '@mui/x-charts'; + +export type PieArcLabelProps = Omit, 'ref' | 'color' | 'id'> & { + startAngle: number; + endAngle: number; + innerRadius: number; + outerRadius: number; + arcLabelRadius: number; + cornerRadius: number; + paddingAngle: number; + formattedArcLabel?: string | null; + id: PieItemId; + color: string; + isFaded: boolean; + isHighlighted: boolean; +}; + +const PieArcLabel = React.forwardRef( + function PieArcLabel(props, ref) { + const { + id, + color, + startAngle, + endAngle, + paddingAngle, + arcLabelRadius, + innerRadius, + outerRadius, + cornerRadius, + formattedArcLabel, + isHighlighted, + isFaded, + style, + ...other + } = props; + + const [x, y] = d3Arc().cornerRadius(cornerRadius).centroid({ + padAngle: paddingAngle, + startAngle, + endAngle, + innerRadius, + outerRadius, + }); + + return ( + + {formattedArcLabel} + + ); + }, +); + +export { PieArcLabel }; diff --git a/packages/x-charts-base/src/PieChart/PieArcLabelPlot.tsx b/packages/x-charts-base/src/PieChart/PieArcLabelPlot.tsx new file mode 100644 index 0000000000000..6944ac4d814ab --- /dev/null +++ b/packages/x-charts-base/src/PieChart/PieArcLabelPlot.tsx @@ -0,0 +1,118 @@ +'use client'; +import { + type PieSeriesType, + type DefaultizedPieValueType, + type DefaultizedPieSeriesType, + type ComputedPieRadius, +} from '@mui/x-charts'; +import { getLabel } from '@mui/x-charts/internals/getLabel'; +import { useTransformData } from '@mui/x-charts/PieChart/dataTransform/useTransformData'; +import { PieArcLabel } from './PieArcLabel'; + +const RATIO = 180 / Math.PI; + +function getItemLabel( + arcLabel: PieSeriesType['arcLabel'], + arcLabelMinAngle: number, + item: DefaultizedPieValueType, +) { + if (!arcLabel) { + return null; + } + const angle = (item.endAngle - item.startAngle) * RATIO; + if (angle < arcLabelMinAngle) { + return null; + } + + switch (arcLabel) { + case 'label': + return getLabel(item.label, 'arc'); + case 'value': + return item.value?.toString(); + case 'formattedValue': + return item.formattedValue; + default: + return arcLabel({ + ...item, + label: getLabel(item.label, 'arc'), + }); + } +} + +export interface PieArcLabelPlotProps + extends Pick< + DefaultizedPieSeriesType, + | 'data' + | 'faded' + | 'highlighted' + | 'cornerRadius' + | 'paddingAngle' + | 'arcLabel' + | 'arcLabelMinAngle' + | 'id' + >, + Pick, 'transform'>, + ComputedPieRadius { + /** + * Override the arc attributes when it is faded. + * @default { additionalRadius: -5 } + */ + faded?: DefaultizedPieSeriesType['faded']; +} + +function PieArcLabelPlot(props: PieArcLabelPlotProps) { + const { + arcLabel, + arcLabelMinAngle = 0, + arcLabelRadius, + cornerRadius = 0, + data, + faded = { additionalRadius: -5 }, + highlighted, + id, + innerRadius, + outerRadius, + paddingAngle = 0, + ...other + } = props; + + const transformedData = useTransformData({ + innerRadius, + outerRadius, + arcLabelRadius, + cornerRadius, + paddingAngle, + id, + highlighted, + faded, + data, + }); + + if (data.length === 0) { + return null; + } + + return ( + + {transformedData.map((item) => ( + + ))} + + ); +} + +export { PieArcLabelPlot }; diff --git a/packages/x-charts-base/src/PieChart/PieArcPlot.tsx b/packages/x-charts-base/src/PieChart/PieArcPlot.tsx new file mode 100644 index 0000000000000..4227e0024bb00 --- /dev/null +++ b/packages/x-charts-base/src/PieChart/PieArcPlot.tsx @@ -0,0 +1,125 @@ +'use client'; +import { + type DefaultizedPieSeriesType, + type ComputedPieRadius, + type PieItemIdentifier, + type DefaultizedPieValueType, +} from '@mui/x-charts'; +import { useFocusedItem } from '@mui/x-charts/hooks/useFocusedItem'; +import { useTransformData } from '@mui/x-charts/PieChart/dataTransform/useTransformData'; +import * as React from 'react'; +import { PieArc } from './PieArc'; + +export interface PieArcPlotProps + extends Pick< + DefaultizedPieSeriesType, + 'data' | 'faded' | 'highlighted' | 'cornerRadius' | 'paddingAngle' | 'id' + >, + Pick, 'transform'>, + ComputedPieRadius { + /** + * Override the arc attributes when it is faded. + * @default { additionalRadius: -5 } + */ + faded?: DefaultizedPieSeriesType['faded']; + /** + * Callback fired when a pie item is clicked. + * @param {React.MouseEvent} event The event source of the callback. + * @param {PieItemIdentifier} pieItemIdentifier The pie item identifier. + * @param {DefaultizedPieValueType} item The pie item. + */ + onItemClick?: ( + event: React.MouseEvent, + pieItemIdentifier: PieItemIdentifier, + item: DefaultizedPieValueType, + ) => void; +} + +function PieArcPlot(props: PieArcPlotProps) { + const { + innerRadius = 0, + outerRadius, + cornerRadius = 0, + paddingAngle = 0, + id, + highlighted, + faded = { additionalRadius: -5 }, + data, + onItemClick, + ...other + } = props; + + const transformedData = useTransformData({ + innerRadius, + outerRadius, + cornerRadius, + paddingAngle, + id, + highlighted, + faded, + data, + }); + + const { dataIndex, seriesId, seriesType } = useFocusedItem() ?? {}; + const focusedItem = + dataIndex !== undefined && seriesId === id && seriesType === 'pie' + ? transformedData[dataIndex] + : null; + + if (data.length === 0) { + return null; + } + + return ( + + {transformedData.map((item, index) => ( + { + onItemClick(event, { type: 'pie', seriesId: id, dataIndex: index }, item); + }) + } + /> + ))} + {/* Render the focus indicator last, so it can align nicely over all arcs */} + {focusedItem && ( + + )} + + ); +} + +export { PieArcPlot }; diff --git a/packages/x-charts-base/src/PieChart/PieChart.hooks.ts b/packages/x-charts-base/src/PieChart/PieChart.hooks.ts new file mode 100644 index 0000000000000..b42951fadc70f --- /dev/null +++ b/packages/x-charts-base/src/PieChart/PieChart.hooks.ts @@ -0,0 +1,75 @@ +import { getPieCoordinates } from '@mui/x-charts'; +import { getPercentageValue } from '@mui/x-charts/internals/getPercentageValue'; +import { selectorChartDrawingArea } from '@mui/x-charts/internals/plugins/corePlugins/useChartDimensions'; +import type { ProcessedSeries } from '@mui/x-charts/internals/plugins/corePlugins/useChartSeries'; +import { selectorAllSeriesOfType } from '@mui/x-charts/internals/seriesSelectorOfType'; +import { useSelector } from '@mui/x-charts/internals/store/useSelector'; +import { useStore } from '@mui/x-charts/internals/store/useStore'; +import { createSelector } from '@mui/x-internals/store'; + +const pieSelector = (store: any) => selectorAllSeriesOfType(store, 'pie') as ProcessedSeries['pie']; + +const selectorPieSeriesData = createSelector( + pieSelector, + selectorChartDrawingArea, + (pieSeries, drawingArea) => { + if (!pieSeries) { + return null; + } + + const { width, height, left, top } = drawingArea; + + const { series, seriesOrder } = pieSeries; + + return seriesOrder.map((seriesId) => { + const { + innerRadius: innerRadiusParam, + outerRadius: outerRadiusParam, + arcLabelRadius: arcLabelRadiusParam, + cornerRadius, + paddingAngle, + arcLabel, + arcLabelMinAngle, + data, + highlighted, + faded, + cx: cxParam, + cy: cyParam, + } = 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 arcLabelRadius = + arcLabelRadiusParam === undefined + ? (outerRadius + innerRadius) / 2 + : getPercentageValue(arcLabelRadiusParam, availableRadius); + return { + innerRadius, + outerRadius, + cornerRadius, + paddingAngle, + id: seriesId, + data, + highlighted, + faded, + arcLabelRadius, + arcLabel, + arcLabelMinAngle, + availableRadius, + cx, + cy, + transform: `translate(${left + cx}, ${top + cy})`, + }; + }); + }, +); + +export const usePiePlotData = () => { + const store = useStore(); + return useSelector(store, selectorPieSeriesData); +}; diff --git a/packages/x-charts-base/src/PieChart/PieChart.parts.ts b/packages/x-charts-base/src/PieChart/PieChart.parts.ts new file mode 100644 index 0000000000000..dc78a9d73a85b --- /dev/null +++ b/packages/x-charts-base/src/PieChart/PieChart.parts.ts @@ -0,0 +1 @@ +export * as PieChart from './PieChart'; diff --git a/packages/x-charts-base/src/PieChart/PieChart.tsx b/packages/x-charts-base/src/PieChart/PieChart.tsx new file mode 100644 index 0000000000000..195f42564b49d --- /dev/null +++ b/packages/x-charts-base/src/PieChart/PieChart.tsx @@ -0,0 +1,97 @@ +import { + ChartDataProvider, + PIE_CHART_PLUGINS, + type ChartDataProviderProps, + type PieChartPluginSignatures, + type PiePlotProps, + type PieSeriesType, + type PieValueType, +} from '@mui/x-charts'; +import type { MakeOptional } from '@mui/x-internals/types'; +import type { ChartsOverlayProps } from '@mui/x-charts/ChartsOverlay'; +import * as React from 'react'; +import { defaultizeMargin } from '@mui/x-charts/internals/defaultizeMargin'; +import { DEFAULT_PIE_CHART_MARGIN } from '@mui/x-charts/internals/constants'; +import { useChartContainerProps } from '@mui/x-charts/ChartContainer/useChartContainerProps'; +import { FakeCss } from '../FakeCss'; +import { ChartsSurface } from '../ChartsSurface'; +import { PiePlot } from './PiePlot'; +import { PieLabelPlot } from './PieLabelPlot'; + +export type PieSeries = MakeOptional>, 'type'>; +export interface PieRootProps + extends Omit< + ChartDataProviderProps<'pie', PieChartPluginSignatures>, + 'series' | 'slots' | 'slotProps' | 'experimentalFeatures' + >, + Omit { + /** + * The series to display in the pie chart. + * An array of [[PieSeries]] objects. + */ + series: Readonly; + /** + * If `true`, the legend is not rendered. + */ + hideLegend?: boolean; + /** + * Callback fired when a pie arc is clicked. + */ + onItemClick?: PiePlotProps['onItemClick']; + /** + * If true, shows the default chart toolbar. + * @default false + */ + showToolbar?: boolean; +} + +const PieRoot = React.forwardRef(function PieChart( + props: PieRootProps, + ref: React.Ref, +) { + const { + series, + width, + height, + margin: marginProps, + colors, + skipAnimation, + hideLegend, + children, + onItemClick, + loading, + highlightedItem, + onHighlightChange, + showToolbar, + ...other + } = props; + const margin = defaultizeMargin(marginProps, DEFAULT_PIE_CHART_MARGIN); + + const { chartDataProviderProps } = useChartContainerProps<'pie', PieChartPluginSignatures>( + { + ...other, + series: series.map((s) => ({ type: 'pie', ...s })), + width, + height, + margin, + colors, + highlightedItem, + onHighlightChange, + skipAnimation, + plugins: PIE_CHART_PLUGINS, + }, + ref, + ); + + return ( + {...chartDataProviderProps}> + {children} + + ); +}); + +// We could use `ChartsSurface` directly, but I think we could propose a different pattern, like if you want a single chart, then +// you can use `PieChart.Surface`, but if you want composition, we could use like `Composition.Surface`, technically they are the same though. +const PieSurface = ChartsSurface; + +export { PieRoot as Root, PieSurface as Surface, PiePlot as Plot, PieLabelPlot as LabelPlot }; diff --git a/packages/x-charts-base/src/PieChart/PieLabelPlot.tsx b/packages/x-charts-base/src/PieChart/PieLabelPlot.tsx new file mode 100644 index 0000000000000..d19d1ab86fa5c --- /dev/null +++ b/packages/x-charts-base/src/PieChart/PieLabelPlot.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { PieArcLabelPlot } from './PieArcLabelPlot'; +import { usePiePlotData } from './PieChart.hooks'; + +function PieLabelPlot() { + const plotData = usePiePlotData(); + + return plotData?.map((seriesData) => { + const { + innerRadius, + outerRadius, + cornerRadius, + paddingAngle, + data, + availableRadius, + arcLabelRadius, + id, + arcLabel, + arcLabelMinAngle, + transform, + } = seriesData; + + return ( + + ); + }); +} + +export { PieLabelPlot }; diff --git a/packages/x-charts-base/src/PieChart/PiePlot.tsx b/packages/x-charts-base/src/PieChart/PiePlot.tsx new file mode 100644 index 0000000000000..0bdce1f62e62b --- /dev/null +++ b/packages/x-charts-base/src/PieChart/PiePlot.tsx @@ -0,0 +1,43 @@ +'use client'; + +import { type PieArcPlotProps } from '@mui/x-charts'; +import { PieArcPlot } from './PieArcPlot'; +import { usePiePlotData } from './PieChart.hooks'; + +export interface PiePlotProps extends Pick {} + +function PiePlot(props: PiePlotProps) { + const { onItemClick } = props; + const plotData = usePiePlotData(); + + return plotData?.map((seriesData) => { + const { + innerRadius, + outerRadius, + cornerRadius, + paddingAngle, + data, + highlighted, + faded, + id, + transform, + } = seriesData; + + return ( + + ); + }); +} + +export { PiePlot }; diff --git a/packages/x-charts-base/src/PieChart/index.ts b/packages/x-charts-base/src/PieChart/index.ts new file mode 100644 index 0000000000000..2f0fc927e8104 --- /dev/null +++ b/packages/x-charts-base/src/PieChart/index.ts @@ -0,0 +1 @@ +export * from './PieChart.parts'; diff --git a/packages/x-charts-base/src/index.ts b/packages/x-charts-base/src/index.ts new file mode 100644 index 0000000000000..52ee2cdcbd0d8 --- /dev/null +++ b/packages/x-charts-base/src/index.ts @@ -0,0 +1,2 @@ +export * from './PieChart'; +export * from './ChartsSurface'; diff --git a/packages/x-charts-base/tsconfig.json b/packages/x-charts-base/tsconfig.json new file mode 100644 index 0000000000000..d4efb5ec64421 --- /dev/null +++ b/packages/x-charts-base/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["node"] + }, + "include": ["src/**/*"] +} diff --git a/packages/x-charts-material/package.json b/packages/x-charts-material/package.json new file mode 100644 index 0000000000000..1bb7b790c366c --- /dev/null +++ b/packages/x-charts-material/package.json @@ -0,0 +1,48 @@ +{ + "name": "@mui/x-charts-material", + "version": "0.0.1", + "description": "Material UI integration for @mui/x-charts-base", + "author": "MUI Team", + "main": "src/index.ts", + "license": "MIT", + "private": true, + "bugs": { + "url": "https://github.com/mui/mui-x/issues" + }, + "homepage": "https://mui.com/x/react-charts/", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "scripts": { + "build": "pnpm build:modern && pnpm build:node && pnpm build:stable && pnpm build:types && pnpm build:copy-files", + "build:modern": "node ../../scripts/build.mjs modern", + "build:node": "node ../../scripts/build.mjs node", + "build:stable": "node ../../scripts/build.mjs stable", + "build:copy-files": "node ../../scripts/copyFiles.mjs", + "build:types": "node ../../scripts/buildTypes.mjs", + "typescript": "tsc -p tsconfig.json" + }, + "dependencies": { + "@babel/runtime": "catalog:", + "@mui/material": "catalog:", + "@mui/utils": "catalog:", + "@mui/x-charts-base": "workspace:^", + "@mui/x-internals": "workspace:^", + "clsx": "catalog:" + }, + "peerDependencies": { + "@emotion/react": "^11.13.3", + "@emotion/styled": "^11.13.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "sideEffects": false, + "publishConfig": { + "access": "public", + "directory": "build" + }, + "engines": { + "node": ">=14.0.0" + } +} diff --git a/packages/x-charts-material/src/ChartsSurface/ChartsSurface.tsx b/packages/x-charts-material/src/ChartsSurface/ChartsSurface.tsx new file mode 100644 index 0000000000000..945b190e416a8 --- /dev/null +++ b/packages/x-charts-material/src/ChartsSurface/ChartsSurface.tsx @@ -0,0 +1,4 @@ +import { styled } from '@mui/material/styles'; +import { ChartsSurface as BaseSurface } from '@mui/x-charts-base/ChartsSurface'; + +export const ChartsSurface = styled(BaseSurface)(); diff --git a/packages/x-charts-material/src/ChartsTooltip/ChartsTooltip.tsx b/packages/x-charts-material/src/ChartsTooltip/ChartsTooltip.tsx new file mode 100644 index 0000000000000..3656aee74f3be --- /dev/null +++ b/packages/x-charts-material/src/ChartsTooltip/ChartsTooltip.tsx @@ -0,0 +1,12 @@ +'use client'; +import { + // This is coming from x-charts but would be in this package for simplicity + ChartsTooltip as MaterialChartsTooltip, + type ChartsTooltipProps, +} from '@mui/x-charts/ChartsTooltip'; + +function ChartsTooltip(props: ChartsTooltipProps) { + return ; +} + +export { ChartsTooltip }; diff --git a/packages/x-charts-material/src/ChartsTooltip/index.ts b/packages/x-charts-material/src/ChartsTooltip/index.ts new file mode 100644 index 0000000000000..392e33dcbde2b --- /dev/null +++ b/packages/x-charts-material/src/ChartsTooltip/index.ts @@ -0,0 +1 @@ +export * from './ChartsTooltip'; diff --git a/packages/x-charts-material/src/FakeCss/index.ts b/packages/x-charts-material/src/FakeCss/index.ts new file mode 100644 index 0000000000000..6db00ca4f4957 --- /dev/null +++ b/packages/x-charts-material/src/FakeCss/index.ts @@ -0,0 +1,9 @@ +import { styled } from '@mui/material'; + +export const FakeCss = styled('div')(({ theme }) => ({ + '--Primary-color': theme.palette.primary.main, + + '--PieChart-arc-stroke': theme.palette.background.paper, + + '--FocusIndicator-color': 'var(--Primary-color)', +})); diff --git a/packages/x-charts-material/src/PieChart/PieChart.tsx b/packages/x-charts-material/src/PieChart/PieChart.tsx new file mode 100644 index 0000000000000..520ca6b8f937e --- /dev/null +++ b/packages/x-charts-material/src/PieChart/PieChart.tsx @@ -0,0 +1,25 @@ +import * as React from 'react'; +import { PieChartProps } from '@mui/x-charts'; +import { PieChart as PieChartBase } from '@mui/x-charts-base'; +import { ChartsSurface } from '../ChartsSurface/ChartsSurface'; +import { ChartsTooltip } from '../ChartsTooltip'; +import { FakeCss } from '../FakeCss'; + +export const PieChart = React.forwardRef(function PieChart( + props: PieChartProps, + ref: React.Ref, +) { + const { title, desc, sx, ...other } = props; + + return ( + + + + + + + + + + ); +}); diff --git a/packages/x-charts-material/src/PieChart/index.ts b/packages/x-charts-material/src/PieChart/index.ts new file mode 100644 index 0000000000000..4ffd151ee3038 --- /dev/null +++ b/packages/x-charts-material/src/PieChart/index.ts @@ -0,0 +1 @@ +export * from './PieChart'; diff --git a/packages/x-charts-material/src/index.ts b/packages/x-charts-material/src/index.ts new file mode 100644 index 0000000000000..4ffd151ee3038 --- /dev/null +++ b/packages/x-charts-material/src/index.ts @@ -0,0 +1 @@ +export * from './PieChart'; diff --git a/packages/x-charts-material/tsconfig.json b/packages/x-charts-material/tsconfig.json new file mode 100644 index 0000000000000..d4efb5ec64421 --- /dev/null +++ b/packages/x-charts-material/tsconfig.json @@ -0,0 +1,7 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "types": ["node"] + }, + "include": ["src/**/*"] +} diff --git a/packages/x-charts/src/internals/seriesSelectorOfType.ts b/packages/x-charts/src/internals/seriesSelectorOfType.ts index df4b37c42dd0e..70aa7c5df18e1 100644 --- a/packages/x-charts/src/internals/seriesSelectorOfType.ts +++ b/packages/x-charts/src/internals/seriesSelectorOfType.ts @@ -9,8 +9,7 @@ import { useSelector } from './store/useSelector'; export const selectorAllSeriesOfType = createSelector( selectorChartSeriesProcessed, - (processedSeries: ProcessedSeries, seriesType: T) => - processedSeries[seriesType], + (processedSeries, seriesType: keyof ChartsSeriesConfig) => processedSeries[seriesType], ); export const selectorSeriesOfType = createSelectorMemoized( @@ -32,7 +31,7 @@ export const selectorSeriesOfType = createSelectorMemoized( return processedSeries[seriesType]?.series?.[ids]; } - const result: ChartSeriesDefaultized[] = []; + const result: ChartSeriesDefaultized[] = []; const failedIds: SeriesId[] = []; for (const id of ids) { const series = processedSeries[seriesType]?.series?.[id]; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e3876a5c19ef..42f98cad096c7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -912,6 +912,71 @@ importers: version: 6.1.2 publishDirectory: build + packages/x-charts-base: + dependencies: + '@babel/runtime': + specifier: 'catalog:' + version: 7.28.4 + '@mui/material': + specifier: 'catalog:' + version: 7.3.5(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.0))(@types/react@19.2.7)(react@19.2.0))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mui/utils': + specifier: 'catalog:' + version: 7.3.5(@types/react@19.2.7)(react@19.2.0) + '@mui/x-charts': + specifier: workspace:^ + version: link:../x-charts/build + '@mui/x-charts-vendor': + specifier: workspace:^ + version: link:../x-charts-vendor + '@mui/x-internals': + specifier: workspace:^ + version: link:../x-internals/build + clsx: + specifier: 'catalog:' + version: 2.1.1 + react: + specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 + version: 19.2.0 + react-dom: + specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 + version: 19.2.0(react@19.2.0) + publishDirectory: build + + packages/x-charts-material: + dependencies: + '@babel/runtime': + specifier: 'catalog:' + version: 7.28.4 + '@emotion/react': + specifier: ^11.13.3 + version: 11.14.0(@types/react@19.2.7)(react@19.2.0) + '@emotion/styled': + specifier: ^11.13.0 + version: 11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.0))(@types/react@19.2.7)(react@19.2.0) + '@mui/material': + specifier: 'catalog:' + version: 7.3.5(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.0))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.7)(react@19.2.0))(@types/react@19.2.7)(react@19.2.0))(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0) + '@mui/utils': + specifier: 'catalog:' + version: 7.3.5(@types/react@19.2.7)(react@19.2.0) + '@mui/x-charts-base': + specifier: workspace:^ + version: link:../x-charts-base/build + '@mui/x-internals': + specifier: workspace:^ + version: link:../x-internals/build + clsx: + specifier: 'catalog:' + version: 2.1.1 + react: + specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 + version: 19.2.0 + react-dom: + specifier: ^17.0.0 || ^18.0.0 || ^19.0.0 + version: 19.2.0(react@19.2.0) + publishDirectory: build + packages/x-charts-premium: dependencies: '@babel/runtime': diff --git a/tsconfig.json b/tsconfig.json index 16252f2118fa2..c209febac8558 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,6 +27,10 @@ "@mui/x-date-pickers-pro/*": ["./packages/x-date-pickers-pro/src/*"], "@mui/x-charts": ["./packages/x-charts/src"], "@mui/x-charts/*": ["./packages/x-charts/src/*"], + "@mui/x-charts-base": ["./packages/x-charts-base/src"], + "@mui/x-charts-base/*": ["./packages/x-charts-base/src/*"], + "@mui/x-charts-material": ["./packages/x-charts-material/src"], + "@mui/x-charts-material/*": ["./packages/x-charts-material/src/*"], "@mui/x-charts-pro": ["./packages/x-charts-pro/src"], "@mui/x-charts-pro/*": ["./packages/x-charts-pro/src/*"], "@mui/x-charts-premium": ["./packages/x-charts-premium/src"],