UNPKG

@mui/x-charts

Version:

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

393 lines (385 loc) 11.7 kB
'use client'; import _extends from "@babel/runtime/helpers/esm/extends"; import * as React from 'react'; import PropTypes from 'prop-types'; import { useTheme } from '@mui/material/styles'; import { useRtl } from '@mui/system/RtlProvider'; import ChartsContinuousGradient from "../internals/components/ChartsAxesGradients/ChartsContinuousGradient.js"; import { useChartId, useDrawingArea } from "../hooks/index.js"; import { getScale } from "../internals/getScale.js"; import { getPercentageValue } from "../internals/getPercentageValue.js"; import { ChartsText } from "../ChartsText/index.js"; import { getStringSize } from "../internals/domUtils.js"; import { useAxis } from "./useAxis.js"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; function getPositionOffset(position, legendBox, svgBox) { let offsetX = 0; let offsetY = 0; switch (position.horizontal) { case 'left': offsetX = 0; break; case 'middle': offsetX = (svgBox.width - legendBox.width) / 2; break; case 'right': default: offsetX = svgBox.width - legendBox.width; break; } switch (position.vertical) { case 'top': offsetY = 0; break; case 'middle': offsetY = (svgBox.height - legendBox.height) / 2; break; case 'bottom': default: offsetY = svgBox.height - legendBox.height; break; } return { offsetX, offsetY }; } /** * Takes placement parameters and element bounding boxes. * Returns the x, y coordinates of the elements. And the textAnchor, dominantBaseline for texts. */ function getElementPositions(text1Box, barBox, text2Box, params) { if (params.direction === 'column') { const text1 = { y: text1Box.height, dominantBaseline: 'auto' }; const text2 = { y: text1Box.height + 2 * params.spacing + barBox.height, dominantBaseline: 'hanging' }; const bar = { y: text1Box.height + params.spacing }; const totalWidth = Math.max(text1Box.width, barBox.width, text2Box.width); const totalHeight = text1Box.height + barBox.height + text2Box.height + 2 * params.spacing; const boundingBox = { width: totalWidth, height: totalHeight }; switch (params.align) { case 'start': return { text1: _extends({}, text1, { textAnchor: 'start', x: 0 }), text2: _extends({}, text2, { textAnchor: 'start', x: 0 }), bar: _extends({}, bar, { x: 0 }), boundingBox }; case 'end': return { text1: _extends({}, text1, { textAnchor: 'end', x: totalWidth }), text2: _extends({}, text2, { textAnchor: 'end', x: totalWidth }), bar: _extends({}, bar, { x: totalWidth - barBox.width }), boundingBox }; case 'middle': default: return { text1: _extends({}, text1, { textAnchor: 'middle', x: totalWidth / 2 }), text2: _extends({}, text2, { textAnchor: 'middle', x: totalWidth / 2 }), bar: _extends({}, bar, { x: totalWidth / 2 - barBox.width / 2 }), boundingBox }; } } else { const text1 = { x: text1Box.width, textAnchor: 'end' }; const text2 = { x: text1Box.width + 2 * params.spacing + barBox.width, textAnchor: 'start' }; const bar = { x: text1Box.width + params.spacing }; const totalHeight = Math.max(text1Box.height, barBox.height, text2Box.height); const totalWidth = text1Box.width + barBox.width + text2Box.width + 2 * params.spacing; const boundingBox = { width: totalWidth, height: totalHeight }; switch (params.align) { case 'start': return { text1: _extends({}, text1, { dominantBaseline: 'hanging', y: 0 }), text2: _extends({}, text2, { dominantBaseline: 'hanging', y: 0 }), bar: _extends({}, bar, { y: 0 }), boundingBox }; case 'end': return { text1: _extends({}, text1, { dominantBaseline: 'auto', y: totalHeight }), text2: _extends({}, text2, { dominantBaseline: 'auto', y: totalHeight }), bar: _extends({}, bar, { y: totalHeight - barBox.height }), boundingBox }; case 'middle': default: return { text1: _extends({}, text1, { dominantBaseline: 'central', y: totalHeight / 2 }), text2: _extends({}, text2, { dominantBaseline: 'central', y: totalHeight / 2 }), bar: _extends({}, bar, { y: totalHeight / 2 - barBox.height / 2 }), boundingBox }; } } } const defaultLabelFormatter = ({ formattedValue }) => formattedValue; function ContinuousColorLegend(props) { const theme = useTheme(); const isRtl = useRtl(); const { id: idProp, minLabel = defaultLabelFormatter, maxLabel = defaultLabelFormatter, scaleType = 'linear', direction, length = '50%', thickness = 5, spacing = 4, align = 'middle', labelStyle = theme.typography.subtitle1, position, axisDirection, axisId } = props; const chartId = useChartId(); const id = idProp ?? `gradient-legend-${chartId}`; const axisItem = useAxis({ axisDirection, axisId }); const { width, height, left, right, top, bottom } = useDrawingArea(); const refLength = direction === 'column' ? height + top + bottom : width + left + right; const size = getPercentageValue(length, refLength); const isReversed = direction === 'column'; const colorMap = axisItem?.colorMap; if (!colorMap || !colorMap.type || colorMap.type !== 'continuous') { return null; } // Define the coordinate to color mapping const colorScale = axisItem.colorScale; const minValue = colorMap.min ?? 0; const maxValue = colorMap.max ?? 100; const scale = getScale(scaleType, [minValue, maxValue], isReversed ? [size, 0] : [0, size]); // Get texts to display const formattedMin = axisItem.valueFormatter?.(minValue, { location: 'legend' }) ?? minValue.toLocaleString(); const formattedMax = axisItem.valueFormatter?.(maxValue, { location: 'legend' }) ?? maxValue.toLocaleString(); const minText = typeof minLabel === 'string' ? minLabel : minLabel({ value: minValue ?? 0, formattedValue: formattedMin }); const maxText = typeof maxLabel === 'string' ? maxLabel : maxLabel({ value: maxValue ?? 0, formattedValue: formattedMax }); const text1 = isReversed ? maxText : minText; const text2 = isReversed ? minText : maxText; const text1Box = getStringSize(text1, _extends({}, labelStyle)); const text2Box = getStringSize(text2, _extends({}, labelStyle)); // Place bar and texts const barBox = direction === 'column' || isRtl && direction === 'row' ? { width: thickness, height: size } : { width: size, height: thickness }; const legendPositions = getElementPositions(text1Box, barBox, text2Box, { spacing, align, direction }); const svgBoundingBox = { width: width + left + right, height: height + top + bottom }; const positionOffset = getPositionOffset(_extends({ horizontal: 'middle', vertical: 'top' }, position), legendPositions.boundingBox, svgBoundingBox); return /*#__PURE__*/_jsxs(React.Fragment, { children: [/*#__PURE__*/_jsx(ChartsContinuousGradient, { isReversed: isReversed, gradientId: id, size: size, direction: direction === 'row' ? 'x' : 'y', scale: scale, colorScale: colorScale, colorMap: colorMap, gradientUnits: "objectBoundingBox" }), /*#__PURE__*/_jsx(ChartsText, { text: text1, x: positionOffset.offsetX + legendPositions.text1.x, y: positionOffset.offsetY + legendPositions.text1.y, style: _extends({ dominantBaseline: legendPositions.text1.dominantBaseline, textAnchor: legendPositions.text1.textAnchor }, labelStyle) }), /*#__PURE__*/_jsx("rect", _extends({ x: positionOffset.offsetX + legendPositions.bar.x, y: positionOffset.offsetY + legendPositions.bar.y }, barBox, { fill: `url(#${id})` })), /*#__PURE__*/_jsx(ChartsText, { text: text2, x: positionOffset.offsetX + legendPositions.text2.x, y: positionOffset.offsetY + legendPositions.text2.y, style: _extends({ dominantBaseline: legendPositions.text2.dominantBaseline, textAnchor: legendPositions.text2.textAnchor }, labelStyle) })] }); } process.env.NODE_ENV !== "production" ? ContinuousColorLegend.propTypes = { // ----------------------------- Warning -------------------------------- // | These PropTypes are generated from the TypeScript type definitions | // | To update them edit the TypeScript types and run "pnpm proptypes" | // ---------------------------------------------------------------------- /** * The alignment of the texts with the gradient bar. * @default 'middle' */ align: PropTypes.oneOf(['end', 'middle', 'start']), /** * The axis direction containing the color configuration to represent. * @default 'z' */ axisDirection: PropTypes.oneOf(['x', 'y', 'z']), /** * The id of the axis item with the color configuration to represent. * @default The first axis item. */ axisId: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), /** * The direction of the legend layout. * The default depends on the chart. */ direction: PropTypes.oneOf(['column', 'row']), /** * A unique identifier for the gradient. * @default auto-generated id */ id: PropTypes.string, /** * The style applied to labels. * @default theme.typography.subtitle1 */ labelStyle: PropTypes.object, /** * The length of the gradient bar. * Can be a number (in px) or a string with a percentage such as '50%'. * The '100%' is the length of the svg. * @default '50%' */ length: PropTypes.oneOfType([PropTypes.number, PropTypes.string]), /** * The label to display at the maximum side of the gradient. * Can either be a string, or a function. * If not defined, the formatted maximal value is display. * @default ({ formattedValue }) => formattedValue */ maxLabel: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), /** * The label to display at the minimum side of the gradient. * Can either be a string, or a function. * @default ({ formattedValue }) => formattedValue */ minLabel: PropTypes.oneOfType([PropTypes.func, PropTypes.string]), /** * The position of the legend. */ position: PropTypes.shape({ horizontal: PropTypes.oneOf(['left', 'middle', 'right']).isRequired, vertical: PropTypes.oneOf(['bottom', 'middle', 'top']).isRequired }), /** * The scale used to display gradient colors. * @default 'linear' */ scaleType: PropTypes.oneOf(['linear', 'log', 'pow', 'sqrt', 'time', 'utc']), /** * The space between the gradient bar and the labels. * @default 4 */ spacing: PropTypes.number, /** * The thickness of the gradient bar. * @default 5 */ thickness: PropTypes.number } : void 0; export { ContinuousColorLegend };