@mui/x-charts
Version:
The community edition of MUI X Charts components.
457 lines (449 loc) • 16.9 kB
JavaScript
"use strict";
'use client';
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault").default;
var _interopRequireWildcard = require("@babel/runtime/helpers/interopRequireWildcard").default;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.ChartsXAxis = ChartsXAxis;
var _extends2 = _interopRequireDefault(require("@babel/runtime/helpers/extends"));
var _objectWithoutPropertiesLoose2 = _interopRequireDefault(require("@babel/runtime/helpers/objectWithoutPropertiesLoose"));
var React = _interopRequireWildcard(require("react"));
var _propTypes = _interopRequireDefault(require("prop-types"));
var _useSlotProps = _interopRequireDefault(require("@mui/utils/useSlotProps"));
var _composeClasses = _interopRequireDefault(require("@mui/utils/composeClasses"));
var _styles = require("@mui/material/styles");
var _RtlProvider = require("@mui/system/RtlProvider");
var _clampAngle = require("../internals/clampAngle");
var _useIsHydrated = require("../hooks/useIsHydrated");
var _ellipsize = require("../internals/ellipsize");
var _domUtils = require("../internals/domUtils");
var _useTicks = require("../hooks/useTicks");
var _axisClasses = require("../ChartsAxis/axisClasses");
var _AxisSharedComponents = require("../internals/components/AxisSharedComponents");
var _ChartsText = require("../ChartsText");
var _geometry = require("../internals/geometry");
var _useMounted = require("../hooks/useMounted");
var _useDrawingArea = require("../hooks/useDrawingArea");
var _getWordsByLines = require("../internals/getWordsByLines");
var _isInfinity = require("../internals/isInfinity");
var _isBandScale = require("../internals/isBandScale");
var _useChartContext = require("../context/ChartProvider/useChartContext");
var _useAxis = require("../hooks/useAxis");
var _defaultTextPlacement = require("../ChartsText/defaultTextPlacement");
var _invertTextAnchor = require("../internals/invertTextAnchor");
var _jsxRuntime = require("react/jsx-runtime");
const _excluded = ["scale", "tickNumber", "reverse"];
const useUtilityClasses = ownerState => {
const {
classes,
position,
id
} = ownerState;
const slots = {
root: ['root', 'directionX', position, `id-${id}`],
line: ['line'],
tickContainer: ['tickContainer'],
tick: ['tick'],
tickLabel: ['tickLabel'],
label: ['label']
};
return (0, _composeClasses.default)(slots, _axisClasses.getAxisUtilityClass, classes);
};
/* Gap between a tick and its label. */
const TICK_LABEL_GAP = 3;
/* Gap between the axis label and tick labels. */
const AXIS_LABEL_TICK_LABEL_GAP = 4;
/* Returns a set of indices of the tick labels that should be visible. */
function getVisibleLabels(xTicks, {
tickLabelStyle: style,
tickLabelInterval,
tickLabelMinGap,
reverse,
isMounted,
isXInside
}) {
const getTickLabelSize = tick => {
if (!isMounted || tick.formattedValue === undefined) {
return {
width: 0,
height: 0
};
}
const tickSizes = (0, _getWordsByLines.getWordsByLines)({
style,
needsComputation: true,
text: tick.formattedValue
});
return {
width: Math.max(...tickSizes.map(size => size.width)),
height: Math.max(tickSizes.length * tickSizes[0].height)
};
};
if (typeof tickLabelInterval === 'function') {
return new Set(xTicks.filter((item, index) => tickLabelInterval(item.value, index)));
}
// Filter label to avoid overlap
let previousTextLimit = 0;
const direction = reverse ? -1 : 1;
return new Set(xTicks.filter((item, labelIndex) => {
const {
offset,
labelOffset
} = item;
const textPosition = offset + labelOffset;
if (labelIndex > 0 && direction * textPosition < direction * (previousTextLimit + tickLabelMinGap)) {
return false;
}
if (!isXInside(textPosition)) {
return false;
}
/* Measuring text width is expensive, so we need to delay it as much as possible to improve performance. */
const {
width,
height
} = getTickLabelSize(item);
const distance = (0, _geometry.getMinXTranslation)(width, height, style?.angle);
const currentTextLimit = textPosition - direction * distance / 2;
if (labelIndex > 0 && direction * currentTextLimit < direction * (previousTextLimit + tickLabelMinGap)) {
// Except for the first label, we skip all label that overlap with the last accepted.
// Notice that the early return prevents `previousTextLimit` from being updated.
return false;
}
previousTextLimit = textPosition + direction * distance / 2;
return true;
}));
}
function shortenLabels(visibleLabels, drawingArea, maxHeight, isRtl, tickLabelStyle) {
const shortenedLabels = new Map();
const angle = (0, _clampAngle.clampAngle)(tickLabelStyle?.angle ?? 0);
// Multiplying the space available to the left of the text position by leftBoundFactor returns the max width of the text.
// Same for rightBoundFactor
let leftBoundFactor = 1;
let rightBoundFactor = 1;
if (tickLabelStyle?.textAnchor === 'start') {
leftBoundFactor = Infinity;
rightBoundFactor = 1;
} else if (tickLabelStyle?.textAnchor === 'end') {
leftBoundFactor = 1;
rightBoundFactor = Infinity;
} else {
leftBoundFactor = 2;
rightBoundFactor = 2;
}
if (angle > 90 && angle < 270) {
[leftBoundFactor, rightBoundFactor] = [rightBoundFactor, leftBoundFactor];
}
if (isRtl) {
[leftBoundFactor, rightBoundFactor] = [rightBoundFactor, leftBoundFactor];
}
for (const item of visibleLabels) {
if (item.formattedValue) {
// That maximum width of the tick depends on its proximity to the axis bounds.
const width = Math.min((item.offset + item.labelOffset) * leftBoundFactor, (drawingArea.left + drawingArea.width + drawingArea.right - item.offset - item.labelOffset) * rightBoundFactor);
const doesTextFit = text => (0, _ellipsize.doesTextFitInRect)(text, {
width,
height: maxHeight,
angle,
measureText: string => (0, _domUtils.getStringSize)(string, tickLabelStyle)
});
shortenedLabels.set(item, (0, _ellipsize.ellipsize)(item.formattedValue.toString(), doesTextFit));
}
}
return shortenedLabels;
}
const XAxisRoot = (0, _styles.styled)(_AxisSharedComponents.AxisRoot, {
name: 'MuiChartsXAxis',
slot: 'Root'
})({});
const defaultProps = {
disableLine: false,
disableTicks: false,
tickSize: 6,
tickLabelMinGap: 4
};
/**
* Demos:
*
* - [Axis](https://mui.com/x/react-charts/axis/)
*
* API:
*
* - [ChartsXAxis API](https://mui.com/x/api/charts/charts-x-axis/)
*/
function ChartsXAxis(inProps) {
const {
xAxis,
xAxisIds
} = (0, _useAxis.useXAxes)();
const _xAxis = xAxis[inProps.axisId ?? xAxisIds[0]],
{
scale: xScale,
tickNumber,
reverse
} = _xAxis,
settings = (0, _objectWithoutPropertiesLoose2.default)(_xAxis, _excluded);
const isMounted = (0, _useMounted.useMounted)();
const themedProps = (0, _styles.useThemeProps)({
props: (0, _extends2.default)({}, settings, inProps),
name: 'MuiChartsXAxis'
});
const defaultizedProps = (0, _extends2.default)({}, defaultProps, themedProps);
const {
position,
disableLine,
disableTicks,
tickLabelStyle,
label,
labelStyle,
tickSize: tickSizeProp,
valueFormatter,
slots,
slotProps,
tickInterval,
tickLabelInterval,
tickPlacement,
tickLabelPlacement,
tickLabelMinGap,
sx,
offset,
height: axisHeight
} = defaultizedProps;
const theme = (0, _styles.useTheme)();
const isRtl = (0, _RtlProvider.useRtl)();
const classes = useUtilityClasses(defaultizedProps);
const drawingArea = (0, _useDrawingArea.useDrawingArea)();
const {
left,
top,
width,
height
} = drawingArea;
const {
instance
} = (0, _useChartContext.useChartContext)();
const isHydrated = (0, _useIsHydrated.useIsHydrated)();
const tickSize = disableTicks ? 4 : tickSizeProp;
const positionSign = position === 'bottom' ? 1 : -1;
const Line = slots?.axisLine ?? 'line';
const Tick = slots?.axisTick ?? 'line';
const TickLabel = slots?.axisTickLabel ?? _ChartsText.ChartsText;
const Label = slots?.axisLabel ?? _ChartsText.ChartsText;
const defaultTextAnchor = (0, _defaultTextPlacement.getDefaultTextAnchor)((position === 'bottom' ? 0 : 180) - (tickLabelStyle?.angle ?? 0));
const defaultDominantBaseline = (0, _defaultTextPlacement.getDefaultBaseline)((position === 'bottom' ? 0 : 180) - (tickLabelStyle?.angle ?? 0));
const axisTickLabelProps = (0, _useSlotProps.default)({
elementType: TickLabel,
externalSlotProps: slotProps?.axisTickLabel,
additionalProps: {
style: (0, _extends2.default)({}, theme.typography.caption, {
fontSize: 12,
lineHeight: 1.25,
textAnchor: isRtl ? (0, _invertTextAnchor.invertTextAnchor)(defaultTextAnchor) : defaultTextAnchor,
dominantBaseline: defaultDominantBaseline
}, tickLabelStyle)
},
className: classes.tickLabel,
ownerState: {}
});
const xTicks = (0, _useTicks.useTicks)({
scale: xScale,
tickNumber,
valueFormatter,
tickInterval,
tickPlacement,
tickLabelPlacement,
direction: 'x'
});
const visibleLabels = getVisibleLabels(xTicks, {
tickLabelStyle: axisTickLabelProps.style,
tickLabelInterval,
tickLabelMinGap,
reverse,
isMounted,
isXInside: instance.isXInside
});
const axisLabelProps = (0, _useSlotProps.default)({
elementType: Label,
externalSlotProps: slotProps?.axisLabel,
additionalProps: {
style: (0, _extends2.default)({}, theme.typography.body1, {
lineHeight: 1,
fontSize: 14,
textAnchor: 'middle',
dominantBaseline: position === 'bottom' ? 'text-after-edge' : 'text-before-edge'
}, labelStyle)
},
ownerState: {}
});
const domain = xScale.domain();
const ordinalAxis = (0, _isBandScale.isBandScale)(xScale);
// Skip axis rendering if no data is available
// - The domain is an empty array for band/point scales.
// - The domains contains Infinity for continuous scales.
// - The position is set to 'none'.
if (ordinalAxis && domain.length === 0 || !ordinalAxis && domain.some(_isInfinity.isInfinity) || position === 'none') {
return null;
}
const labelHeight = label ? (0, _domUtils.getStringSize)(label, axisLabelProps.style).height : 0;
const labelRefPoint = {
x: left + width / 2,
y: positionSign * axisHeight
};
/* If there's an axis title, the tick labels have less space to render */
const tickLabelsMaxHeight = Math.max(0, axisHeight - (label ? labelHeight + AXIS_LABEL_TICK_LABEL_GAP : 0) - tickSize - TICK_LABEL_GAP);
const tickLabels = isHydrated ? shortenLabels(visibleLabels, drawingArea, tickLabelsMaxHeight, isRtl, axisTickLabelProps.style) : new Map(Array.from(visibleLabels).map(item => [item, item.formattedValue]));
return /*#__PURE__*/(0, _jsxRuntime.jsxs)(XAxisRoot, {
transform: `translate(0, ${position === 'bottom' ? top + height + offset : top - offset})`,
className: classes.root,
sx: sx,
children: [!disableLine && /*#__PURE__*/(0, _jsxRuntime.jsx)(Line, (0, _extends2.default)({
x1: left,
x2: left + width,
className: classes.line
}, slotProps?.axisLine)), xTicks.map((item, index) => {
const {
offset: tickOffset,
labelOffset
} = item;
const xTickLabel = labelOffset ?? 0;
const yTickLabel = positionSign * (tickSize + TICK_LABEL_GAP);
const showTick = instance.isXInside(tickOffset);
const tickLabel = tickLabels.get(item);
const showTickLabel = visibleLabels.has(item);
return /*#__PURE__*/(0, _jsxRuntime.jsxs)("g", {
transform: `translate(${tickOffset}, 0)`,
className: classes.tickContainer,
children: [!disableTicks && showTick && /*#__PURE__*/(0, _jsxRuntime.jsx)(Tick, (0, _extends2.default)({
y2: positionSign * tickSize,
className: classes.tick
}, slotProps?.axisTick)), tickLabel !== undefined && showTickLabel && /*#__PURE__*/(0, _jsxRuntime.jsx)(TickLabel, (0, _extends2.default)({
x: xTickLabel,
y: yTickLabel
}, axisTickLabelProps, {
text: tickLabel
}))]
}, index);
}), label && /*#__PURE__*/(0, _jsxRuntime.jsx)("g", {
className: classes.label,
children: /*#__PURE__*/(0, _jsxRuntime.jsx)(Label, (0, _extends2.default)({}, labelRefPoint, axisLabelProps, {
text: label
}))
})]
});
}
process.env.NODE_ENV !== "production" ? ChartsXAxis.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "pnpm proptypes" |
// ----------------------------------------------------------------------
axis: _propTypes.default.oneOf(['x']),
/**
* The id of the axis to render.
* If undefined, it will be the first defined axis.
*/
axisId: _propTypes.default.oneOfType([_propTypes.default.number, _propTypes.default.string]),
/**
* Override or extend the styles applied to the component.
*/
classes: _propTypes.default.object,
/**
* If true, the axis line is disabled.
* @default false
*/
disableLine: _propTypes.default.bool,
/**
* If true, the ticks are disabled.
* @default false
*/
disableTicks: _propTypes.default.bool,
/**
* The fill color of the axis text.
* @default 'currentColor'
*/
fill: _propTypes.default.string,
/**
* The label of the axis.
*/
label: _propTypes.default.string,
/**
* The style applied to the axis label.
*/
labelStyle: _propTypes.default.object,
/**
* The props used for each component slot.
* @default {}
*/
slotProps: _propTypes.default.object,
/**
* Overridable component slots.
* @default {}
*/
slots: _propTypes.default.object,
/**
* The stroke color of the axis line.
* @default 'currentColor'
*/
stroke: _propTypes.default.string,
sx: _propTypes.default.oneOfType([_propTypes.default.arrayOf(_propTypes.default.oneOfType([_propTypes.default.func, _propTypes.default.object, _propTypes.default.bool])), _propTypes.default.func, _propTypes.default.object]),
/**
* Defines which ticks are displayed.
* Its value can be:
* - 'auto' In such case the ticks are computed based on axis scale and other parameters.
* - a filtering function of the form `(value, index) => boolean` which is available only if the axis has "point" scale.
* - an array containing the values where ticks should be displayed.
* @see See {@link https://mui.com/x/react-charts/axis/#fixed-tick-positions}
* @default 'auto'
*/
tickInterval: _propTypes.default.oneOfType([_propTypes.default.oneOf(['auto']), _propTypes.default.array, _propTypes.default.func]),
/**
* Defines which ticks get its label displayed. Its value can be:
* - 'auto' In such case, labels are displayed if they do not overlap with the previous one.
* - a filtering function of the form (value, index) => boolean. Warning: the index is tick index, not data ones.
* @default 'auto'
*/
tickLabelInterval: _propTypes.default.oneOfType([_propTypes.default.oneOf(['auto']), _propTypes.default.func]),
/**
* The minimum gap in pixels between two tick labels.
* If two tick labels are closer than this minimum gap, one of them will be hidden.
* @default 4
*/
tickLabelMinGap: _propTypes.default.number,
/**
* The placement of ticks label. Can be the middle of the band, or the tick position.
* Only used if scale is 'band'.
* @default 'middle'
*/
tickLabelPlacement: _propTypes.default.oneOf(['middle', 'tick']),
/**
* The style applied to ticks text.
*/
tickLabelStyle: _propTypes.default.object,
/**
* Maximal step between two ticks.
* When using time data, the value is assumed to be in ms.
* Not supported by categorical axis (band, points).
*/
tickMaxStep: _propTypes.default.number,
/**
* Minimal step between two ticks.
* When using time data, the value is assumed to be in ms.
* Not supported by categorical axis (band, points).
*/
tickMinStep: _propTypes.default.number,
/**
* The number of ticks. This number is not guaranteed.
* Not supported by categorical axis (band, points).
*/
tickNumber: _propTypes.default.number,
/**
* The placement of ticks in regard to the band interval.
* Only used if scale is 'band'.
* @default 'extremities'
*/
tickPlacement: _propTypes.default.oneOf(['end', 'extremities', 'middle', 'start']),
/**
* The size of the ticks.
* @default 6
*/
tickSize: _propTypes.default.number
} : void 0;