Skip to content

Commit

Permalink
feat(D3 plugin): add chart click event (#430)
Browse files Browse the repository at this point in the history
feat(D3 plugin): add chart click event (#418)

* feat(D3 plugin): add chart click event

* fix import

* add scatter playground story

* fix review
  • Loading branch information
korvin89 authored Feb 20, 2024
1 parent de9c84b commit 5376d96
Show file tree
Hide file tree
Showing 14 changed files with 321 additions and 47 deletions.
95 changes: 95 additions & 0 deletions src/plugins/d3/__stories__/area/Playground.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import React from 'react';

import {Button} from '@gravity-ui/uikit';
import {action} from '@storybook/addon-actions';
import {StoryObj} from '@storybook/react';

import {D3Plugin} from '../..';
import {ChartKit} from '../../../../components/ChartKit';
import {settings} from '../../../../libs';
import {ChartKitWidgetData} from '../../../../types';
import {HighchartsPlugin} from '../../../highcharts';

function prepareData(): ChartKitWidgetData {
return {
series: {
options: {
line: {
lineWidth: 2,
},
},
data: [
{
name: 'A',
type: 'area',
data: [
{x: 1, y: 200},
{x: 2, y: 220},
{x: 3, y: 180},
],
stacking: 'normal',
dataLabels: {
enabled: true,
},
},
{
name: 'B',
type: 'area',
data: [
{x: 1, y: 30},
{x: 2, y: 25},
{x: 3, y: 45},
],
stacking: 'normal',
dataLabels: {
enabled: true,
},
},
],
},
chart: {
events: {
click: action('chart.events.click'),
},
},
};
}

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>;
}

return (
<>
<div
style={{
height: '80vh',
width: '100%',
}}
>
<ChartKit type="d3" data={data} />
</div>
</>
);
};

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

export default {
title: 'Plugins/D3/Area',
component: ChartStory,
};
7 changes: 6 additions & 1 deletion src/plugins/d3/__stories__/bar-x/Playground.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function prepareData(): ChartKitWidgetData {
},
xAxis: {
type: 'category',
categories: gamesByPlatform.map(([key]) => key),
categories: gamesByPlatform.map(([key, _group]) => key),
title: {
text: 'Game Platforms',
},
Expand All @@ -48,6 +48,11 @@ function prepareData(): ChartKitWidgetData {
},
},
],
chart: {
events: {
click: action('chart.events.click'),
},
},
};
}

Expand Down
5 changes: 5 additions & 0 deletions src/plugins/d3/__stories__/bar-y/Playground.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ function prepareData(): ChartKitWidgetData {
},
},
],
chart: {
events: {
click: action('chart.events.click'),
},
},
};
}

Expand Down
5 changes: 5 additions & 0 deletions src/plugins/d3/__stories__/line/Playground.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ function prepareData(): ChartKitWidgetData {
},
},
],
chart: {
events: {
click: action('chart.events.click'),
},
},
};
}

Expand Down
5 changes: 5 additions & 0 deletions src/plugins/d3/__stories__/pie/Playground.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ function prepareData(): ChartKitWidgetData {
],
},
legend: {enabled: true},
chart: {
events: {
click: action('chart.events.click'),
},
},
};
}

Expand Down
89 changes: 89 additions & 0 deletions src/plugins/d3/__stories__/scatter/Playground.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import React from 'react';

import {Button} from '@gravity-ui/uikit';
import {action} from '@storybook/addon-actions';
import {StoryObj} from '@storybook/react';

import {D3Plugin} from '../..';
import {ChartKit} from '../../../../components/ChartKit';
import {settings} from '../../../../libs';
import {ChartKitWidgetData} from '../../../../types';
import nintendoGames from '../../examples/nintendoGames';

function prepareData() {
const dataset = nintendoGames.filter((d) => d.date && d.user_score);
const data = dataset.map((d) => ({
x: d.date || undefined,
y: d.user_score || undefined,
custom: d,
}));

const widgetData: ChartKitWidgetData = {
series: {
data: [
{
type: 'scatter',
data,
name: 'Scatter series',
},
],
},
yAxis: [
{
title: {
text: 'User score',
},
},
],
xAxis: {
type: 'datetime',
title: {
text: 'Release dates',
},
},
chart: {
events: {
click: action('chart.events.click'),
},
},
};

return widgetData;
}

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

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

return (
<div
style={{
height: '80vh',
width: '100%',
}}
>
<ChartKit type="d3" data={data} />
</div>
);
};

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

export default {
title: 'Plugins/D3/Scatter',
component: ChartStory,
};
51 changes: 28 additions & 23 deletions src/plugins/d3/__stories__/treemap/Playground.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,36 @@ import type {ChartKitWidgetData} from '../../../../types/widget-data';
import {D3Plugin} from '../..';

const prepareData = (): ChartKitWidgetData => {
const treemapSeries: TreemapSeries = {
type: 'treemap',
name: 'Example',
dataLabels: {
enabled: true,
},
layoutAlgorithm: 'binary',
levels: [{index: 1}, {index: 2}, {index: 3}],
data: [
{name: 'One', value: 15},
{name: 'Two', value: 10},
{name: 'Three', value: 15},
{name: 'Four'},
{name: 'Four-1', value: 5, parentId: 'Four'},
{name: 'Four-2', parentId: 'Four'},
{name: 'Four-3', value: 4, parentId: 'Four'},
{name: 'Four-2-1', value: 5, parentId: 'Four-2'},
{name: 'Four-2-2', value: 7, parentId: 'Four-2'},
{name: 'Four-2-3', value: 10, parentId: 'Four-2'},
],
};

return {
series: {
data: [
{
type: 'treemap',
name: 'Example',
dataLabels: {
enabled: true,
},
layoutAlgorithm: 'binary',
levels: [{index: 1}, {index: 2}, {index: 3}],
data: [
{name: 'One', value: 15},
{name: 'Two', value: 10},
{name: 'Three', value: 15},
{name: 'Four'},
{name: 'Four-1', value: 5, parentId: 'Four'},
{name: 'Four-2', parentId: 'Four'},
{name: 'Four-3', value: 4, parentId: 'Four'},
{name: 'Four-2-1', value: 5, parentId: 'Four-2'},
{name: 'Four-2-2', value: 7, parentId: 'Four-2'},
{name: 'Four-2-3', value: 10, parentId: 'Four-2'},
],
},
],
data: [treemapSeries],
},
chart: {
events: {
click: action('chart.events.click'),
},
},
};
};
Expand Down
9 changes: 9 additions & 0 deletions src/plugins/d3/renderer/components/Chart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,15 @@ export const Chart = (props: Props) => {
yScale,
svgContainer: svgRef.current,
});
React.useEffect(() => {
if (data.chart?.events?.click) {
dispatcher.on('click-chart', data.chart?.events?.click);
}

return () => {
dispatcher.on('click-chart', null);
};
}, [dispatcher]);

const boundsOffsetTop = chart.margin.top;
const boundsOffsetLeft = chart.margin.left + getWidthOccupiedByYAxis({preparedAxis: yAxis});
Expand Down
30 changes: 26 additions & 4 deletions src/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ export const TooltipTriggerArea = (args: Args) => {
return sort(result, (item) => item.y);
}, [shapesData]);

const handleMouseMove: React.MouseEventHandler<SVGRectElement> = (e) => {
const getShapeData = (point: [number, number]) => {
const {left: ownLeft, top: ownTop} = rectRef.current?.getBoundingClientRect() || {
left: 0,
top: 0,
Expand All @@ -178,10 +178,10 @@ export const TooltipTriggerArea = (args: Args) => {
left: 0,
top: 0,
};
const [pointerX, pointerY] = pointer(e, svgContainer);
const hoverShapeData = [];
const [pointerX, pointerY] = point; //pointer(e, svgContainer);
const result = [];

hoverShapeData?.push(
result?.push(
...getBarXShapeData({
shapesData,
point: [pointerX, pointerY],
Expand All @@ -200,6 +200,13 @@ export const TooltipTriggerArea = (args: Args) => {
}),
);

return result;
};

const handleMouseMove: React.MouseEventHandler<SVGRectElement> = (e) => {
const [pointerX, pointerY] = pointer(e, svgContainer);
const hoverShapeData = getShapeData([pointerX, pointerY]);

if (hoverShapeData.length) {
const position: PointerPosition = [pointerX, pointerY];
dispatcher.call('hover-shape', e.target, hoverShapeData, position);
Expand All @@ -213,6 +220,20 @@ export const TooltipTriggerArea = (args: Args) => {
dispatcher.call('hover-shape', {}, undefined);
};

const handleClick: React.MouseEventHandler<SVGRectElement> = (e) => {
const [pointerX, pointerY] = pointer(e, svgContainer);
const shapeData = getShapeData([pointerX, pointerY]);

if (shapeData.length) {
dispatcher.call(
'click-chart',
undefined,
{point: get(shapeData, '[0].data'), series: get(shapeData, '[0].series')},
e,
);
}
};

return (
<rect
ref={rectRef}
Expand All @@ -221,6 +242,7 @@ export const TooltipTriggerArea = (args: Args) => {
fill="transparent"
onMouseMove={throttledHandleMouseMove}
onMouseLeave={handleMouseLeave}
onClick={handleClick}
/>
);
};
2 changes: 1 addition & 1 deletion src/plugins/d3/renderer/d3-dispatcher.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {dispatch} from 'd3';

export const getD3Dispatcher = () => {
return dispatch('hover-shape');
return dispatch('hover-shape', 'click-chart');
};
Loading

0 comments on commit 5376d96

Please sign in to comment.