Skip to content

Commit

Permalink
Add legend with values + trend indicator data
Browse files Browse the repository at this point in the history
  • Loading branch information
michaelnesen committed Dec 6, 2023
1 parent 90d2535 commit 3ab8830
Show file tree
Hide file tree
Showing 16 changed files with 269 additions and 21 deletions.
6 changes: 5 additions & 1 deletion packages/polaris-viz/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).

<!-- ## Unreleased -->
## Unreleased

### Added

- Added support for showing values and trends in `<DonutChart />` legend with new prop `showLegendValues`.

## [10.1.0] - 2023-12-04

Expand Down
56 changes: 42 additions & 14 deletions packages/polaris-viz/src/components/DonutChart/Chart.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Fragment, useState} from 'react';
import {Fragment, useMemo, useState} from 'react';
import {pie} from 'd3-shape';
import {
clamp,
Expand Down Expand Up @@ -27,14 +27,15 @@ import {
} from '../../hooks';
import {Arc} from '../Arc';
import type {
ColorVisionInteractionMethods,
LegendPosition,
RenderInnerValueContent,
RenderLegendContent,
} from '../../types';
import {ChartSkeleton} from '../../components/ChartSkeleton';

import styles from './DonutChart.scss';
import {InnerValue} from './components';
import {InnerValue, LegendValues} from './components';

const ERROR_ANIMATION_PADDING = 40;
const FULL_CIRCLE = Math.PI * 2;
Expand All @@ -46,6 +47,7 @@ export interface ChartProps {
labelFormatter: LabelFormatter;
legendPosition: LegendPosition;
showLegend: boolean;
showLegendValues: boolean;
state: ChartState;
theme: string;
accessibilityLabel?: string;
Expand All @@ -63,6 +65,7 @@ export function Chart({
labelFormatter,
legendPosition = 'right',
showLegend,
showLegendValues,
state,
theme,
accessibilityLabel = '',
Expand Down Expand Up @@ -92,22 +95,24 @@ export function Chart({
? 'vertical'
: 'horizontal';

const longestLegendWidth = data.reduce((previous, current) => {
const estimatedLegendWidth = estimateLegendItemWidth(
current.name ?? '',
characterWidths,
);
const longestLegendWidth = useMemo(() => {
return data.reduce((previous, current) => {
const estimatedLegendWidth = estimateLegendItemWidth(
`${current.name ?? ''} ${current.data[0].value} `,
characterWidths,
);

if (estimatedLegendWidth > previous) {
return estimatedLegendWidth;
}
if (estimatedLegendWidth > previous) {
return estimatedLegendWidth;
}

return previous;
}, 0);
return previous;
}, 0);
}, [characterWidths, data]);

const maxLegendWidth =
legendDirection === 'vertical'
? Math.min(
? Math.max(
longestLegendWidth,
dimensions.width * MAX_LEGEND_WIDTH_PERCENTAGE,
)
Expand Down Expand Up @@ -167,6 +172,25 @@ export function Chart({
const containerAlignmentStyle =
getContainerAlignmentForLegend(legendPosition);

const renderLegendContentWithValues = ({
getColorVisionStyles,
getColorVisionEventAttrs,
}: ColorVisionInteractionMethods) => {
return (
<LegendValues
data={data}
labelFormatter={labelFormatter}
getColorVisionStyles={getColorVisionStyles}
getColorVisionEventAttrs={getColorVisionEventAttrs}
/>
);
};

const shouldRenderLegendContentWithValues =
!renderLegendContent &&
showLegendValues &&
(legendPosition === 'right' || legendPosition === 'left');

return (
<div className={styles.DonutWrapper} style={containerAlignmentStyle}>
<div className={styles.Donut}>
Expand Down Expand Up @@ -258,7 +282,11 @@ export function Chart({
direction={legendDirection}
position={legendPosition}
maxWidth={maxLegendWidth}
renderLegendContent={renderLegendContent}
renderLegendContent={
shouldRenderLegendContentWithValues
? renderLegendContentWithValues
: renderLegendContent
}
/>
)}
</div>
Expand Down
5 changes: 5 additions & 0 deletions packages/polaris-viz/src/components/DonutChart/DonutChart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ import type {
} from '../../types';

import {Chart} from './Chart';
import type {DonutChartDataSeries} from './types';

export type DonutChartProps = {
data: DonutChartDataSeries[];
comparisonMetric?: ComparisonMetricProps;
showLegend?: boolean;
showLegendValues?: boolean;
labelFormatter?: LabelFormatter;
legendFullWidth?: boolean;
legendPosition?: LegendPosition;
Expand All @@ -32,6 +35,7 @@ export function DonutChart(props: DonutChartProps) {
theme = defaultTheme,
comparisonMetric,
showLegend = true,
showLegendValues = false,
labelFormatter = (value) => `${value}`,
legendFullWidth,
legendPosition = 'left',
Expand Down Expand Up @@ -61,6 +65,7 @@ export function DonutChart(props: DonutChartProps) {
labelFormatter={labelFormatter}
comparisonMetric={comparisonMetric}
showLegend={showLegend}
showLegendValues={showLegendValues}
legendFullWidth={legendFullWidth}
legendPosition={legendPosition}
renderInnerValueContent={renderInnerValueContent}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ export function InnerValue({
);

const activeValueExists = activeValue !== null && activeValue !== undefined;
const valueToDisplay = activeValueExists ? activeValue : animatedTotalValue;
const valueToDisplay = activeValueExists
? labelFormatter(activeValue)
: animatedTotalValue;

const innerContent = renderInnerValueContent?.({
activeValue,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
.Table {
min-width: 200px;
width: 100%;
border-collapse: separate;
border-spacing: 0 10px;

.alignLeft {
text-align: left;
}

.alignRight {
text-align: right;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import type {ColorVisionInteractionMethods, DataSeries} from 'index';
import type {LabelFormatter} from '@shopify/polaris-viz-core';
import {useTheme} from '@shopify/polaris-viz-core';

import {TrendIndicator} from '../../../TrendIndicator';
import {getTrendIndicatorData} from '../../../../utilities/getTrendIndicatorData';
import {SquareColorPreview} from '../../../../components/SquareColorPreview';

import styles from './LegendValues.scss';

interface LegendContentProps {
data: DataSeries[];
labelFormatter: LabelFormatter;
getColorVisionStyles: ColorVisionInteractionMethods['getColorVisionStyles'];
getColorVisionEventAttrs: ColorVisionInteractionMethods['getColorVisionEventAttrs'];
}

export function LegendValues({
data,
labelFormatter,
getColorVisionStyles,
getColorVisionEventAttrs,
}: LegendContentProps) {
const selectedTheme = useTheme();

const maxTrendIndicatorWidth = data.reduce((maxWidth, {metadata}) => {
if (!metadata?.trend) {
return maxWidth;
}

const {trendIndicatorWidth} = getTrendIndicatorData(metadata.trend);

return Math.max(maxWidth, trendIndicatorWidth);
}, 0);

return (
<table className={styles.Table}>
{data.map(({name, data, metadata}, index) => {
const value = data[0].value || 0;

return (
<tr
key={name}
style={{
...getColorVisionStyles(index),
}}
{...getColorVisionEventAttrs(index)}
>
<td
style={{
width: '12px',
}}
>
<SquareColorPreview
color={selectedTheme.seriesColors.upToEight[index]}
/>
</td>

<td style={{paddingLeft: '4px'}}>
<span
style={{
color: selectedTheme.legend.labelColor,
}}
className={styles.Name}
>
{name}
</span>
</td>

<td style={{paddingLeft: '30px'}} />

<td className={styles.alignRight}>
<span
style={{
color: selectedTheme.legend.labelColor,
}}
>
{labelFormatter(value)}
</span>
</td>

<td
className={styles.alignLeft}
style={{minWidth: maxTrendIndicatorWidth}}
>
<span>
{metadata?.trend && <TrendIndicator {...metadata.trend} />}
</span>
</td>
</tr>
);
})}
</table>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export {LegendValues} from './LegendValues';
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export {InnerValue} from './InnerValue';
export {LegendValues} from './LegendValues';
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import type {Story} from '@storybook/react';

export {META as default} from './meta';

import type {DonutChartProps} from '../../DonutChart';

import {DEFAULT_PROPS, Template} from './data';

export const WithLegendValues: Story<DonutChartProps> = Template.bind({});

WithLegendValues.args = {
...DEFAULT_PROPS,
showLegend: true,
showLegendValues: true,
legendPosition: 'right',
labelFormatter: (value) => `$${value}`,
data: [
{
name: 'Shopify Payments',
data: [{key: 'april - march', value: 50000}],
metadata: {
trend: {
value: '5%',
},
},
},
{
name: 'Paypal',
data: [{key: 'april - march', value: 25000}],
metadata: {
trend: {
value: '50%',
direction: 'downward',
trend: 'negative',
},
},
},
{
name: 'Other',
data: [{key: 'april - march', value: 10000}],
metadata: {
trend: {
value: '100%',
direction: 'upward',
trend: 'positive',
},
},
},
{
name: 'Amazon Pay',
data: [{key: 'april - march', value: 5000}],
metadata: {
trend: {
direction: 'upward',
trend: 'positive',
},
},
},
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {
LEGEND_FULL_WIDTH_ARGS,
LEGEND_POSITION_ARGS,
RENDER_LEGEND_CONTENT_ARGS,
SHOW_LEGEND_ARGS,
SHOW_LEGEND_VALUES_ARGS,
THEME_CONTROL_ARGS,
} from '../../../storybook/constants';
import type {DonutChartProps} from '../DonutChart';
Expand All @@ -29,6 +31,8 @@ export const META: Meta<DonutChartProps> = {
data: DATA_SERIES_ARGS,
legendFullWidth: LEGEND_FULL_WIDTH_ARGS,
legendPosition: LEGEND_POSITION_ARGS,
showLegend: SHOW_LEGEND_ARGS,
showLegendValues: SHOW_LEGEND_VALUES_ARGS,
renderLegendContent: RENDER_LEGEND_CONTENT_ARGS,
theme: THEME_CONTROL_ARGS,
state: CHART_STATE_CONTROL_ARGS,
Expand Down
13 changes: 13 additions & 0 deletions packages/polaris-viz/src/components/DonutChart/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type {DataSeries} from '@shopify/polaris-viz-core/src/types';

import type {TrendIndicatorProps} from '../TrendIndicator';

export type MetaDataTrendIndicator = Omit<TrendIndicatorProps, 'theme'>;

export interface MetaData {
trend?: MetaDataTrendIndicator;
}

export interface DonutChartDataSeries extends DataSeries {
metadata?: MetaData;
}
2 changes: 1 addition & 1 deletion packages/polaris-viz/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,5 @@ export {ChartSkeleton} from './ChartSkeleton';
export {ComboChart} from './ComboChart';
export type {ComboChartProps} from './ComboChart';
export {XAxis} from './XAxis';
export {TrendIndicator} from './TrendIndicator';
export {TrendIndicator, estimateTrendIndicatorWidth} from './TrendIndicator';
export {LineChartRelational} from './LineChartRelational';
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
clamp,
} from '@shopify/polaris-viz-core';

import {getTrendIndicatorData} from '../../../utilities/getTrendIndicatorData';
import {TREND_INDICATOR_HEIGHT, TrendIndicator} from '../../TrendIndicator';
import {getHoverZoneOffset} from '../../../utilities';
import {
Expand All @@ -21,7 +22,6 @@ import {getGradientDefId} from '../GradientDefs';

import {Label, Bar, LabelWrapper} from './components';
import styles from './HorizontalBars.scss';
import {getTrendIndicatorData} from './utilities/getTrendIndicatorData';

const SERIES_DELAY = 150;

Expand Down
Loading

0 comments on commit 3ab8830

Please sign in to comment.