@mui/x-charts
Version:
The community edition of the charts components (MUI X).
297 lines (294 loc) • 12.7 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import * as React from 'react';
import { scaleBand, scalePoint } from 'd3-scale';
import PropTypes from 'prop-types';
import { getExtremumX as getBarExtremumX, getExtremumY as getBarExtremumY } from '../BarChart/extremums';
import { getExtremumX as getScatterExtremumX, getExtremumY as getScatterExtremumY } from '../ScatterChart/extremums';
import { getExtremumX as getLineExtremumX, getExtremumY as getLineExtremumY } from '../LineChart/extremums';
import { 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 { getTickNumber } from '../hooks/useTicks';
import { jsx as _jsx } from "react/jsx-runtime";
const DEFAULT_CATEGORY_GAP_RATIO = 0.2;
const DEFAULT_BAR_GAP_RATIO = 0.1;
// TODO: those might be better placed in a distinct file
const xExtremumGetters = {
bar: getBarExtremumX,
scatter: getScatterExtremumX,
line: getLineExtremumX
};
const yExtremumGetters = {
bar: getBarExtremumY,
scatter: getScatterExtremumY,
line: getLineExtremumY
};
export const CartesianContext = /*#__PURE__*/React.createContext({
xAxis: {},
yAxis: {},
xAxisIds: [],
yAxisIds: []
});
/**
* API:
*
* - [CartesianContextProvider API](https://mui.com/x/api/charts/cartesian-context-provider/)
*/
function CartesianContextProvider({
xAxis: inXAxis,
yAxis: inYAxis,
dataset,
children
}) {
const formattedSeries = React.useContext(SeriesContext);
const drawingArea = React.useContext(DrawingContext);
const xAxis = React.useMemo(() => inXAxis == null ? void 0 : inXAxis.map(axisConfig => {
const dataKey = axisConfig.dataKey;
if (dataKey === undefined || axisConfig.data !== undefined) {
return axisConfig;
}
if (dataset === undefined) {
throw Error('MUI-X-Charts: x-axis uses `dataKey` but no `dataset` is provided.');
}
return _extends({}, axisConfig, {
data: dataset.map(d => d[dataKey])
});
}), [inXAxis, dataset]);
const yAxis = React.useMemo(() => inYAxis == null ? void 0 : inYAxis.map(axisConfig => {
const dataKey = axisConfig.dataKey;
if (dataKey === undefined || axisConfig.data !== undefined) {
return axisConfig;
}
if (dataset === undefined) {
throw Error('MUI-X-Charts: y-axis uses `dataKey` but no `dataset` is provided.');
}
return _extends({}, axisConfig, {
data: dataset.map(d => d[dataKey])
});
}), [inYAxis, dataset]);
const value = React.useMemo(() => {
var _xAxis$map, _yAxis$map;
const axisExtremumCallback = (acc, chartType, axis, getters, isDefaultAxis) => {
var _ref, _formattedSeries$char;
const getter = getters[chartType];
const series = (_ref = (_formattedSeries$char = formattedSeries[chartType]) == null ? void 0 : _formattedSeries$char.series) != null ? _ref : {};
const [minChartTypeData, maxChartTypeData] = getter({
series,
axis,
isDefaultAxis
});
const [minData, maxData] = acc;
if (minData === null || maxData === null) {
return [minChartTypeData, maxChartTypeData];
}
if (minChartTypeData === null || maxChartTypeData === null) {
return [minData, maxData];
}
return [Math.min(minChartTypeData, minData), Math.max(maxChartTypeData, maxData)];
};
const getAxisExtremum = (axis, getters, isDefaultAxis) => {
const charTypes = Object.keys(getters);
return charTypes.reduce((acc, charType) => axisExtremumCallback(acc, charType, axis, getters, isDefaultAxis), [null, null]);
};
const allXAxis = [...((_xAxis$map = xAxis == null ? void 0 : xAxis.map((axis, index) => _extends({
id: `deaultized-x-axis-${index}`
}, axis))) != null ? _xAxis$map : []),
// Allows to specify an axis with id=DEFAULT_X_AXIS_KEY
...(xAxis === undefined || xAxis.findIndex(({
id
}) => id === DEFAULT_X_AXIS_KEY) === -1 ? [{
id: DEFAULT_X_AXIS_KEY,
scaleType: 'linear'
}] : [])];
const completedXAxis = {};
allXAxis.forEach((axis, axisIndex) => {
var _axis$scaleType, _axis$min, _axis$max, _axis$min2, _axis$max2;
const isDefaultAxis = axisIndex === 0;
const [minData, maxData] = getAxisExtremum(axis, xExtremumGetters, isDefaultAxis);
const range = [drawingArea.left, drawingArea.left + drawingArea.width];
if (isBandScaleConfig(axis)) {
var _axis$categoryGapRati, _axis$barGapRatio;
const categoryGapRatio = (_axis$categoryGapRati = axis.categoryGapRatio) != null ? _axis$categoryGapRati : DEFAULT_CATEGORY_GAP_RATIO;
const barGapRatio = (_axis$barGapRatio = axis.barGapRatio) != null ? _axis$barGapRatio : DEFAULT_BAR_GAP_RATIO;
completedXAxis[axis.id] = _extends({
categoryGapRatio,
barGapRatio
}, axis, {
scale: scaleBand(axis.data, range).paddingInner(categoryGapRatio).paddingOuter(categoryGapRatio / 2),
tickNumber: axis.data.length
});
}
if (isPointScaleConfig(axis)) {
completedXAxis[axis.id] = _extends({}, axis, {
scale: scalePoint(axis.data, range),
tickNumber: axis.data.length
});
}
if (axis.scaleType === 'band' || axis.scaleType === 'point') {
// Could be merged with the two previous "if conditions" but then TS does not get that `axis.scaleType` can't be `band` or `point`.
return;
}
const scaleType = (_axis$scaleType = axis.scaleType) != null ? _axis$scaleType : 'linear';
const extremums = [(_axis$min = axis.min) != null ? _axis$min : minData, (_axis$max = axis.max) != null ? _axis$max : maxData];
const tickNumber = getTickNumber(_extends({}, axis, {
range,
domain: extremums
}));
const niceScale = getScale(scaleType, extremums, range).nice(tickNumber);
const niceDomain = niceScale.domain();
const domain = [(_axis$min2 = axis.min) != null ? _axis$min2 : niceDomain[0], (_axis$max2 = axis.max) != null ? _axis$max2 : niceDomain[1]];
completedXAxis[axis.id] = _extends({}, axis, {
scaleType,
scale: niceScale.domain(domain),
tickNumber
});
});
const allYAxis = [...((_yAxis$map = yAxis == null ? void 0 : yAxis.map((axis, index) => _extends({
id: `deaultized-y-axis-${index}`
}, axis))) != null ? _yAxis$map : []), ...(yAxis === undefined || yAxis.findIndex(({
id
}) => id === DEFAULT_Y_AXIS_KEY) === -1 ? [{
id: DEFAULT_Y_AXIS_KEY,
scaleType: 'linear'
}] : [])];
const completedYAxis = {};
allYAxis.forEach((axis, axisIndex) => {
var _axis$scaleType2, _axis$min3, _axis$max3, _axis$min4, _axis$max4;
const isDefaultAxis = axisIndex === 0;
const [minData, maxData] = getAxisExtremum(axis, yExtremumGetters, isDefaultAxis);
const range = [drawingArea.top + drawingArea.height, drawingArea.top];
if (isBandScaleConfig(axis)) {
var _axis$categoryGapRati2;
const categoryGapRatio = (_axis$categoryGapRati2 = axis.categoryGapRatio) != null ? _axis$categoryGapRati2 : DEFAULT_CATEGORY_GAP_RATIO;
completedYAxis[axis.id] = _extends({
categoryGapRatio,
barGapRatio: 0
}, axis, {
scale: scaleBand(axis.data, [range[1], range[0]]).paddingInner(categoryGapRatio).paddingOuter(categoryGapRatio / 2),
tickNumber: axis.data.length
});
}
if (isPointScaleConfig(axis)) {
completedYAxis[axis.id] = _extends({}, axis, {
scale: scalePoint(axis.data, [range[1], range[0]]),
tickNumber: axis.data.length
});
}
if (axis.scaleType === 'band' || axis.scaleType === 'point') {
// Could be merged with the two previous "if conditions" but then TS does not get that `axis.scaleType` can't be `band` or `point`.
return;
}
const scaleType = (_axis$scaleType2 = axis.scaleType) != null ? _axis$scaleType2 : 'linear';
const extremums = [(_axis$min3 = axis.min) != null ? _axis$min3 : minData, (_axis$max3 = axis.max) != null ? _axis$max3 : maxData];
const tickNumber = getTickNumber(_extends({}, axis, {
range,
domain: extremums
}));
const niceScale = getScale(scaleType, extremums, range).nice(tickNumber);
const niceDomain = niceScale.domain();
const domain = [(_axis$min4 = axis.min) != null ? _axis$min4 : niceDomain[0], (_axis$max4 = axis.max) != null ? _axis$max4 : niceDomain[1]];
completedYAxis[axis.id] = _extends({}, axis, {
scaleType,
scale: niceScale.domain(domain),
tickNumber
});
});
return {
xAxis: completedXAxis,
yAxis: completedYAxis,
xAxisIds: allXAxis.map(({
id
}) => id),
yAxisIds: allYAxis.map(({
id
}) => id)
};
}, [drawingArea.height, drawingArea.left, drawingArea.top, drawingArea.width, formattedSeries, xAxis, yAxis]);
// @ts-ignore
return /*#__PURE__*/_jsx(CartesianContext.Provider, {
value: value,
children: children
});
}
process.env.NODE_ENV !== "production" ? CartesianContextProvider.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "yarn proptypes" |
// ----------------------------------------------------------------------
children: PropTypes.node,
/**
* An array of objects that can be used to populate series and axes data using their `dataKey` property.
*/
dataset: PropTypes.arrayOf(PropTypes.object),
/**
* The configuration of the x-axes.
* If not provided, a default axis config is used with id set to `DEFAULT_X_AXIS_KEY`.
*/
xAxis: PropTypes.arrayOf(PropTypes.shape({
axisId: PropTypes.string,
classes: PropTypes.object,
data: PropTypes.array,
dataKey: PropTypes.string,
disableLine: PropTypes.bool,
disableTicks: PropTypes.bool,
fill: PropTypes.string,
hideTooltip: PropTypes.bool,
id: PropTypes.string,
label: PropTypes.string,
labelFontSize: PropTypes.number,
labelStyle: PropTypes.object,
max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]),
min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]),
position: PropTypes.oneOf(['bottom', 'left', 'right', 'top']),
scaleType: PropTypes.oneOf(['band', 'linear', 'log', 'point', 'pow', 'sqrt', 'time', 'utc']),
slotProps: PropTypes.object,
slots: PropTypes.object,
stroke: PropTypes.string,
tickFontSize: PropTypes.number,
tickInterval: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.array, PropTypes.func]),
tickLabelInterval: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.func]),
tickLabelStyle: PropTypes.object,
tickMaxStep: PropTypes.number,
tickMinStep: PropTypes.number,
tickNumber: PropTypes.number,
tickSize: PropTypes.number,
valueFormatter: PropTypes.func
})),
/**
* The configuration of the y-axes.
* If not provided, a default axis config is used with id set to `DEFAULT_Y_AXIS_KEY`.
*/
yAxis: PropTypes.arrayOf(PropTypes.shape({
axisId: PropTypes.string,
classes: PropTypes.object,
data: PropTypes.array,
dataKey: PropTypes.string,
disableLine: PropTypes.bool,
disableTicks: PropTypes.bool,
fill: PropTypes.string,
hideTooltip: PropTypes.bool,
id: PropTypes.string,
label: PropTypes.string,
labelFontSize: PropTypes.number,
labelStyle: PropTypes.object,
max: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]),
min: PropTypes.oneOfType([PropTypes.instanceOf(Date), PropTypes.number]),
position: PropTypes.oneOf(['bottom', 'left', 'right', 'top']),
scaleType: PropTypes.oneOf(['band', 'linear', 'log', 'point', 'pow', 'sqrt', 'time', 'utc']),
slotProps: PropTypes.object,
slots: PropTypes.object,
stroke: PropTypes.string,
tickFontSize: PropTypes.number,
tickInterval: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.array, PropTypes.func]),
tickLabelInterval: PropTypes.oneOfType([PropTypes.oneOf(['auto']), PropTypes.func]),
tickLabelStyle: PropTypes.object,
tickMaxStep: PropTypes.number,
tickMinStep: PropTypes.number,
tickNumber: PropTypes.number,
tickSize: PropTypes.number,
valueFormatter: PropTypes.func
}))
} : void 0;
export { CartesianContextProvider };