@awsui/components-react
Version:
On July 19th, 2022, we launched [Cloudscape Design System](https://cloudscape.design). Cloudscape is an evolution of AWS-UI. It consists of user interface guidelines, front-end components, design resources, and development tools for building intuitive, en
121 lines • 8.35 kB
JavaScript
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0
'use client';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import clsx from 'clsx';
import { pie } from 'd3-shape';
import { useMergeRefs } from '@awsui/component-toolkit/internal';
import { getBaseProps } from '../internal/base-component';
import Filter from '../internal/components/chart-filter';
import Legend from '../internal/components/chart-legend';
import ChartSeriesMarker from '../internal/components/chart-series-marker';
import ChartStatusContainer, { getChartStatus } from '../internal/components/chart-status-container';
import { ChartWrapper } from '../internal/components/chart-wrapper';
import { fireNonCancelableEvent } from '../internal/events';
import useBaseComponent from '../internal/hooks/use-base-component';
import { useControllable } from '../internal/hooks/use-controllable';
import { useVisualRefresh } from '../internal/hooks/use-visual-mode';
import { applyDisplayName } from '../internal/utils/apply-display-name';
import createCategoryColorScale from '../internal/utils/create-category-color-scale';
import { nodeBelongs } from '../internal/utils/node-belongs';
import useContainerWidth from '../internal/utils/use-container-width';
import InternalPieChart from './pie-chart';
import { getDimensionsBySize } from './utils';
import styles from './styles.css.js';
const PieChart = function PieChart({ fitHeight, variant = 'pie', size = 'medium', hideTitles = false, hideDescriptions = false, hideLegend = false, hideFilter = false, statusType = 'finished', data: externalData = [], i18nStrings = {}, highlightedSegment: controlledHighlightedSegment, visibleSegments: controlledVisibleSegments, onHighlightChange: controlledOnHighlightChange, onFilterChange, additionalFilters, legendTitle, detailPopoverSize = 'medium', ...props }) {
const { __internalRootRef = null } = useBaseComponent('PieChart', {
props: {
fitHeight,
variant,
size,
hideTitles,
hideDescriptions,
hideLegend,
hideFilter,
detailPopoverSize,
},
});
const baseProps = getBaseProps(props);
const containerRef = useRef(null);
const [containerWidth, measureRef] = useContainerWidth();
const data = useMemo(() => {
const colors = createCategoryColorScale(externalData, undefined, it => it.color || null);
return externalData.map((datum, i) => ({
index: i,
color: colors[i],
datum,
}));
}, [externalData]);
const [highlightedSegment = null, setHighlightedSegment] = useControllable(controlledHighlightedSegment, controlledOnHighlightChange, null, {
componentName: 'PieChart',
controlledProp: 'highlightedSegment',
changeHandler: 'onHighlightChange',
});
const [legendSegment, setLegendSegment] = useState(highlightedSegment);
useEffect(() => {
setLegendSegment(controlledHighlightedSegment || null);
}, [controlledHighlightedSegment]);
const [visibleSegments, setVisibleSegments] = useControllable(controlledVisibleSegments, onFilterChange, externalData, {
componentName: 'PieChart',
controlledProp: 'visibleSegments',
changeHandler: 'onFilterChange',
});
const visibleData = useMemo(() => data.filter(d => (visibleSegments === null || visibleSegments === void 0 ? void 0 : visibleSegments.indexOf(d.datum)) !== -1), [data, visibleSegments]);
const filterItems = data === null || data === void 0 ? void 0 : data.map(data => ({
label: data.datum.title,
marker: React.createElement(ChartSeriesMarker, { color: data.color, type: "rectangle" }),
datum: data.datum,
}));
const legendItems = filterItems.filter(d => (visibleSegments === null || visibleSegments === void 0 ? void 0 : visibleSegments.indexOf(d.datum)) !== -1);
const filterChange = useCallback((selectedSeries) => {
setVisibleSegments(selectedSeries);
fireNonCancelableEvent(onFilterChange, {
visibleSegments: selectedSeries,
});
}, [setVisibleSegments, onFilterChange]);
const onHighlightChange = useCallback((segment) => {
setLegendSegment(segment);
setHighlightedSegment(segment);
fireNonCancelableEvent(controlledOnHighlightChange, { highlightedSegment: segment });
}, [controlledOnHighlightChange, setHighlightedSegment]);
const onBlur = (event) => {
if (event.relatedTarget && !nodeBelongs(containerRef.current, event.relatedTarget)) {
if (highlightedSegment) {
onHighlightChange(null);
}
setLegendSegment(null);
}
};
const mergedRef = useMergeRefs(containerRef, measureRef, __internalRootRef);
const { pieData, dataSum } = useMemo(() => {
const dataSum = visibleData.reduce((sum, d) => sum + d.datum.value, 0);
const pieFactory = pie()
// Minimum 1% segment size
.value(d => (d.datum.value < dataSum / 100 ? dataSum / 100 : d.datum.value))
.sort(null);
// Filter out segments with value of zero or below
const pieData = pieFactory(visibleData.filter(d => d.datum.value > 0));
return { pieData, dataSum };
}, [visibleData]);
const hasNoData = !externalData || externalData.length === 0;
const { isEmpty, showChart } = getChartStatus({ externalData: data, visibleData: pieData, statusType });
// Pie charts have a special condition for empty/noMatch due to how zero-value segments are handled.
const isNoMatch = isEmpty && visibleData.length !== data.length;
const showFilters = statusType === 'finished' && !hasNoData && (additionalFilters || !hideFilter);
const reserveLegendSpace = !showChart && !hideLegend;
const reserveFilterSpace = statusType !== 'finished' && !isNoMatch && (!hideFilter || additionalFilters);
const hasLabels = !(hideTitles && hideDescriptions);
const isRefresh = useVisualRefresh();
const defaultDimensions = getDimensionsBySize({ size, hasLabels, visualRefresh: isRefresh });
const radius = defaultDimensions.outerRadius;
const height = 2 * (radius + defaultDimensions.padding + (hasLabels ? defaultDimensions.paddingLabels : 0));
return (React.createElement(ChartWrapper, { ref: mergedRef, fitHeight: !!fitHeight, ...baseProps, className: clsx(baseProps.className, styles.root), contentClassName: clsx(styles.content, styles[`content--${defaultDimensions.size}`], {
[styles['content--without-labels']]: !hasLabels,
[styles['content--fit-height']]: fitHeight,
}), defaultFilter: showFilters && !hideFilter ? (React.createElement(Filter, { series: filterItems, onChange: filterChange, selectedSeries: visibleSegments, i18nStrings: i18nStrings })) : null, additionalFilters: showFilters ? additionalFilters : null, reserveFilterSpace: !!reserveFilterSpace, reserveLegendSpace: !!reserveLegendSpace, chartStatus: React.createElement(ChartStatusContainer, { isEmpty: isEmpty, isNoMatch: isNoMatch, showChart: showChart, statusType: statusType, empty: props.empty, noMatch: props.noMatch, loadingText: props.loadingText, errorText: props.errorText, recoveryText: props.recoveryText, onRecoveryClick: props.onRecoveryClick }), chart: showChart ? (React.createElement(InternalPieChart, { ...props, variant: variant, size: size, height: height, fitHeight: fitHeight, data: externalData, width: containerWidth, hideTitles: hideTitles, hideDescriptions: hideDescriptions, i18nStrings: i18nStrings, onHighlightChange: onHighlightChange, highlightedSegment: highlightedSegment, legendSegment: legendSegment, detailPopoverSize: detailPopoverSize, pieData: pieData, dataSum: dataSum })) : null, legend: !hideLegend &&
!hasNoData &&
statusType === 'finished' && (React.createElement(Legend, { series: legendItems, highlightedSeries: legendSegment, legendTitle: legendTitle, ariaLabel: i18nStrings === null || i18nStrings === void 0 ? void 0 : i18nStrings.legendAriaLabel, onHighlightChange: onHighlightChange, plotContainerRef: containerRef })), onBlur: onBlur }));
};
applyDisplayName(PieChart, 'PieChart');
export default PieChart;
//# sourceMappingURL=index.js.map