UNPKG

@mui/x-charts

Version:

The community edition of MUI X Charts components.

457 lines (449 loc) 16.9 kB
"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;