@mui/x-charts
Version:
The community edition of MUI X Charts components.
375 lines (371 loc) • 12.9 kB
JavaScript
'use client';
import _extends from "@babel/runtime/helpers/esm/extends";
import _objectWithoutPropertiesLoose from "@babel/runtime/helpers/esm/objectWithoutPropertiesLoose";
const _excluded = ["scale", "tickNumber"];
import * as React from 'react';
import PropTypes from 'prop-types';
import useSlotProps from '@mui/utils/useSlotProps';
import composeClasses from '@mui/utils/composeClasses';
import { useThemeProps, styled, useTheme } from '@mui/material/styles';
import { useRtl } from '@mui/system/RtlProvider';
import { useIsHydrated } from "../hooks/useIsHydrated.js";
import { getDefaultBaseline, getDefaultTextAnchor } from "../ChartsText/defaultTextPlacement.js";
import { doesTextFitInRect, ellipsize } from "../internals/ellipsize.js";
import { getStringSize } from "../internals/domUtils.js";
import { useTicks } from "../hooks/useTicks.js";
import { useDrawingArea } from "../hooks/useDrawingArea.js";
import { AxisRoot } from "../internals/components/AxisSharedComponents.js";
import { ChartsText } from "../ChartsText/index.js";
import { getAxisUtilityClass } from "../ChartsAxis/axisClasses.js";
import { isInfinity } from "../internals/isInfinity.js";
import { isBandScale } from "../internals/isBandScale.js";
import { useChartContext } from "../context/ChartProvider/index.js";
import { useYAxes } from "../hooks/index.js";
import { clampAngle } from "../internals/clampAngle.js";
import { invertTextAnchor } from "../internals/invertTextAnchor.js";
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
const useUtilityClasses = ownerState => {
const {
classes,
position,
id
} = ownerState;
const slots = {
root: ['root', 'directionY', position, `id-${id}`],
line: ['line'],
tickContainer: ['tickContainer'],
tick: ['tick'],
tickLabel: ['tickLabel'],
label: ['label']
};
return composeClasses(slots, getAxisUtilityClass, classes);
};
/* Gap between a tick and its label. */
const TICK_LABEL_GAP = 2;
/* Gap between the axis label and tick labels. */
const AXIS_LABEL_TICK_LABEL_GAP = 2;
function shortenLabels(visibleLabels, drawingArea, maxWidth, isRtl, tickLabelStyle) {
const shortenedLabels = new Map();
const angle = clampAngle(tickLabelStyle?.angle ?? 0);
let topBoundFactor = 1;
let bottomBoundFactor = 1;
if (tickLabelStyle?.textAnchor === 'start') {
topBoundFactor = Infinity;
bottomBoundFactor = 1;
} else if (tickLabelStyle?.textAnchor === 'end') {
topBoundFactor = 1;
bottomBoundFactor = Infinity;
} else {
topBoundFactor = 2;
bottomBoundFactor = 2;
}
if (angle > 180) {
[topBoundFactor, bottomBoundFactor] = [bottomBoundFactor, topBoundFactor];
}
if (isRtl) {
[topBoundFactor, bottomBoundFactor] = [bottomBoundFactor, topBoundFactor];
}
for (const item of visibleLabels) {
if (item.formattedValue) {
// That maximum height of the tick depends on its proximity to the axis bounds.
const height = Math.min((item.offset + item.labelOffset) * topBoundFactor, (drawingArea.top + drawingArea.height + drawingArea.bottom - item.offset - item.labelOffset) * bottomBoundFactor);
const doesTextFit = text => doesTextFitInRect(text, {
width: maxWidth,
height,
angle,
measureText: string => getStringSize(string, tickLabelStyle)
});
shortenedLabels.set(item, ellipsize(item.formattedValue.toString(), doesTextFit));
}
}
return shortenedLabels;
}
const YAxisRoot = styled(AxisRoot, {
name: 'MuiChartsYAxis',
slot: 'Root'
})({});
const defaultProps = {
disableLine: false,
disableTicks: false,
tickSize: 6
};
/**
* Demos:
*
* - [Axis](https://mui.com/x/react-charts/axis/)
*
* API:
*
* - [ChartsYAxis API](https://mui.com/x/api/charts/charts-y-axis/)
*/
function ChartsYAxis(inProps) {
const {
yAxisIds,
yAxis
} = useYAxes();
const _yAxis = yAxis[inProps.axisId ?? yAxisIds[0]],
{
scale: yScale,
tickNumber
} = _yAxis,
settings = _objectWithoutPropertiesLoose(_yAxis, _excluded);
const themedProps = useThemeProps({
props: _extends({}, settings, inProps),
name: 'MuiChartsYAxis'
});
const defaultizedProps = _extends({}, defaultProps, themedProps);
const {
position,
disableLine,
disableTicks,
label,
labelStyle,
tickLabelStyle,
tickSize: tickSizeProp,
valueFormatter,
slots,
slotProps,
tickPlacement,
tickLabelPlacement,
tickInterval,
tickLabelInterval,
sx,
offset,
width: axisWidth
} = defaultizedProps;
const theme = useTheme();
const isRtl = useRtl();
const isHydrated = useIsHydrated();
const classes = useUtilityClasses(defaultizedProps);
const {
instance
} = useChartContext();
const drawingArea = useDrawingArea();
const {
left,
top,
width,
height
} = drawingArea;
const tickSize = disableTicks ? 4 : tickSizeProp;
const yTicks = useTicks({
scale: yScale,
tickNumber,
valueFormatter,
tickPlacement,
tickLabelPlacement,
tickInterval,
direction: 'y'
});
const positionSign = position === 'right' ? 1 : -1;
const tickFontSize = typeof tickLabelStyle?.fontSize === 'number' ? tickLabelStyle.fontSize : 12;
const Line = slots?.axisLine ?? 'line';
const Tick = slots?.axisTick ?? 'line';
const TickLabel = slots?.axisTickLabel ?? ChartsText;
const Label = slots?.axisLabel ?? ChartsText;
const defaultTextAnchor = getDefaultTextAnchor((position === 'right' ? -90 : 90) - (tickLabelStyle?.angle ?? 0));
const defaultDominantBaseline = getDefaultBaseline((position === 'right' ? -90 : 90) - (tickLabelStyle?.angle ?? 0));
const axisTickLabelProps = useSlotProps({
elementType: TickLabel,
externalSlotProps: slotProps?.axisTickLabel,
additionalProps: {
style: _extends({}, theme.typography.caption, {
fontSize: tickFontSize,
textAnchor: isRtl ? invertTextAnchor(defaultTextAnchor) : defaultTextAnchor,
dominantBaseline: defaultDominantBaseline
}, tickLabelStyle)
},
className: classes.tickLabel,
ownerState: {}
});
const axisLabelProps = useSlotProps({
elementType: Label,
externalSlotProps: slotProps?.axisLabel,
additionalProps: {
style: _extends({}, theme.typography.body1, {
lineHeight: 1,
fontSize: 14,
angle: positionSign * 90,
textAnchor: 'middle',
dominantBaseline: 'text-before-edge'
}, labelStyle)
},
ownerState: {}
});
const lineSlotProps = useSlotProps({
elementType: Line,
externalSlotProps: slotProps?.axisLine,
additionalProps: {
strokeLinecap: 'square'
},
ownerState: {}
});
const domain = yScale.domain();
const ordinalAxis = isBandScale(yScale);
// 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 'none'.
if (ordinalAxis && domain.length === 0 || !ordinalAxis && domain.some(isInfinity) || position === 'none') {
return null;
}
const labelRefPoint = {
x: positionSign * axisWidth,
y: top + height / 2
};
/* If there's an axis title, the tick labels have less space to render */
const tickLabelsMaxWidth = Math.max(0, axisWidth - (label ? getStringSize(label, axisLabelProps.style).height + AXIS_LABEL_TICK_LABEL_GAP : 0) - tickSize - TICK_LABEL_GAP);
const tickLabels = isHydrated ? shortenLabels(yTicks, drawingArea, tickLabelsMaxWidth, isRtl, axisTickLabelProps.style) : new Map(Array.from(yTicks).map(item => [item, item.formattedValue]));
return /*#__PURE__*/_jsxs(YAxisRoot, {
transform: `translate(${position === 'right' ? left + width + offset : left - offset}, 0)`,
className: classes.root,
sx: sx,
children: [!disableLine && /*#__PURE__*/_jsx(Line, _extends({
y1: top,
y2: top + height,
className: classes.line
}, lineSlotProps)), yTicks.map((item, index) => {
const {
offset: tickOffset,
labelOffset,
value
} = item;
const xTickLabel = positionSign * (tickSize + TICK_LABEL_GAP);
const yTickLabel = labelOffset;
const skipLabel = typeof tickLabelInterval === 'function' && !tickLabelInterval?.(value, index);
const showLabel = instance.isYInside(tickOffset);
const tickLabel = tickLabels.get(item);
if (!showLabel) {
return null;
}
return /*#__PURE__*/_jsxs("g", {
transform: `translate(0, ${tickOffset})`,
className: classes.tickContainer,
children: [!disableTicks && /*#__PURE__*/_jsx(Tick, _extends({
x2: positionSign * tickSize,
className: classes.tick
}, slotProps?.axisTick)), tickLabel !== undefined && !skipLabel && /*#__PURE__*/_jsx(TickLabel, _extends({
x: xTickLabel,
y: yTickLabel,
text: tickLabel
}, axisTickLabelProps))]
}, index);
}), label && isHydrated && /*#__PURE__*/_jsx("g", {
className: classes.label,
children: /*#__PURE__*/_jsx(Label, _extends({}, labelRefPoint, axisLabelProps, {
text: label
}))
})]
});
}
process.env.NODE_ENV !== "production" ? ChartsYAxis.propTypes = {
// ----------------------------- Warning --------------------------------
// | These PropTypes are generated from the TypeScript type definitions |
// | To update them edit the TypeScript types and run "pnpm proptypes" |
// ----------------------------------------------------------------------
axis: PropTypes.oneOf(['y']),
/**
* The id of the axis to render.
* If undefined, it will be the first defined axis.
*/
axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]),
/**
* Override or extend the styles applied to the component.
*/
classes: PropTypes.object,
/**
* If true, the axis line is disabled.
* @default false
*/
disableLine: PropTypes.bool,
/**
* If true, the ticks are disabled.
* @default false
*/
disableTicks: PropTypes.bool,
/**
* The fill color of the axis text.
* @default 'currentColor'
*/
fill: PropTypes.string,
/**
* The label of the axis.
*/
label: PropTypes.string,
/**
* The style applied to the axis label.
*/
labelStyle: PropTypes.object,
/**
* The props used for each component slot.
* @default {}
*/
slotProps: PropTypes.object,
/**
* Overridable component slots.
* @default {}
*/
slots: PropTypes.object,
/**
* The stroke color of the axis line.
* @default 'currentColor'
*/
stroke: PropTypes.string,
sx: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.func, PropTypes.object, PropTypes.bool])), PropTypes.func, PropTypes.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.oneOfType([PropTypes.oneOf(['auto']), PropTypes.array, PropTypes.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.oneOfType([PropTypes.oneOf(['auto']), PropTypes.func]),
/**
* 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.oneOf(['middle', 'tick']),
/**
* The style applied to ticks text.
*/
tickLabelStyle: PropTypes.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.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.number,
/**
* The number of ticks. This number is not guaranteed.
* Not supported by categorical axis (band, points).
*/
tickNumber: PropTypes.number,
/**
* The placement of ticks in regard to the band interval.
* Only used if scale is 'band'.
* @default 'extremities'
*/
tickPlacement: PropTypes.oneOf(['end', 'extremities', 'middle', 'start']),
/**
* The size of the ticks.
* @default 6
*/
tickSize: PropTypes.number
} : void 0;
export { ChartsYAxis };