Skip to content

Commit

Permalink
Limit Donut legend to 50% of chart width
Browse files Browse the repository at this point in the history
  • Loading branch information
envex committed Apr 2, 2024
1 parent 9acf2ed commit e371426
Show file tree
Hide file tree
Showing 9 changed files with 211 additions and 40 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

### Changed

- Limit `<DonutChart />` legends from taking up more than 50% of the chart width.

## [12.2.2] - 2024-04-01

Expand Down
44 changes: 20 additions & 24 deletions packages/polaris-viz/src/components/DonutChart/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
useUniqueId,
ChartState,
useChartContext,
estimateStringWidth,
} from '@shopify/polaris-viz-core';
import type {
DataPoint,
Expand All @@ -17,7 +18,6 @@ import type {
} from '@shopify/polaris-viz-core';

import {getContainerAlignmentForLegend} from '../../utilities';
import {estimateLegendItemWidth} from '../Legend';
import type {ComparisonMetricProps} from '../ComparisonMetric';
import {LegendContainer, useLegend} from '../../components/LegendContainer';
import {
Expand All @@ -40,7 +40,6 @@ import {InnerValue, LegendValues} from './components';

const ERROR_ANIMATION_PADDING = 40;
const FULL_CIRCLE = Math.PI * 2;
const MAX_LEGEND_WIDTH_PERCENTAGE = 0.35;
const RADIUS_PADDING = 20;

export interface ChartProps {
Expand Down Expand Up @@ -98,28 +97,8 @@ export function Chart({
? 'vertical'
: 'horizontal';

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

if (estimatedLegendWidth > previous) {
return estimatedLegendWidth;
}

return previous;
}, 0);

const maxLegendWidth =
legendDirection === 'vertical'
? Math.max(
longestLegendWidth,
dimensions.width * MAX_LEGEND_WIDTH_PERCENTAGE,
)
: 0;
legendDirection === 'vertical' ? dimensions.width / 2 : 0;

const {height, width, legend, setLegendDimensions, isLegendMounted} =
useLegend({
Expand All @@ -131,6 +110,19 @@ export function Chart({
maxWidth: maxLegendWidth,
});

const longestLegendValueWidth = legend.reduce((previous, current) => {
const estimatedLegendWidth = estimateStringWidth(
`${labelFormatter(`${current.value || ''}`)}`,
characterWidths,
);

if (estimatedLegendWidth > previous) {
return estimatedLegendWidth;
}

return previous;
}, 0);

const shouldUseColorVisionEvents = Boolean(
width && height && isLegendMounted,
);
Expand All @@ -147,7 +139,10 @@ export function Chart({
},
});

if (!width || !height) return null;
if (!width || !height) {
return null;
}

const diameter = Math.min(height, width);
const radius = diameter / 2;

Expand Down Expand Up @@ -187,6 +182,7 @@ export function Chart({
data={data}
activeIndex={activeIndex}
labelFormatter={labelFormatter}
longestLegendValueWidth={longestLegendValueWidth}
getColorVisionStyles={getColorVisionStyles}
getColorVisionEventAttrs={getColorVisionEventAttrs}
dimensions={{...dimensions, x: 0, y: 0}}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@
width: 100%;
border-collapse: separate;
border-spacing: 0 10px;
table-layout: fixed;
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface LegendContentProps {
activeIndex: number;
dimensions: BoundingRect;
labelFormatter: LabelFormatter;
longestLegendValueWidth: number;
renderHiddenLegendLabel?: RenderHiddenLegendLabel;
getColorVisionStyles: ColorVisionInteractionMethods['getColorVisionStyles'];
getColorVisionEventAttrs: ColorVisionInteractionMethods['getColorVisionEventAttrs'];
Expand All @@ -33,6 +34,7 @@ export function LegendValues({
data: allData,
activeIndex,
labelFormatter,
longestLegendValueWidth,
renderHiddenLegendLabel = (count) => `+${count} more`,
getColorVisionStyles,
getColorVisionEventAttrs,
Expand Down Expand Up @@ -90,6 +92,7 @@ export function LegendValues({
value={value}
trend={trend}
index={index}
longestLegendValueWidth={longestLegendValueWidth}
maxTrendIndicatorWidth={maxTrendIndicatorWidth}
seriesColors={seriesColors}
onDimensionChange={(dimensions) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,10 @@

.Name {
overflow: hidden;
text-wrap: nowrap;
white-space: nowrap;
text-overflow: ellipsis;
padding-left: 4px;
}

.TableSpacer {
padding-left: 30px;
padding-right: 20px;
}

.alignLeft {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ interface Props {
value?: string;
trend?: MetaDataTrendIndicator;
labelFormatter: LabelFormatter;
longestLegendValueWidth: number;
seriesColors: Color[];
maxTrendIndicatorWidth: number;
onDimensionChange: (dimensions: Dimensions) => void;
Expand All @@ -33,6 +34,7 @@ export function LegendValueItem({
value,
index,
labelFormatter,
longestLegendValueWidth,
trend,
seriesColors,
maxTrendIndicatorWidth,
Expand Down Expand Up @@ -73,13 +75,12 @@ export function LegendValueItem({
style={{
color: selectedTheme.legend.labelColor,
}}
title={name}
>
<span>{name}</span>
</td>

<td className={styles.TableSpacer} />

<td className={styles.alignRight}>
<td className={styles.alignRight} width={longestLegendValueWidth}>
<span
style={{
color: selectedTheme.legend.labelColor,
Expand All @@ -89,12 +90,17 @@ export function LegendValueItem({
</span>
</td>

<td
className={styles.alignLeft}
style={{minWidth: maxTrendIndicatorWidth, paddingLeft: '4px'}}
>
<span>{trend && valueExists && <TrendIndicator {...trend} />}</span>
</td>
{trend && valueExists && (
<td
className={styles.alignLeft}
width={maxTrendIndicatorWidth}
style={{minWidth: maxTrendIndicatorWidth, paddingLeft: '4px'}}
>
<span>
<TrendIndicator {...trend} />
</span>
</td>
)}
</tr>
);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import {storiesOf} from '@storybook/react';
import type {PropCombinations} from '../../../chromatic/types';

import {
addWithPropsCombinations,
renderCombinationSections,
} from '../../../chromatic';
import {DonutChart} from '../';
import type {DonutChartProps} from '../DonutChart';
import {DEFAULT_DATA} from './data';

const stories = storiesOf('Chromatic/Components', module).addParameters({
docs: {page: null},
chromatic: {disableSnapshot: false},
});

const DEFAULT_PROPS: PropCombinations<DonutChartProps> = {
data: [DEFAULT_DATA],
comparisonMetric: [
{
metric: '6%',
trend: 'positive',
accessibilityLabel: 'trending up 6%',
},
],
legendPosition: ['left'],
isAnimated: [false],
};

const CHART_SIZE = {style: {width: 500, height: 300}};

const combinations = renderCombinationSections([
[
'Data',
addWithPropsCombinations<DonutChartProps>(
DonutChart,
{
...DEFAULT_PROPS,
},
CHART_SIZE,
),
],
[
'Legend Position',
addWithPropsCombinations(
DonutChart,
{
...DEFAULT_PROPS,
legendPosition: [
'top-left',
'top-right',
'bottom-left',
'bottom-right',
'top',
'right',
'bottom',
'left',
],
},
CHART_SIZE,
),
],
[
'Truncated Legends',
addWithPropsCombinations(
DonutChart,
{
...DEFAULT_PROPS,
data: [
[
{
name: 'This is a long name that will get truncated',
data: [{key: 'april - march', value: 50000}],
metadata: {
trend: {
value: '5%',
},
},
},
{
name: 'This is another long name that will get truncated',
data: [{key: 'april - march', value: 250000}],
metadata: {
trend: {
value: '50%',
direction: 'downward',
trend: 'negative',
},
},
},
{
name: 'This is the last long name that will get truncated',
data: [{key: 'april - march', value: 10000}],
metadata: {
trend: {
value: '100%',
direction: 'upward',
trend: 'positive',
},
},
},
],
],
legendPosition: ['left', 'right'],
showLegendValues: [true, false],
},
CHART_SIZE,
),
],
]);

stories.add('DonutChart', combinations);
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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 TruncatedLegends: Story<DonutChartProps> = Template.bind({});

TruncatedLegends.args = {
...DEFAULT_PROPS,
showLegendValues: true,
legendPosition: 'right',
legendFullWidth: false,
labelFormatter: (value) => `$${value}`,
isAnimated: false,
data: [
{
name: 'This is a long name that will get truncated',
data: [{key: 'april - march', value: 50000}],
metadata: {
trend: {
value: '5%',
},
},
},
{
name: 'This is another long name that will get truncated',
data: [{key: 'april - march', value: 250000}],
metadata: {
trend: {
value: '50%',
direction: 'downward',
trend: 'negative',
},
},
},
{
name: 'This is the last long name that will get truncated',
data: [{key: 'april - march', value: 10000}],
metadata: {
trend: {
value: '100%',
direction: 'upward',
trend: 'positive',
},
},
},
],
};
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,13 @@ export function LegendItem({
paddingRight: LEGEND_ITEM_RIGHT_PADDING,
gap: LEGEND_ITEM_GAP,
// if there is overflow, add a max width and truncate with ellipsis
maxWidth: truncate ? minWidth : undefined,
maxWidth: truncate ? minWidth : '100%',
// if the item width is less than the minWidth, don't set a min width
minWidth: width < minWidth ? undefined : minWidth,
}}
className={style.Legend}
ref={ref}
title={name}
>
{renderSeriesIcon == null ? (
<span
Expand Down

0 comments on commit e371426

Please sign in to comment.