@mui/x-charts
Version:
The community edition of MUI X Charts components.
300 lines (294 loc) • 14.7 kB
JavaScript
import _extends from "@babel/runtime/helpers/esm/extends";
import { createSelector, createSelectorMemoized } from '@mui/x-internals/store';
import { selectorChartDrawingArea } from "../../corePlugins/useChartDimensions/index.js";
import { selectorChartSeriesConfig, selectorChartSeriesProcessed } from "../../corePlugins/useChartSeries/index.js";
import { computeAxisValue } from "./computeAxisValue.js";
import { createContinuousScaleGetAxisFilter, createDiscreteScaleGetAxisFilter, createGetAxisFilters } from "./createAxisFilterMapper.js";
import { createZoomLookup } from "./createZoomLookup.js";
import { isBandScaleConfig, isPointScaleConfig } from "../../../../models/axis.js";
import { selectorChartRawXAxis, selectorChartRawYAxis } from "./useChartCartesianAxisLayout.selectors.js";
import { selectorPreferStrictDomainInLineCharts } from "../../corePlugins/useChartExperimentalFeature/index.js";
import { getDefaultTickNumber, getTickNumber } from "../../../ticks.js";
import { getNormalizedAxisScale, getRange } from "./getAxisScale.js";
import { isOrdinalScale } from "../../../scaleGuards.js";
import { zoomScaleRange } from "./zoom.js";
import { getAxisExtrema } from "./getAxisExtrema.js";
import { calculateFinalDomain, calculateInitialDomainAndTickNumber } from "./domain.js";
import { Flatbush } from "../../../Flatbush.js";
export const createZoomMap = zoom => {
const zoomItemMap = new Map();
zoom.forEach(zoomItem => {
zoomItemMap.set(zoomItem.axisId, zoomItem);
});
return zoomItemMap;
};
const selectorChartZoomState = state => state.zoom;
/**
* Following selectors are not exported because they exist in the MIT chart only to ba able to reuse the Zoom state from the pro.
*/
export const selectorChartZoomIsInteracting = createSelector(selectorChartZoomState, zoom => zoom?.isInteracting);
export const selectorChartZoomMap = createSelectorMemoized(selectorChartZoomState, function selectorChartZoomMap(zoom) {
return zoom?.zoomData && createZoomMap(zoom?.zoomData);
});
export const selectorChartAxisZoomData = createSelector(selectorChartZoomMap, (zoomMap, axisId) => zoomMap?.get(axisId));
export const selectorChartZoomOptionsLookup = createSelectorMemoized(selectorChartRawXAxis, selectorChartRawYAxis, function selectorChartZoomOptionsLookup(xAxis, yAxis) {
return _extends({}, createZoomLookup('x')(xAxis), createZoomLookup('y')(yAxis));
});
export const selectorChartAxisZoomOptionsLookup = createSelector(selectorChartZoomOptionsLookup, (axisLookup, axisId) => axisLookup[axisId]);
export const selectorDefaultXAxisTickNumber = createSelector(selectorChartDrawingArea, function selectorDefaultXAxisTickNumber(drawingArea) {
return getDefaultTickNumber(drawingArea.width);
});
export const selectorDefaultYAxisTickNumber = createSelector(selectorChartDrawingArea, function selectorDefaultYAxisTickNumber(drawingArea) {
return getDefaultTickNumber(drawingArea.height);
});
export const selectorChartXAxisWithDomains = createSelectorMemoized(selectorChartRawXAxis, selectorChartSeriesProcessed, selectorChartSeriesConfig, selectorPreferStrictDomainInLineCharts, selectorDefaultXAxisTickNumber, function selectorChartXAxisWithDomains(axes, formattedSeries, seriesConfig, preferStrictDomainInLineCharts, defaultTickNumber) {
const axisDirection = 'x';
const domains = {};
axes?.forEach((eachAxis, axisIndex) => {
const axis = eachAxis;
if (isBandScaleConfig(axis) || isPointScaleConfig(axis)) {
domains[axis.id] = {
domain: axis.data
};
if (axis.ordinalTimeTicks !== undefined) {
domains[axis.id].tickNumber = getTickNumber(axis, [axis.data?.find(d => d !== null), axis.data?.findLast(d => d !== null)], defaultTickNumber);
}
return;
}
const axisExtrema = getAxisExtrema(axis, axisDirection, seriesConfig, axisIndex, formattedSeries);
domains[axis.id] = calculateInitialDomainAndTickNumber(axis, 'x', axisIndex, formattedSeries, axisExtrema, defaultTickNumber, preferStrictDomainInLineCharts);
});
return {
axes,
domains
};
});
export const selectorChartYAxisWithDomains = createSelectorMemoized(selectorChartRawYAxis, selectorChartSeriesProcessed, selectorChartSeriesConfig, selectorPreferStrictDomainInLineCharts, selectorDefaultYAxisTickNumber, function selectorChartYAxisWithDomains(axes, formattedSeries, seriesConfig, preferStrictDomainInLineCharts, defaultTickNumber) {
const axisDirection = 'y';
const domains = {};
axes?.forEach((eachAxis, axisIndex) => {
const axis = eachAxis;
if (isBandScaleConfig(axis) || isPointScaleConfig(axis)) {
domains[axis.id] = {
domain: axis.data
};
if (axis.ordinalTimeTicks !== undefined) {
domains[axis.id].tickNumber = getTickNumber(axis, [axis.data?.find(d => d !== null), axis.data?.findLast(d => d !== null)], defaultTickNumber);
}
return;
}
const axisExtrema = getAxisExtrema(axis, axisDirection, seriesConfig, axisIndex, formattedSeries);
domains[axis.id] = calculateInitialDomainAndTickNumber(axis, 'y', axisIndex, formattedSeries, axisExtrema, defaultTickNumber, preferStrictDomainInLineCharts);
});
return {
axes,
domains
};
});
export const selectorChartZoomAxisFilters = createSelectorMemoized(selectorChartZoomMap, selectorChartZoomOptionsLookup, selectorChartXAxisWithDomains, selectorChartYAxisWithDomains, function selectorChartZoomAxisFilters(zoomMap, zoomOptions, {
axes: xAxis,
domains: xDomains
}, {
axes: yAxis,
domains: yDomains
}) {
if (!zoomMap || !zoomOptions) {
return undefined;
}
let hasFilter = false;
const filters = {};
const axes = [...(xAxis ?? []), ...(yAxis ?? [])];
for (let i = 0; i < axes.length; i += 1) {
const axis = axes[i];
if (!zoomOptions[axis.id] || zoomOptions[axis.id].filterMode !== 'discard') {
continue;
}
const zoom = zoomMap.get(axis.id);
if (zoom === undefined || zoom.start <= 0 && zoom.end >= 100) {
// No zoom, or zoom with all data visible
continue;
}
const axisDirection = i < (xAxis?.length ?? 0) ? 'x' : 'y';
if (axis.scaleType === 'band' || axis.scaleType === 'point') {
filters[axis.id] = createDiscreteScaleGetAxisFilter(axis.data, zoom.start, zoom.end, axisDirection);
} else {
const {
domain
} = axisDirection === 'x' ? xDomains[axis.id] : yDomains[axis.id];
filters[axis.id] = createContinuousScaleGetAxisFilter(
// For continuous scales, the domain is always a two-value array.
domain, zoom.start, zoom.end, axisDirection, axis.data);
}
hasFilter = true;
}
if (!hasFilter) {
return undefined;
}
return createGetAxisFilters(filters);
});
export const selectorChartFilteredXDomains = createSelectorMemoized(selectorChartSeriesProcessed, selectorChartSeriesConfig, selectorChartZoomMap, selectorChartZoomOptionsLookup, selectorChartZoomAxisFilters, selectorPreferStrictDomainInLineCharts, selectorChartXAxisWithDomains, function selectorChartFilteredXDomains(formattedSeries, seriesConfig, zoomMap, zoomOptions, getFilters, preferStrictDomainInLineCharts, {
axes,
domains
}) {
const filteredDomains = {};
axes?.forEach((axis, axisIndex) => {
const domain = domains[axis.id].domain;
if (isBandScaleConfig(axis) || isPointScaleConfig(axis)) {
filteredDomains[axis.id] = domain;
return;
}
const zoom = zoomMap?.get(axis.id);
const zoomOption = zoomOptions?.[axis.id];
const filter = zoom === undefined && !zoomOption ? getFilters : undefined; // Do not apply filtering if zoom is already defined.
if (!filter) {
filteredDomains[axis.id] = domain;
return;
}
const rawTickNumber = domains[axis.id].tickNumber;
const axisExtrema = getAxisExtrema(axis, 'x', seriesConfig, axisIndex, formattedSeries, filter);
filteredDomains[axis.id] = calculateFinalDomain(axis, 'x', axisIndex, formattedSeries, axisExtrema, rawTickNumber, preferStrictDomainInLineCharts);
});
return filteredDomains;
});
export const selectorChartFilteredYDomains = createSelectorMemoized(selectorChartSeriesProcessed, selectorChartSeriesConfig, selectorChartZoomMap, selectorChartZoomOptionsLookup, selectorChartZoomAxisFilters, selectorPreferStrictDomainInLineCharts, selectorChartYAxisWithDomains, function selectorChartFilteredYDomains(formattedSeries, seriesConfig, zoomMap, zoomOptions, getFilters, preferStrictDomainInLineCharts, {
axes,
domains
}) {
const filteredDomains = {};
axes?.forEach((axis, axisIndex) => {
const domain = domains[axis.id].domain;
if (isBandScaleConfig(axis) || isPointScaleConfig(axis)) {
filteredDomains[axis.id] = domain;
return;
}
const zoom = zoomMap?.get(axis.id);
const zoomOption = zoomOptions?.[axis.id];
const filter = zoom === undefined && !zoomOption ? getFilters : undefined; // Do not apply filtering if zoom is already defined.
if (!filter) {
filteredDomains[axis.id] = domain;
return;
}
const rawTickNumber = domains[axis.id].tickNumber;
const axisExtrema = getAxisExtrema(axis, 'y', seriesConfig, axisIndex, formattedSeries, filter);
filteredDomains[axis.id] = calculateFinalDomain(axis, 'y', axisIndex, formattedSeries, axisExtrema, rawTickNumber, preferStrictDomainInLineCharts);
});
return filteredDomains;
});
export const selectorChartNormalizedXScales = createSelectorMemoized(selectorChartRawXAxis, selectorChartFilteredXDomains, function selectorChartNormalizedXScales(axes, filteredDomains) {
const scales = {};
axes?.forEach(eachAxis => {
const axis = eachAxis;
const domain = filteredDomains[axis.id];
scales[axis.id] = getNormalizedAxisScale(axis, domain);
});
return scales;
});
export const selectorChartNormalizedYScales = createSelectorMemoized(selectorChartRawYAxis, selectorChartFilteredYDomains, function selectorChartNormalizedYScales(axes, filteredDomains) {
const scales = {};
axes?.forEach(eachAxis => {
const axis = eachAxis;
const domain = filteredDomains[axis.id];
scales[axis.id] = getNormalizedAxisScale(axis, domain);
});
return scales;
});
export const selectorChartXScales = createSelectorMemoized(selectorChartRawXAxis, selectorChartNormalizedXScales, selectorChartDrawingArea, selectorChartZoomMap, function selectorChartXScales(axes, normalizedScales, drawingArea, zoomMap) {
const scales = {};
axes?.forEach(eachAxis => {
const axis = eachAxis;
const zoom = zoomMap?.get(axis.id);
const zoomRange = zoom ? [zoom.start, zoom.end] : [0, 100];
const range = getRange(drawingArea, 'x', axis);
const scale = normalizedScales[axis.id].copy();
const zoomedRange = zoomScaleRange(range, zoomRange);
scale.range(zoomedRange);
scales[axis.id] = scale;
});
return scales;
});
export const selectorChartYScales = createSelectorMemoized(selectorChartRawYAxis, selectorChartNormalizedYScales, selectorChartDrawingArea, selectorChartZoomMap, function selectorChartYScales(axes, normalizedScales, drawingArea, zoomMap) {
const scales = {};
axes?.forEach(eachAxis => {
const axis = eachAxis;
const zoom = zoomMap?.get(axis.id);
const zoomRange = zoom ? [zoom.start, zoom.end] : [0, 100];
const range = getRange(drawingArea, 'y', axis);
const scale = normalizedScales[axis.id].copy();
const scaleRange = isOrdinalScale(scale) ? range.reverse() : range;
const zoomedRange = zoomScaleRange(scaleRange, zoomRange);
scale.range(zoomedRange);
scales[axis.id] = scale;
});
return scales;
});
/**
* The only interesting selectors that merge axis data and zoom if provided.
*/
export const selectorChartXAxis = createSelectorMemoized(selectorChartDrawingArea, selectorChartSeriesProcessed, selectorChartSeriesConfig, selectorChartZoomMap, selectorChartXAxisWithDomains, selectorChartXScales, function selectorChartXAxis(drawingArea, formattedSeries, seriesConfig, zoomMap, {
axes,
domains
}, scales) {
return computeAxisValue({
scales,
drawingArea,
formattedSeries,
axis: axes,
seriesConfig,
axisDirection: 'x',
zoomMap,
domains
});
});
export const selectorChartYAxis = createSelectorMemoized(selectorChartDrawingArea, selectorChartSeriesProcessed, selectorChartSeriesConfig, selectorChartZoomMap, selectorChartYAxisWithDomains, selectorChartYScales, function selectorChartYAxis(drawingArea, formattedSeries, seriesConfig, zoomMap, {
axes,
domains
}, scales) {
return computeAxisValue({
scales,
drawingArea,
formattedSeries,
axis: axes,
seriesConfig,
axisDirection: 'y',
zoomMap,
domains
});
});
export const selectorChartAxis = createSelector(selectorChartXAxis, selectorChartYAxis, (xAxes, yAxes, axisId) => xAxes?.axis[axisId] ?? yAxes?.axis[axisId]);
export const selectorChartRawAxis = createSelector(selectorChartRawXAxis, selectorChartRawYAxis, (xAxes, yAxes, axisId) => {
const axis = xAxes?.find(a => a.id === axisId) ?? yAxes?.find(a => a.id === axisId) ?? null;
if (!axis) {
return undefined;
}
return axis;
});
export const selectorChartDefaultXAxisId = createSelector(selectorChartRawXAxis, xAxes => xAxes[0].id);
export const selectorChartDefaultYAxisId = createSelector(selectorChartRawYAxis, yAxes => yAxes[0].id);
const EMPTY_MAP = new Map();
export const selectorChartSeriesEmptyFlatbushMap = () => EMPTY_MAP;
export const selectorChartSeriesFlatbushMap = createSelectorMemoized(selectorChartSeriesProcessed, selectorChartNormalizedXScales, selectorChartNormalizedYScales, selectorChartDefaultXAxisId, selectorChartDefaultYAxisId, function selectChartSeriesFlatbushMap(allSeries, xAxesScaleMap, yAxesScaleMap, defaultXAxisId, defaultYAxisId) {
// FIXME: Do we want to support non-scatter series here?
const validSeries = allSeries.scatter;
const flatbushMap = new Map();
if (!validSeries) {
return flatbushMap;
}
validSeries.seriesOrder.forEach(seriesId => {
const {
data,
xAxisId = defaultXAxisId,
yAxisId = defaultYAxisId
} = validSeries.series[seriesId];
const flatbush = new Flatbush(data.length);
const originalXScale = xAxesScaleMap[xAxisId];
const originalYScale = yAxesScaleMap[yAxisId];
for (const datum of data) {
// Add the points using a [0, 1] range so that we don't need to recreate the Flatbush structure when zooming.
// This doesn't happen in practice, though, because currently the scales depend on the drawing area.
flatbush.add(originalXScale(datum.x), originalYScale(datum.y));
}
flatbush.finish();
flatbushMap.set(seriesId, flatbush);
});
return flatbushMap;
});