UNPKG

@mui/x-charts

Version:

The community edition of the charts components (MUI X).

297 lines (294 loc) 12.7 kB
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 };