Skip to content

Commit

Permalink
first working draft
Browse files Browse the repository at this point in the history
  • Loading branch information
alexfauquette committed Mar 22, 2024
1 parent acea8ee commit f734ecb
Show file tree
Hide file tree
Showing 17 changed files with 522 additions and 41 deletions.
32 changes: 30 additions & 2 deletions docs/data/charts/bar-demo/TinyBarChart.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import * as React from 'react';
import { ChartContainer, BarPlot } from '@mui/x-charts';
import { ChartContainer, BarPlot, ChartsTooltip } from '@mui/x-charts';

const uData = [4000, 3000, 2000, 2780, 1890, 2390, 3490];
const xLabels = [
Expand All @@ -18,9 +18,37 @@ export default function TinyBarChart() {
width={500}
height={300}
series={[{ data: uData, label: 'uv', type: 'bar' }]}
xAxis={[{ scaleType: 'band', data: xLabels }]}
yAxis={[
{
colorMap: {
type: 'continuous',
min: 2000,
max: 5000,
color: ['blue', 'red'],
},
},
]}
xAxis={[
{
scaleType: 'band',
data: xLabels,
colorMap: {
type: 'ordinal',
colors: [
'#ffff00',
'#ffff00',
'#f0f',
'#ffff00',
'#ffff00',
'#ffff00',
'#ffff00',
],
},
},
]}
>
<BarPlot />
<ChartsTooltip trigger="item" />
</ChartContainer>
);
}
34 changes: 28 additions & 6 deletions packages/x-charts/src/BarChart/BarPlot.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -98,7 +99,8 @@ const useAggregatedData = (): CompletedBarData[] => {
const yAxisConfig = yAxis[yAxisKey];

const verticalLayout = series[seriesId].layout === 'vertical';
let baseScaleConfig;
let baseScaleConfig: AxisDefaultized<'band'>;
// let valueScaleConfig: AxisDefaultized<ContinuouseScaleName>;
if (verticalLayout) {
if (!isBandScaleConfig(xAxisConfig)) {
throw new Error(
Expand All @@ -118,7 +120,17 @@ 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 continuouse type to display the bar series of id "${seriesId}".`,
);
}
// valueScaleConfig = yAxisConfig as AxisDefaultized<ContinuouseScaleName>;
} else {
if (!isBandScaleConfig(yAxisConfig)) {
throw new Error(
Expand All @@ -139,12 +151,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 continuouse 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({
Expand All @@ -154,7 +176,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)!));
Expand All @@ -176,7 +198,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,
};
});
Expand Down
36 changes: 36 additions & 0 deletions packages/x-charts/src/BarChart/getColor.ts
Original file line number Diff line number Diff line change
@@ -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;
}
18 changes: 15 additions & 3 deletions packages/x-charts/src/ChartsTooltip/ChartsAxisTooltipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChartSeriesType> & {
getColor: (dataIndex: number) => string;
};

export type ChartsAxisContentProps = {
/**
Expand All @@ -19,7 +24,7 @@ export type ChartsAxisContentProps = {
/**
* The series linked to the triggered axis.
*/
series: ChartSeriesDefaultized<ChartSeriesType>[];
series: ChartSeriesDefaultizedWithColorGetter[];
/**
* The properties of the triggered axis.
*/
Expand Down Expand Up @@ -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];
Expand Down
24 changes: 24 additions & 0 deletions packages/x-charts/src/ChartsTooltip/ChartsItemTooltipContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<T extends ChartSeriesType = ChartSeriesType> = {
/**
Expand All @@ -21,6 +23,12 @@ export type ChartsItemContentProps<T extends ChartSeriesType = ChartSeriesType>
* 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<Theme>;
};

Expand All @@ -37,6 +45,21 @@ function ChartsItemTooltipContent<T extends ChartSeriesType>(props: {
itemData.seriesId
] as ChartSeriesDefaultized<T>;

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,
Expand All @@ -46,6 +69,7 @@ function ChartsItemTooltipContent<T extends ChartSeriesType>(props: {
series,
sx,
classes,
getColor,
},
ownerState: {},
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,32 +38,34 @@ function DefaultChartsAxisTooltipContent(props: ChartsAxisContentProps) {
)}

<tbody>
{series.filter(isCartesianSeries).map(({ color, id, label, valueFormatter, data }) => {
// @ts-ignore
const formattedValue = valueFormatter(data[dataIndex] ?? null);
if (formattedValue == null) {
return null;
}
return (
<ChartsTooltipRow key={id} className={classes.row}>
<ChartsTooltipCell className={clsx(classes.markCell, classes.cell)}>
<ChartsTooltipMark
ownerState={{ color }}
boxShadow={1}
className={classes.mark}
/>
</ChartsTooltipCell>
{series
.filter(isCartesianSeries)
.map(({ color, id, label, valueFormatter, data, getColor }) => {
// @ts-ignore
const formattedValue = valueFormatter(data[dataIndex] ?? null);
if (formattedValue == null) {
return null;
}
return (
<ChartsTooltipRow key={id} className={classes.row}>
<ChartsTooltipCell className={clsx(classes.markCell, classes.cell)}>
<ChartsTooltipMark
ownerState={{ color: getColor(dataIndex) ?? color }}
boxShadow={1}
className={classes.mark}
/>
</ChartsTooltipCell>

<ChartsTooltipCell className={clsx(classes.labelCell, classes.cell)}>
{label ? <Typography>{label}</Typography> : null}
</ChartsTooltipCell>
<ChartsTooltipCell className={clsx(classes.labelCell, classes.cell)}>
{label ? <Typography>{label}</Typography> : null}
</ChartsTooltipCell>

<ChartsTooltipCell className={clsx(classes.valueCell, classes.cell)}>
<Typography>{formattedValue}</Typography>
</ChartsTooltipCell>
</ChartsTooltipRow>
);
})}
<ChartsTooltipCell className={clsx(classes.valueCell, classes.cell)}>
<Typography>{formattedValue}</Typography>
</ChartsTooltipCell>
</ChartsTooltipRow>
);
})}
</tbody>
</ChartsTooltipTable>
</ChartsTooltipPaper>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,19 +15,19 @@ import { CommonSeriesType } from '../models/seriesType/common';
function DefaultChartsItemTooltipContent<T extends ChartSeriesType = ChartSeriesType>(
props: ChartsItemContentProps<T>,
) {
const { series, itemData, sx, classes } = props;
const { series, itemData, sx, classes, getColor } = props;

if (itemData.dataIndex === undefined) {
return null;
}
const { displayedLabel, color } =
series.type === 'pie'
? {
color: series.data[itemData.dataIndex].color,
color: getColor(itemData.dataIndex),
displayedLabel: series.data[itemData.dataIndex].label,
}
: {
color: series.color,
color: getColor(itemData.dataIndex) ?? series.color,
displayedLabel: series.label,
};

Expand Down
5 changes: 5 additions & 0 deletions packages/x-charts/src/ChartsTooltip/utils.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ export function isCartesianSeriesType(seriesType: string): seriesType is Cartesi
return ['bar', 'line', 'scatter'].includes(seriesType);
}

export function isCartesianSeries(
series: ChartSeriesDefaultized<ChartSeriesType> & { getColor: (dataIndex: number) => string },
): series is ChartSeriesDefaultized<CartesianChartSeriesType> & {
getColor: (dataIndex: number) => string;
};
export function isCartesianSeries(
series: ChartSeriesDefaultized<ChartSeriesType>,
): series is ChartSeriesDefaultized<CartesianChartSeriesType> {
Expand Down
34 changes: 34 additions & 0 deletions packages/x-charts/src/LineChart/getColor.ts
Original file line number Diff line number Diff line change
@@ -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;
}

0 comments on commit f734ecb

Please sign in to comment.