Skip to content

Commit

Permalink
Add shapes support for lines in D3
Browse files Browse the repository at this point in the history
  • Loading branch information
artemipanchuk committed Dec 20, 2023
1 parent 4b95adb commit 4cd4bd2
Show file tree
Hide file tree
Showing 8 changed files with 243 additions and 6 deletions.
187 changes: 187 additions & 0 deletions src/plugins/d3/__stories__/line/Shapes.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import React from 'react';
import {StoryObj} from '@storybook/react';
import {Button} from '@gravity-ui/uikit';
import {settings} from '../../../../libs';
import {D3Plugin} from '../..';
import {ChartKitWidgetData, LineSeriesData} from '../../../../types';
import {ChartKit} from '../../../../components/ChartKit';
import nintendoGames from '../../examples/nintendoGames';
import {HighchartsPlugin} from '../../../highcharts';

enum LineShapeType {
Solid = 'Solid',
ShortDash = 'ShortDash',
ShortDot = 'ShortDot',
ShortDashDot = 'ShortDashDot',
ShortDashDotDot = 'ShortDashDotDot',
Dot = 'Dot',
Dash = 'Dash',
LongDash = 'LongDash',
DashDot = 'DashDot',
LongDashDot = 'LongDashDot',
LongDashDotDot = 'LongDashDotDot',
}
const SHAPES_ORDER = {
[LineShapeType.Solid]: 1,
[LineShapeType.Dash]: 2,
[LineShapeType.Dot]: 3,
[LineShapeType.ShortDashDot]: 4,
[LineShapeType.LongDash]: 5,
[LineShapeType.LongDashDot]: 6,
[LineShapeType.ShortDot]: 7,
[LineShapeType.LongDashDotDot]: 8,
[LineShapeType.ShortDash]: 9,
[LineShapeType.DashDot]: 10,
[LineShapeType.ShortDashDotDot]: 11,
};

const selectShapes = (): LineShapeType[] => Object.values(LineShapeType);
const getServerShapesOrder = () => selectShapes().sort((a, b) => SHAPES_ORDER[a] - SHAPES_ORDER[b]);

const SHAPES_IN_ORDER = getServerShapesOrder();

function prepareData(): ChartKitWidgetData {
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,
};
}) as LineSeriesData[];
};

return {
series: {
options: {
line: {
lineWidth: 2,
},
},
data: [
{
name: '3D',
type: 'line',
data: byGenre('3D'),
dataLabels: {
enabled: true,
},
},
{
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,
},
},
],
},
xAxis: {
type: 'datetime',
title: {
text: 'Release date',
},
},
yAxis: [
{
title: {text: 'User score'},
labels: {
enabled: true,
},
ticks: {
pixelInterval: 120,
},
},
],
};
}

const ChartStory = ({data}: {data: ChartKitWidgetData}) => {
const [shown, setShown] = React.useState(false);

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

data.series.data.forEach((graph, i) => {
// @ts-ignore
graph.dashStyle = SHAPES_IN_ORDER[i % SHAPES_IN_ORDER.length];
});

return (
<>
<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>
</>
);
};

export const ShapesLineChartStory: StoryObj<typeof ChartStory> = {
name: 'Shapes',
args: {
data: prepareData(),
},
argTypes: {
data: {
control: 'object',
},
},
};

export default {
title: 'Plugins/D3/Line',
component: ChartStory,
};
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ export function prepareLineSeries(args: PrepareLineSeriesArgs) {
marker: prepareMarker(series, seriesOptions),
};

if (series.dashStyle) {
prepared.dashStyle = series.dashStyle;
}

return prepared;
}, []);
}
2 changes: 2 additions & 0 deletions src/plugins/d3/renderer/hooks/useSeries/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ConnectorShape,
ConnectorCurve,
PathLegendSymbolOptions,
DashStyle,
} from '../../../../../types';
import type {SeriesOptionsDefaults} from '../../constants';

Expand Down Expand Up @@ -153,6 +154,7 @@ export type PreparedLineSeries = {
};
};
};
dashStyle?: DashStyle;
} & BasePreparedSeries;

export type PreparedSeries =
Expand Down
25 changes: 22 additions & 3 deletions src/plugins/d3/renderer/hooks/useShapes/line/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import get from 'lodash/get';
import {block} from '../../../../../../utils/cn';
import type {PreparedSeriesOptions} from '../../useSeries/types';
import type {MarkerData, PointData, PreparedLineData} from './types';
import type {TooltipDataChunkLine} from '../../../../../../types';
import type {DashStyle, TooltipDataChunkLine} from '../../../../../../types';
import type {LabelData} from '../../../types';
import {filterOverlappingLabels} from '../../../utils';
import {setActiveState} from '../utils';
Expand Down Expand Up @@ -48,6 +48,26 @@ function getMarkerSymbol(type: string, radius: number) {
}
}

const getLineDashArray = (dashStyle: DashStyle = 'Solid', strokeWidth = 2) => {
const value = dashStyle && dashStyle.toLowerCase();

const arrayValue = value
.replace('shortdashdotdot', '3,1,1,1,1,1,')
.replace('shortdashdot', '3,1,1,1')
.replace('shortdot', '1,1,')
.replace('shortdash', '3,1,')
.replace('longdash', '8,3,')
.replace(/dot/g, '1,3,')
.replace('dash', '4,3,')
.replace(/,$/, '')
.split(',')
.map((part) => {
return `${parseInt(part, 10) * strokeWidth}`;
});

return arrayValue.join(',').replace(/NaN/g, 'none');
};

const getMarkerVisibility = (d: MarkerData) => {
const markerStates = d.point.series.marker.states;
const enabled = (markerStates.hover.enabled && d.hovered) || markerStates.normal.enabled;
Expand Down Expand Up @@ -88,8 +108,7 @@ export const LineSeriesShapes = (args: Args) => {
.attr('fill', 'none')
.attr('stroke', (d) => d.color)
.attr('stroke-width', (d) => d.width)
.attr('stroke-linejoin', 'round')
.attr('stroke-linecap', 'round');
.attr('stroke-dasharray', (d) => getLineDashArray(d.dashStyle, d.width));

let dataLabels = preparedData.reduce((acc, d) => {
return acc.concat(d.labels);
Expand Down
10 changes: 8 additions & 2 deletions src/plugins/d3/renderer/hooks/useShapes/line/prepare-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ export const prepareLineData = (args: {
}));
}

acc.push({
const result: PreparedLineData = {
points,
markers,
labels,
Expand All @@ -80,7 +80,13 @@ export const prepareLineData = (args: {
hovered: false,
active: true,
id: s.id,
});
};

if (s.dashStyle) {
result.dashStyle = s.dashStyle;
}

acc.push(result);

return acc;
}, []);
Expand Down
3 changes: 2 additions & 1 deletion src/plugins/d3/renderer/hooks/useShapes/line/types.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {PreparedLineSeries} from '../../useSeries/types';
import {LineSeriesData} from '../../../../../../types';
import {DashStyle, LineSeriesData} from '../../../../../../types';
import {LabelData} from '../../../types';

export type PointData = {
Expand All @@ -25,4 +25,5 @@ export type PreparedLineData = {
hovered: boolean;
active: boolean;
labels: LabelData[];
dashStyle?: DashStyle;
};
2 changes: 2 additions & 0 deletions src/types/widget-data/line.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {BaseSeries, BaseSeriesData} from './base';
import {ChartKitWidgetLegend, RectLegendSymbolOptions} from './legend';
import {PointMarkerOptions} from './marker';
import {DashStyle} from './series';

export type LineSeriesData<T = any> = BaseSeriesData<T> & {
/**
Expand Down Expand Up @@ -45,4 +46,5 @@ export type LineSeries<T = any> = BaseSeries & {
};
/** Options for the point markers of line series */
marker?: LineMarkerOptions;
dashStyle?: DashStyle;
};
16 changes: 16 additions & 0 deletions src/types/widget-data/series.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,19 @@ export type DataLabelRendererData<T = any> = {
data: ChartKitWidgetSeriesData<T>;
};

export type DashStyle =
| 'Dash'
| 'DashDot'
| 'Dot'
| 'LongDash'
| 'LongDashDot'
| 'LongDashDotDot'
| 'ShortDash'
| 'ShortDashDot'
| 'ShortDashDotDot'
| 'ShortDot'
| 'Solid';

type BasicHoverState = {
/**
* Enable separate styles for the hovered series.
Expand Down Expand Up @@ -174,5 +187,8 @@ export type ChartKitWidgetSeriesOptions = {
};
/** Options for the point markers of line series */
marker?: LineMarkerOptions;

/** Options for line style */
dashStyle?: DashStyle;
};
};

0 comments on commit 4cd4bd2

Please sign in to comment.