Skip to content

Commit

Permalink
feat(D3 plugin): add allowOverlap dataLabels option (#347)
Browse files Browse the repository at this point in the history
* feat(D3 plugin): add allowOverlap dataLabels option

* fix review(1)
  • Loading branch information
kuzmadom authored Nov 21, 2023
1 parent 4bdc4d2 commit 760e5d0
Show file tree
Hide file tree
Showing 23 changed files with 574 additions and 126 deletions.
2 changes: 2 additions & 0 deletions src/components/ChartKit.scss
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@
--highcharts-tooltip-alternate-bg: var(--yc-color-base-generic);
--highcharts-tooltip-text-complementary: var(--yc-color-text-complementary);
--highcharts-holiday-band: var(--yc-color-base-generic);

--d3-data-labels: var(--yc-color-text-complementary);
}
14 changes: 14 additions & 0 deletions src/plugins/d3/__stories__/Showcase.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,15 @@ import {Loader} from '../../../components/Loader/Loader';
import {BasicBarXChart} from '../examples/bar-x/Basic';
import {GroupedColumns} from '../examples/bar-x/GroupedColumns';
import {StackedColumns} from '../examples/bar-x/StackedColumns';
import {DataLabels as BarXDataLabels} from '../examples/bar-x/DataLabels';
import {Basic as BasicBarY} from '../examples/bar-y/Basic';
import {GroupedColumns as GroupedColumnsBarY} from '../examples/bar-y/GroupedColumns';
import {StackedColumns as StackedColumnsBarY} from '../examples/bar-y/StackedColumns';
import {Container, Row, Col, Text} from '@gravity-ui/uikit';
import {BasicPie} from '../examples/pie/Basic';
import {Basic as BasicScatter} from '../examples/scatter/Basic';
import {Basic as BasicLine} from '../examples/line/Basic';
import {DataLabels as LineWithDataLabels} from '../examples/line/DataLabels';
import {Donut} from '../examples/pie/Donut';
import {LineAndBarXCombinedChart} from '../examples/combined/LineAndBarX';

Expand All @@ -38,6 +40,10 @@ const ShowcaseStory = () => {
<Text variant="subheader-1">Basic line chart</Text>
<BasicLine />
</Col>
<Col>
<Text variant="subheader-1">Line chart with data labels</Text>
<LineWithDataLabels />
</Col>
</Row>
<Row space={1}>
<Text variant="header-2">Bar-x charts</Text>
Expand All @@ -56,6 +62,14 @@ const ShowcaseStory = () => {
<StackedColumns />
</Col>
</Row>
<Row space={3} style={{minHeight: 280}}>
<Col>
<Text variant="subheader-1">Bar-x chart with data labels</Text>
<BarXDataLabels />
</Col>
<Col></Col>
<Col></Col>
</Row>
<Row space={1}>
<Text variant="header-2">Bar-y charts</Text>
</Row>
Expand Down
54 changes: 45 additions & 9 deletions src/plugins/d3/__stories__/line/Playground.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {D3Plugin} from '../..';
import {ChartKitWidgetData, LineSeriesData} from '../../../../types';
import {ChartKit} from '../../../../components/ChartKit';
import nintendoGames from '../../examples/nintendoGames';
import {HighchartsPlugin} from '../../../highcharts';

function prepareData(): ChartKitWidgetData {
const games = nintendoGames.filter((d) => {
Expand All @@ -19,6 +20,7 @@ function prepareData(): ChartKitWidgetData {
return {
x: d.date,
y: d.user_score,
label: d.title,
};
}) as LineSeriesData[];
};
Expand All @@ -43,16 +45,25 @@ function prepareData(): ChartKitWidgetData {
name: '2D',
type: 'line',
data: byGenre('2D'),
dataLabels: {
enabled: true,
},
},
{
name: 'Strategy',
type: 'line',
data: byGenre('Strategy'),
dataLabels: {
enabled: true,
},
},
{
name: 'Shooter',
type: 'line',
data: byGenre('Shooter'),
dataLabels: {
enabled: true,
},
},
],
},
Expand Down Expand Up @@ -80,19 +91,44 @@ const ChartStory = ({data}: {data: ChartKitWidgetData}) => {
const [shown, setShown] = React.useState(false);

if (!shown) {
settings.set({plugins: [D3Plugin]});
settings.set({plugins: [D3Plugin, HighchartsPlugin]});
return <Button onClick={() => setShown(true)}>Show chart</Button>;
}

return (
<div
style={{
height: '80vh',
width: '100%',
}}
>
<ChartKit type="d3" data={data} />
</div>
<>
<div
style={{
height: '80vh',
width: '100%',
}}
>
<ChartKit type="d3" data={{...data, title: {text: 'D3'}}} />
</div>
<div
style={{
height: '80vh',
width: '100%',
marginTop: 20,
}}
>
<ChartKit
type="highcharts"
data={{
data: {
graphs: data.series.data,
},
config: {},
libraryConfig: {
title: {text: 'Highcharts'},
xAxis: {
type: 'datetime',
},
},
}}
/>
</div>
</>
);
};

Expand Down
67 changes: 67 additions & 0 deletions src/plugins/d3/examples/bar-x/DataLabels.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import React from 'react';
import {ChartKit} from '../../../../components/ChartKit';
import type {BarXSeries, BarXSeriesData, ChartKitWidgetData} from '../../../../types';
import nintendoGames from '../nintendoGames';
import {groups, sort} from 'd3';

const years = Array.from(
new Set(
nintendoGames.map((g) =>
g.date ? new Date(g.date as number).getFullYear().toString() : 'unknown',
),
),
);

function prepareData(): BarXSeries[] {
const games = sort(
nintendoGames.filter((d) => {
return d.date && d.user_score;
}),
(d) => d.date,
);

const groupByYear = (d: any) => (d.date ? new Date(d.date as number).getFullYear() : 'unknown');

const byGenre = (genre: string): BarXSeries => {
const data = groups(
games.filter((d) => d.genres.includes(genre)),
groupByYear,
).map<BarXSeriesData>(([year, items]) => {
return {
x: years.indexOf(String(year)),
y: items.length,
};
});

return {
type: 'bar-x',
name: genre,
dataLabels: {
enabled: true,
},
stacking: 'normal',
data,
};
};

return [byGenre('Strategy'), byGenre('Shooter'), byGenre('Puzzle'), byGenre('Action')];
}

export const DataLabels = () => {
const series = prepareData();
const widgetData: ChartKitWidgetData = {
series: {
data: series,
},
xAxis: {
categories: years,
type: 'category',
title: {
text: 'Release year',
},
ticks: {pixelInterval: 200},
},
};

return <ChartKit type="d3" data={widgetData} />;
};
99 changes: 99 additions & 0 deletions src/plugins/d3/examples/line/DataLabels.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
import React from 'react';
import {ChartKit} from '../../../../components/ChartKit';
import type {ChartKitWidgetData, LineSeries, LineSeriesData} from '../../../../types';
import nintendoGames from '../nintendoGames';
import {dateTime} from '@gravity-ui/date-utils';

function prepareData(): LineSeries[] {
const games = nintendoGames.filter((d) => {
return d.date && d.user_score;
});

const byGenre = (genre: string) => {
return games
.filter((d) => d.genres.includes(genre))
.map((d) => {
return {
x: d.date,
y: d.user_score,
label: `${d.title} (${d.user_score})`,
custom: d,
};
}) as LineSeriesData[];
};

return [
{
name: 'Strategy',
type: 'line',
data: byGenre('Strategy'),
dataLabels: {
enabled: true,
},
},
{
name: 'Shooter',
type: 'line',
data: byGenre('Shooter'),
dataLabels: {
enabled: true,
},
},
];
}

export const DataLabels = () => {
const series = prepareData();

const widgetData: ChartKitWidgetData = {
series: {
data: series.map<LineSeries>((s) => ({
type: 'line',
data: s.data.filter((d) => d.x),
name: s.name,
dataLabels: {
enabled: true,
},
})),
},
yAxis: [
{
title: {
text: 'User score',
},
},
],
xAxis: {
type: 'datetime',
title: {
text: 'Release dates',
},
ticks: {pixelInterval: 200},
},
tooltip: {
renderer: (d) => {
const point = d.hovered[0]?.data as LineSeriesData;

if (!point) {
return null;
}

const title = point.custom.title;
const score = point.custom.user_score;
const date = dateTime({input: point.custom.date}).format('DD MMM YYYY');

return (
<React.Fragment>
<b>{title}</b>
<br />
Release date: {date}
<br />
User score: {score}
</React.Fragment>
);
},
},
};

return <ChartKit type="d3" data={widgetData} />;
};
16 changes: 14 additions & 2 deletions src/plugins/d3/renderer/hooks/useChartOptions/y-axis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
import type {PreparedAxis} from './types';
import {createYScale} from '../useAxisScales';
import {PreparedSeries} from '../useSeries/types';
import {ChartKitWidgetAxis} from '../../../../../types';

const getAxisLabelMaxWidth = (args: {axis: PreparedAxis; series: ChartKitWidgetSeries[]}) => {
const {axis, series} = args;
Expand Down Expand Up @@ -49,6 +50,16 @@ const getAxisLabelMaxWidth = (args: {axis: PreparedAxis; series: ChartKitWidgetS
}).maxWidth;
};

function getAxisMin(axis?: ChartKitWidgetAxis, series?: ChartKitWidgetSeries[]) {
const min = axis?.min;

if (typeof min === 'undefined' && series?.some((s) => s.type === 'bar-x')) {
return 0;
}

return min;
}

export const getPreparedYAxis = ({
series,
yAxis,
Expand All @@ -67,8 +78,9 @@ export const getPreparedYAxis = ({
const y1TitleStyle: BaseTextStyle = {
fontSize: get(yAxis1, 'title.style.fontSize', yAxisTitleDefaults.fontSize),
};
const axisType = get(yAxis1, 'type', 'linear');
const preparedY1Axis: PreparedAxis = {
type: get(yAxis1, 'type', 'linear'),
type: axisType,
labels: {
enabled: labelsEnabled,
margin: labelsEnabled ? get(yAxis1, 'labels.margin', axisLabelsDefaults.margin) : 0,
Expand All @@ -93,7 +105,7 @@ export const getPreparedYAxis = ({
? getHorisontalSvgTextHeight({text: y1TitleText, style: y1TitleStyle})
: 0,
},
min: get(yAxis1, 'min'),
min: getAxisMin(yAxis1, series),
maxPadding: get(yAxis1, 'maxPadding', 0.05),
grid: {
enabled: get(yAxis1, 'grid.enabled', true),
Expand Down
1 change: 1 addition & 0 deletions src/plugins/d3/renderer/hooks/useSeries/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ export const DEFAULT_DATALABELS_PADDING = 5;
export const DEFAULT_DATALABELS_STYLE: BaseTextStyle = {
fontSize: '11px',
fontWeight: 'bold',
fontColor: 'var(--d3-data-labels)',
};
4 changes: 3 additions & 1 deletion src/plugins/d3/renderer/hooks/useSeries/prepare-bar-x.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type {BarXSeries} from '../../../../../types';
import type {PreparedBarXSeries, PreparedLegend, PreparedSeries} from './types';
import {getRandomCKId} from '../../../../../utils';
import {prepareLegendSymbol} from './utils';
import {DEFAULT_DATALABELS_STYLE} from './constants';
import {DEFAULT_DATALABELS_PADDING, DEFAULT_DATALABELS_STYLE} from './constants';

type PrepareBarXSeriesArgs = {
colorScale: ScaleOrdinal<string, string>;
Expand Down Expand Up @@ -45,6 +45,8 @@ export function prepareBarXSeries(args: PrepareBarXSeriesArgs): PreparedSeries[]
? series.dataLabels?.inside
: false,
style: Object.assign({}, DEFAULT_DATALABELS_STYLE, series.dataLabels?.style),
allowOverlap: series.dataLabels?.allowOverlap || false,
padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
},
};
}, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function prepareLineSeries(args: PrepareLineSeriesArgs): PreparedSeries[]
enabled: series.dataLabels?.enabled || false,
style: Object.assign({}, DEFAULT_DATALABELS_STYLE, series.dataLabels?.style),
padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
allowOverlap: get(series, 'dataLabels.allowOverlap', false),
},
};
}, []);
Expand Down
Loading

0 comments on commit 760e5d0

Please sign in to comment.