Skip to content

Commit

Permalink
feat(d3): Add text for center in donut charts (#389)
Browse files Browse the repository at this point in the history
* feat(d3): add text for center in donut charts

* add utils

* fix

* fix story

* fix type
  • Loading branch information
kuzmadom authored Jan 19, 2024
1 parent 18cd80e commit da86362
Show file tree
Hide file tree
Showing 9 changed files with 113 additions and 1 deletion.
8 changes: 8 additions & 0 deletions src/plugins/d3/__stories__/pie/Basic.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {settings} from '../../../../libs';
import {D3Plugin} from '../..';
import {BasicPie} from '../../examples/pie/Basic';
import {Donut} from '../../examples/pie/Donut';
import {DonutWithTotals} from '../../examples/pie/DonutWithTotals';

const ChartStory = ({Chart}: {Chart: React.FC}) => {
const [shown, setShown] = React.useState(false);
Expand Down Expand Up @@ -41,6 +42,13 @@ export const BasicDonutStory: StoryObj<typeof ChartStory> = {
},
};

export const DonutWithTotalsStory: StoryObj<typeof ChartStory> = {
name: 'Donut with totals',
args: {
Chart: DonutWithTotals,
},
};

export default {
title: 'Plugins/D3/Pie',
decorators: [withKnobs],
Expand Down
44 changes: 44 additions & 0 deletions src/plugins/d3/examples/pie/DonutWithTotals.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import React from 'react';
import {groups} from 'd3';
import {ChartKit} from '../../../../components/ChartKit';
import type {ChartKitWidgetData} from '../../../../types';
import {ExampleWrapper} from '../ExampleWrapper';
import nintendoGames from '../nintendoGames';
import {CustomShapeRenderer} from '../../utils';

function prepareData() {
const gamesByPlatform = groups(nintendoGames, (d) => d.esrb_rating || 'unknown');
return gamesByPlatform.map(([value, games]) => ({
name: value,
value: games.length,
}));
}

export const DonutWithTotals = () => {
const data = prepareData();
const totals = data.reduce((sum, d) => sum + d.value, 0);

const widgetData: ChartKitWidgetData = {
series: {
data: [
{
type: 'pie',
innerRadius: '50%',
data: data,
renderCustomShape: CustomShapeRenderer.pieCenterText(`${totals}`),
},
],
},
legend: {enabled: true},
title: {
text: 'ESRB ratings',
style: {fontSize: '12px', fontWeight: 'normal'},
},
};

return (
<ExampleWrapper>
<ChartKit type="d3" data={widgetData} />
</ExampleWrapper>
);
};
2 changes: 1 addition & 1 deletion src/plugins/d3/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import {ChartKitPlugin} from '../../types';

export * from './types';

export * from './utils';
/**
* It is an experemental plugin
*
Expand Down
1 change: 1 addition & 0 deletions src/plugins/d3/renderer/hooks/useSeries/prepare-pie.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ export function preparePieSeries(args: PreparePieSeriesArgs) {
},
},
},
renderCustomShape: series.renderCustomShape,
};

return result;
Expand Down
1 change: 1 addition & 0 deletions src/plugins/d3/renderer/hooks/useSeries/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export type PreparedPieSeries = {
halo: PreparedHaloOptions;
};
};
renderCustomShape?: PieSeries['renderCustomShape'];
} & BasePreparedSeries;

export type PreparedLineSeries = {
Expand Down
15 changes: 15 additions & 0 deletions src/plugins/d3/renderer/hooks/useShapes/pie/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export function PieSeriesShapes(args: PreparePieSeriesArgs) {
.style('stroke', (pieData) => pieData.borderColor)
.style('stroke-width', (pieData) => pieData.borderWidth);

// Render halo appearing outside the hovered slice
shapesSelection
.selectAll('halo')
.data((pieData) => {
Expand All @@ -77,6 +78,7 @@ export function PieSeriesShapes(args: PreparePieSeriesArgs) {
.attr('z-index', -1)
.attr('visibility', getHaloVisibility);

// Render segments
shapesSelection
.selectAll(segmentSelector)
.data((pieData) => pieData.segments)
Expand Down Expand Up @@ -128,6 +130,19 @@ export function PieSeriesShapes(args: PreparePieSeriesArgs) {
.attr('stroke-linecap', 'round')
.style('fill', 'none');

// Render custom shapes if defined
shapesSelection.each(function (d, index, nodes) {
const customShape = d.series.renderCustomShape?.({
series: {
innerRadius: d.innerRadius,
},
});

if (customShape) {
(nodes[index] as Element).append(customShape as Node);
}
});

const eventName = `hover-shape.pie`;
const hoverOptions = get(seriesOptions, 'pie.states.hover');
const inactiveOptions = get(seriesOptions, 'pie.states.inactive');
Expand Down
5 changes: 5 additions & 0 deletions src/plugins/d3/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import {pieCenterText} from './pie-center-text';

export const CustomShapeRenderer = {
pieCenterText,
};
31 changes: 31 additions & 0 deletions src/plugins/d3/utils/pie-center-text.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import {create} from 'd3';
import get from 'lodash/get';

import {getLabelsSize} from '../renderer/utils';

const MAX_FONT_SIZE = 64;

export function pieCenterText(text: string, options?: {padding?: number}) {
if (!text) {
return undefined;
}

const padding = get(options, 'padding', 12);

return function (args: {series: {innerRadius: number}}) {
let fontSize = MAX_FONT_SIZE;

const textSize = getLabelsSize({labels: [text], style: {fontSize: `${fontSize}px`}});
fontSize = (fontSize * (args.series.innerRadius - padding) * 2) / textSize.maxWidth;

const container = create('svg:g');
container
.append('text')
.text(text)
.attr('text-anchor', 'middle')
.attr('alignment-baseline', 'middle')
.style('font-size', `${fontSize}px`);

return container.node();
};
}
7 changes: 7 additions & 0 deletions src/types/widget-data/pie.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import {BaseType} from 'd3';
import {SeriesType} from '../../constants';
import type {BaseSeries, BaseSeriesData} from './base';
import {ChartKitWidgetLegend, RectLegendSymbolOptions} from './legend';
Expand Down Expand Up @@ -74,4 +75,10 @@ export type PieSeries<T = any> = BaseSeries & {
* */
connectorCurve?: ConnectorCurve;
};
/**
* Function for adding custom svg nodes for a series
*
* @return BaseType
* */
renderCustomShape?: (args: {series: {innerRadius: number}}) => BaseType;
};

0 comments on commit da86362

Please sign in to comment.