UNPKG

@talend/react-components

Version:
330 lines (318 loc) 10.5 kB
import PropTypes from 'prop-types'; import { arc } from 'd3'; import { withTranslation } from 'react-i18next'; import I18N_DOMAIN_COMPONENTS from '../constants'; import getDefaultT from '../translate'; import Skeleton from '../Skeleton'; import pieChartCssModule from './PieChart.module.scss'; import { getTheme } from '../theme'; import { omit } from "lodash"; import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; const theme = getTheme(pieChartCssModule); export const PIECHART_CONSTANTS = { MIN_SIZE: 20, MAX_SIZE: 100, MAX_PERCENT: 100, BASE_INNER_RADIUS: 6, BASE_OUTER_RADIUS: 9, BASE_PAD_ANGLE: 0.2, INNER_RADIUS_PER_PIXEL: 0.4, OUTER_RADIUS_PER_PIXEL: 0.45, PAD_ANGLE_PER_PIXEL: 0.0013 }; export const PIECHART_SIZES = { SMALL: 'small', MEDIUM: 'medium', LARGE: 'large', XLARGE: 'xlarge', XXLARGE: 'xxlarge' }; const displaySizes = { small: 20, medium: 35, large: 50, xlarge: 80, xxlarge: 100 }; // we need just one instance of this, it's just a generator const arcGen = arc(); /** * This function return the angle for a given percentage * @param {number} percentage the percentage to calculate the angle */ export function getAngle(percentage) { return percentage * 2 / 100 * Math.PI; } /** * This function return the showed value on the chart * @param {array} model the pie chart model * @param {index} index current index showed */ function getShowedValue(model, index) { if (!model) { return {}; } return model[index]; } /** * This function is just a sort * @param {object} a an element with a percentageShown to compare * @param {object} b an other element with a percentageShown to compare */ function sortElements(a, b) { return b.percentageShown - a.percentageShown; } /** * this function generates chart's empty part * @param {array} values the values shown in the graph * @param {object} size the current size * @param {function} arcGen the arc generator * @param {number} minimumPercentage the minimum percentage to be shown */ export function getEmptyPartCircle(values, size, minimumPercentage) { const allPercentages = values.reduce((acc, value) => acc + value.percentageShown, 0); if (allPercentages >= PIECHART_CONSTANTS.MAX_PERCENT - minimumPercentage) { return null; } const arcGenerated = arcGen({ innerRadius: size.innerRadius, outerRadius: size.outerRadius, padAngle: size.padAngle, startAngle: getAngle(allPercentages), endAngle: Math.PI * 2 }); return /*#__PURE__*/_jsx("path", { className: theme('tc-pie-chart-color-alto'), d: arcGenerated, transform: `translate(${size.svgSize / 2},${size.svgSize / 2})` }); } /** * It increase values below minPercentage & decrease values above if some are increased * setMinimumPercentage([{percentage: 50}, {percentage: 2}], 5) would result on * [{percentage: 50, percentageShown: 47}, {percentage: 2, percentageShown: 5}] * @param {*} values the set of values * @param {*} minimumPercentage the minimum value we have to show * @param {*} amountToSubtract the amount to decrease */ export function distributePercentages(values, minimumPercentage, amountToSubtract) { /** * This function decrease the percentage shown & the amount to subtract by 1 * @param {object} element the current element */ function decreaseElement(element) { if (amountToSubtract > 0) { // eslint-disable-next-line no-param-reassign element.percentageShown -= 1; // eslint-disable-next-line no-param-reassign amountToSubtract -= 1; } } while (amountToSubtract > 0) { const elementsToDecrease = values.filter(value => value.percentageShown > minimumPercentage).sort(sortElements); if (elementsToDecrease.length > 0) { elementsToDecrease.forEach(decreaseElement); } else { // eslint-disable-next-line no-param-reassign amountToSubtract = 0; } } return values; } /** * This function sets minimum percentage show with the minimum percentage shown * @param {array} values the set of values * @param {number} minimumPercentage the minimum value we have to show */ export function setMinimumPercentage(model, minimumPercentage) { if (!model) { return []; } let amountToSubtract = 0; const valuesMins = model.map(value => { if (value.percentage && value.percentage < minimumPercentage) { amountToSubtract += minimumPercentage - value.percentage; return { ...value, percentageShown: minimumPercentage }; } return { ...value, percentageShown: value.percentage }; }); return distributePercentages(valuesMins, minimumPercentage, amountToSubtract); } /** * This function return the sum of the percentages below the given index * @param {array} values the values to get when we have to start * @param {number} index the current index */ export function getPercentageToIndex(values, index) { return values.reduce((acc, value, i) => { if (i < index) { return acc + value.percentageShown; } return acc; }, 0); } /** * This function return the percentage label rounded or with some prefix * @param {number} percentage percentage to show as label */ function getPercentageAndPrefix(percentage) { if (percentage > 0 && percentage < 1) { return { prefix: '< ', percentage: 1 }; } else if (percentage > 99 && percentage < 100) { return { prefix: '> ', percentage: 99 }; } return { prefix: '', percentage: Math.round(percentage) }; } /** * This function return the label or nothing if the label is npt passed or hidden * @param {boolean} hideLabel tell if the label has to be hidden or not * @param {number} labelValue the label value ( percentage ) * @param {function} t translate function */ export function getLabel(hideLabel, labelValue, t) { if (!hideLabel && labelValue.percentage != null) { const { percentage, prefix } = getPercentageAndPrefix(labelValue.percentage); const labelPercentage = t('PIE_CHART_PERCENTAGE', { defaultValue: '{{percentage}}%', percentage }); return `${prefix}${labelPercentage}`; } return ''; } /** * This function generate a part of circle for a value * @param {object} value the value to generate * @param {number} index the current index * @param {array} values the values to get when we have to start * @param {object} size the current graph size */ export function getCircle(value, index, values, size) { const percentagesDone = getPercentageToIndex(values, index); const arcGenerated = arcGen({ innerRadius: size.innerRadius, outerRadius: size.outerRadius, padAngle: size.padAngle, startAngle: getAngle(percentagesDone), endAngle: getAngle(percentagesDone + value.percentageShown) }); return /*#__PURE__*/_jsx("path", { d: arcGenerated, className: theme(`tc-pie-chart-color-${value.color}`), transform: `translate(${size.svgSize / 2},${size.svgSize / 2})` }, index); } /** * This function return useful stuff to build the graph of the loader * @param {number} size the size in px of the graph * @param {string} display the chosen display if given */ export function getDisplaySize(size, display) { let currentSize = size; if (!currentSize && display) { currentSize = displaySizes[display]; } const pixelNumber = currentSize - PIECHART_CONSTANTS.MIN_SIZE; return { svgSize: currentSize, innerRadius: parseInt(PIECHART_CONSTANTS.BASE_INNER_RADIUS + PIECHART_CONSTANTS.INNER_RADIUS_PER_PIXEL * pixelNumber, 10), outerRadius: parseInt(PIECHART_CONSTANTS.BASE_OUTER_RADIUS + PIECHART_CONSTANTS.OUTER_RADIUS_PER_PIXEL * pixelNumber, 10), padAngle: PIECHART_CONSTANTS.BASE_PAD_ANGLE - PIECHART_CONSTANTS.PAD_ANGLE_PER_PIXEL * pixelNumber }; } export function PieChartIconComponent({ display, hideLabel, labelIndex, loading, minimumPercentage, model, size, t, ...rest }) { const sizeObject = getDisplaySize(size, display); if (loading) { return /*#__PURE__*/_jsxs("span", { className: theme('tc-pie-chart-loading'), "aria-busy": "true", "aria-label": t('PIE_CHART_LOADING', { defaultValue: 'Loading chart' }), children: [/*#__PURE__*/_jsx(Skeleton, { type: Skeleton.TYPES.circle, width: sizeObject.svgSize, height: sizeObject.svgSize, className: theme('tc-pie-chart-loading-circle') }), !hideLabel && /*#__PURE__*/_jsx(Skeleton, { type: Skeleton.TYPES.text, size: Skeleton.SIZES.small })] }); } const labelValue = getShowedValue(model, labelIndex); const preparedValues = setMinimumPercentage(model, minimumPercentage); // Here we are omitting the props from i18n, // to keep only the event listener from the TooltipTrigger. const omitI18N = omit(rest, ['i18n', 'tReady']); return /*#__PURE__*/_jsxs("span", { className: theme('tc-pie-chart-icon'), children: [/*#__PURE__*/_jsxs("svg", { width: sizeObject.svgSize, height: sizeObject.svgSize, className: theme('tc-pie-chart-icon-graph'), style: { width: sizeObject.svgSize, height: sizeObject.svgSize }, ...omitI18N, children: [preparedValues.map((value, index) => getCircle(value, index, preparedValues, sizeObject)), getEmptyPartCircle(preparedValues, sizeObject, minimumPercentage)] }), /*#__PURE__*/_jsx("div", { className: theme(`tc-pie-chart-color-${labelValue.color}`), children: getLabel(hideLabel, labelValue, t) })] }); } export const pieChartIconPropTypes = { display: PropTypes.oneOf(['small', 'medium', 'large', 'xlarge', 'xxlarge']), hideLabel: PropTypes.bool, labelIndex: PropTypes.number, loading: PropTypes.bool, minimumPercentage: PropTypes.number, model: PropTypes.arrayOf(PropTypes.shape({ color: PropTypes.oneOf(['rio-grande', 'chestnut-rose', 'lightning-yellow', 'dove-gray', 'silver-chalice', 'jaffa']), percentage: PropTypes.number.isRequired }).isRequired), size: PropTypes.string }; PieChartIconComponent.propTypes = { ...pieChartIconPropTypes, t: PropTypes.func }; PieChartIconComponent.defaultProps = { labelIndex: 0, minimumPercentage: 5, display: 'small', t: getDefaultT() }; PieChartIconComponent.displayName = 'PieChartIcon'; export default withTranslation(I18N_DOMAIN_COMPONENTS)(PieChartIconComponent); //# sourceMappingURL=PieChartIcon.component.js.map