@gravity-ui/chartkit 3.1.3 → 3.2.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/plugins/d3/__stories__/bar/category.stories.d.ts +4 -0
- package/build/plugins/d3/__stories__/bar/category.stories.js +75 -0
- package/build/plugins/d3/__stories__/bar/datetime.stories.d.ts +4 -0
- package/build/plugins/d3/__stories__/bar/datetime.stories.js +71 -0
- package/build/plugins/d3/__stories__/bar/linear.stories.d.ts +4 -0
- package/build/plugins/d3/__stories__/bar/linear.stories.js +74 -0
- package/build/plugins/d3/__stories__/penguins.json +3098 -0
- package/build/plugins/d3/__stories__/scatter/LinearCategories.stories.d.ts +4 -0
- package/build/plugins/d3/__stories__/scatter/LinearCategories.stories.js +103 -0
- package/build/plugins/d3/__stories__/scatter/Timestamp.stories.d.ts +4 -0
- package/build/plugins/d3/__stories__/scatter/Timestamp.stories.js +91 -0
- package/build/plugins/d3/index.d.ts +7 -0
- package/build/plugins/d3/index.js +10 -0
- package/build/plugins/d3/renderer/D3Widget.d.ts +15 -0
- package/build/plugins/d3/renderer/D3Widget.js +40 -0
- package/build/plugins/d3/renderer/components/AxisX.d.ts +10 -0
- package/build/plugins/d3/renderer/components/AxisX.js +68 -0
- package/build/plugins/d3/renderer/components/AxisY.d.ts +10 -0
- package/build/plugins/d3/renderer/components/AxisY.js +73 -0
- package/build/plugins/d3/renderer/components/Chart.d.ts +10 -0
- package/build/plugins/d3/renderer/components/Chart.js +64 -0
- package/build/plugins/d3/renderer/components/Legend.d.ts +12 -0
- package/build/plugins/d3/renderer/components/Legend.js +66 -0
- package/build/plugins/d3/renderer/components/Title.d.ts +7 -0
- package/build/plugins/d3/renderer/components/Title.js +8 -0
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.d.ts +10 -0
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +21 -0
- package/build/plugins/d3/renderer/components/Tooltip/index.d.ts +12 -0
- package/build/plugins/d3/renderer/components/Tooltip/index.js +53 -0
- package/build/plugins/d3/renderer/components/index.d.ts +1 -0
- package/build/plugins/d3/renderer/components/index.js +1 -0
- package/build/plugins/d3/renderer/components/styles.css +61 -0
- package/build/plugins/d3/renderer/constants.d.ts +1 -0
- package/build/plugins/d3/renderer/constants.js +22 -0
- package/build/plugins/d3/renderer/hooks/index.d.ts +10 -0
- package/build/plugins/d3/renderer/hooks/index.js +10 -0
- package/build/plugins/d3/renderer/hooks/useChartDimensions/index.d.ts +17 -0
- package/build/plugins/d3/renderer/hooks/useChartDimensions/index.js +13 -0
- package/build/plugins/d3/renderer/hooks/useChartEvents/index.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartEvents/index.js +15 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.d.ts +8 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.js +60 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/constants.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/constants.js +3 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/index.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/index.js +32 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/legend.d.ts +6 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/legend.js +7 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/title.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/title.js +17 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/tooltip.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/tooltip.js +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/types.d.ts +33 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/types.js +1 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/utils.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/utils.js +18 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +29 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +35 -0
- package/build/plugins/d3/renderer/hooks/useLegend/index.d.ts +13 -0
- package/build/plugins/d3/renderer/hooks/useLegend/index.js +28 -0
- package/build/plugins/d3/renderer/hooks/useScales/index.d.ts +17 -0
- package/build/plugins/d3/renderer/hooks/useScales/index.js +106 -0
- package/build/plugins/d3/renderer/hooks/useSeries/index.d.ts +14 -0
- package/build/plugins/d3/renderer/hooks/useSeries/index.js +23 -0
- package/build/plugins/d3/renderer/hooks/useShapes/bar.d.ts +16 -0
- package/build/plugins/d3/renderer/hooks/useShapes/bar.js +75 -0
- package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +19 -0
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +44 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.d.ts +18 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.js +62 -0
- package/build/plugins/d3/renderer/hooks/useTooltip/index.d.ts +13 -0
- package/build/plugins/d3/renderer/hooks/useTooltip/index.js +19 -0
- package/build/plugins/d3/renderer/hooks/useTooltip/types.d.ts +7 -0
- package/build/plugins/d3/renderer/hooks/useTooltip/types.js +1 -0
- package/build/plugins/d3/renderer/utils/index.d.ts +18 -0
- package/build/plugins/d3/renderer/utils/index.js +71 -0
- package/build/plugins/d3/types.d.ts +4 -0
- package/build/plugins/d3/types.js +1 -0
- package/build/types/widget-data/axis.d.ts +24 -0
- package/build/types/widget-data/axis.js +1 -0
- package/build/types/widget-data/bar.d.ts +31 -0
- package/build/types/widget-data/bar.js +1 -0
- package/build/types/widget-data/base.d.ts +15 -0
- package/build/types/widget-data/base.js +1 -0
- package/build/types/widget-data/chart.d.ts +9 -0
- package/build/types/widget-data/chart.js +1 -0
- package/build/types/widget-data/index.d.ts +28 -0
- package/build/types/widget-data/index.js +10 -0
- package/build/types/widget-data/legend.d.ts +3 -0
- package/build/types/widget-data/legend.js +1 -0
- package/build/types/widget-data/pie.d.ts +10 -0
- package/build/types/widget-data/pie.js +1 -0
- package/build/types/widget-data/scatter.d.ts +20 -0
- package/build/types/widget-data/scatter.js +1 -0
- package/build/types/widget-data/series.d.ts +18 -0
- package/build/types/widget-data/series.js +1 -0
- package/build/types/widget-data/title.d.ts +5 -0
- package/build/types/widget-data/title.js +1 -0
- package/build/types/widget-data/tooltip.d.ts +12 -0
- package/build/types/widget-data/tooltip.js +1 -0
- package/build/types/widget.d.ts +5 -0
- package/package.json +7 -2
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import random from 'lodash/random';
|
|
3
|
+
import { withKnobs, select, radios, text } from '@storybook/addon-knobs';
|
|
4
|
+
import { Button } from '@gravity-ui/uikit';
|
|
5
|
+
import { settings } from '../../../../libs';
|
|
6
|
+
import { ChartKit } from '../../../../components/ChartKit';
|
|
7
|
+
import { D3Plugin } from '../..';
|
|
8
|
+
import penguins from '../penguins.json';
|
|
9
|
+
const shapeScatterSeriesData = (args) => {
|
|
10
|
+
const { data, groupBy, map } = args;
|
|
11
|
+
return data.reduce((acc, d) => {
|
|
12
|
+
const seriesName = d[groupBy];
|
|
13
|
+
if (!seriesName) {
|
|
14
|
+
return acc;
|
|
15
|
+
}
|
|
16
|
+
if (!acc[seriesName]) {
|
|
17
|
+
acc[seriesName] = [];
|
|
18
|
+
}
|
|
19
|
+
acc[seriesName].push(Object.assign({ x: d[map.x], y: d[map.y], radius: random(3, 6) }, (map.category && { category: d[map.category] })));
|
|
20
|
+
return acc;
|
|
21
|
+
}, {});
|
|
22
|
+
};
|
|
23
|
+
const shapeScatterSeries = (data) => {
|
|
24
|
+
return Object.keys(data).reduce((acc, name) => {
|
|
25
|
+
acc.push({
|
|
26
|
+
type: 'scatter',
|
|
27
|
+
data: data[name],
|
|
28
|
+
name,
|
|
29
|
+
});
|
|
30
|
+
return acc;
|
|
31
|
+
}, []);
|
|
32
|
+
};
|
|
33
|
+
const shapeScatterChartData = (series, categoriesType, categories) => {
|
|
34
|
+
let xAxis = {
|
|
35
|
+
labels: {
|
|
36
|
+
enabled: false,
|
|
37
|
+
},
|
|
38
|
+
title: {
|
|
39
|
+
text: text('X axis title', ''),
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
let yAxis = {
|
|
43
|
+
title: {
|
|
44
|
+
text: text('Y axis title', ''),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
if (categories && categoriesType === 'x') {
|
|
48
|
+
xAxis = Object.assign(Object.assign({}, xAxis), { type: 'category', categories });
|
|
49
|
+
}
|
|
50
|
+
if (categories && categoriesType === 'y') {
|
|
51
|
+
yAxis = Object.assign(Object.assign({}, yAxis), { type: 'category', categories });
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
series: {
|
|
55
|
+
data: series,
|
|
56
|
+
},
|
|
57
|
+
xAxis,
|
|
58
|
+
yAxis: [yAxis],
|
|
59
|
+
title: {
|
|
60
|
+
text: text('title', 'Chart title'),
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
};
|
|
64
|
+
const Template = () => {
|
|
65
|
+
const [shown, setShown] = React.useState(false);
|
|
66
|
+
const chartkitRef = React.useRef();
|
|
67
|
+
const x = select('x', ['culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'body_mass_g'], 'culmen_length_mm');
|
|
68
|
+
const y = select('y', ['culmen_length_mm', 'culmen_depth_mm', 'flipper_length_mm', 'body_mass_g'], 'culmen_depth_mm');
|
|
69
|
+
const groupBy = select('groupBy', ['species', 'island', 'sex'], 'species');
|
|
70
|
+
const categoriesType = radios('categoriesType', { none: 'none', x: 'x', y: 'y' }, 'none');
|
|
71
|
+
const category = categoriesType === 'none'
|
|
72
|
+
? undefined
|
|
73
|
+
: select('category', ['species', 'island', 'sex'], 'island');
|
|
74
|
+
let categories;
|
|
75
|
+
if (categoriesType !== 'none' && category) {
|
|
76
|
+
categories = penguins.reduce((acc, p) => {
|
|
77
|
+
const cerrentCategory = p[category];
|
|
78
|
+
if (typeof cerrentCategory === 'string' && !acc.includes(cerrentCategory)) {
|
|
79
|
+
acc.push(cerrentCategory);
|
|
80
|
+
}
|
|
81
|
+
return acc;
|
|
82
|
+
}, []);
|
|
83
|
+
}
|
|
84
|
+
const shapedScatterSeriesData = shapeScatterSeriesData({
|
|
85
|
+
data: penguins,
|
|
86
|
+
groupBy,
|
|
87
|
+
map: { x, y, category },
|
|
88
|
+
});
|
|
89
|
+
const shapedScatterSeries = shapeScatterSeries(shapedScatterSeriesData);
|
|
90
|
+
const data = shapeScatterChartData(shapedScatterSeries, categoriesType, categories);
|
|
91
|
+
if (!shown) {
|
|
92
|
+
settings.set({ plugins: [D3Plugin] });
|
|
93
|
+
return React.createElement(Button, { onClick: () => setShown(true) }, "Show chart");
|
|
94
|
+
}
|
|
95
|
+
return (React.createElement("div", { style: { height: '80vh', width: '100%' } },
|
|
96
|
+
React.createElement(ChartKit, { ref: chartkitRef, type: "d3", data: data })));
|
|
97
|
+
};
|
|
98
|
+
export const LinearAndCategories = Template.bind({});
|
|
99
|
+
const meta = {
|
|
100
|
+
title: 'Plugins/D3/Scatter',
|
|
101
|
+
decorators: [withKnobs],
|
|
102
|
+
};
|
|
103
|
+
export default meta;
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import random from 'lodash/random';
|
|
3
|
+
import { dateTime } from '@gravity-ui/date-utils';
|
|
4
|
+
import { Button } from '@gravity-ui/uikit';
|
|
5
|
+
import { settings } from '../../../../libs';
|
|
6
|
+
import { ChartKit } from '../../../../components/ChartKit';
|
|
7
|
+
import { D3Plugin } from '../..';
|
|
8
|
+
const rowData = [
|
|
9
|
+
{
|
|
10
|
+
x: 1690686000000,
|
|
11
|
+
y: 86.71905594602345,
|
|
12
|
+
custom: 'green',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
x: 1690426800000,
|
|
16
|
+
y: 86.73089353359981,
|
|
17
|
+
custom: 'yellow',
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
x: 1690254000000,
|
|
21
|
+
y: 86.53675705168267,
|
|
22
|
+
custom: 'red',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
x: 1690772400000,
|
|
26
|
+
y: 86.47880981408552,
|
|
27
|
+
custom: 'blue',
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
x: 1690340400000,
|
|
31
|
+
y: 86.4108836764148,
|
|
32
|
+
custom: 'gray',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
x: 1690599600000,
|
|
36
|
+
y: 86.73440096266042,
|
|
37
|
+
custom: 'pink',
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
x: 1690513200000,
|
|
41
|
+
y: 86.64935929597681,
|
|
42
|
+
custom: 'purple',
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
const shapeData = (data) => {
|
|
46
|
+
const scatterData = data.map((d) => ({
|
|
47
|
+
x: d.x,
|
|
48
|
+
y: d.y,
|
|
49
|
+
radius: random(3, 6),
|
|
50
|
+
custom: d.custom,
|
|
51
|
+
}));
|
|
52
|
+
const scatterSeries = {
|
|
53
|
+
type: 'scatter',
|
|
54
|
+
data: scatterData,
|
|
55
|
+
name: 'some-name',
|
|
56
|
+
};
|
|
57
|
+
return {
|
|
58
|
+
series: {
|
|
59
|
+
data: [scatterSeries],
|
|
60
|
+
},
|
|
61
|
+
xAxis: {
|
|
62
|
+
type: 'datetime',
|
|
63
|
+
timestamps: data.map((d) => d.x),
|
|
64
|
+
},
|
|
65
|
+
tooltip: {
|
|
66
|
+
renderer: ({ hovered }) => {
|
|
67
|
+
const d = hovered.data;
|
|
68
|
+
return React.createElement("div", { style: { color: d.custom } }, dateTime({ input: d.x }).format('LL'));
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
title: {
|
|
72
|
+
text: 'Chart title',
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
const Template = () => {
|
|
77
|
+
const [shown, setShown] = React.useState(false);
|
|
78
|
+
const chartkitRef = React.useRef();
|
|
79
|
+
const data = shapeData(rowData);
|
|
80
|
+
if (!shown) {
|
|
81
|
+
settings.set({ plugins: [D3Plugin] });
|
|
82
|
+
return React.createElement(Button, { onClick: () => setShown(true) }, "Show chart");
|
|
83
|
+
}
|
|
84
|
+
return (React.createElement("div", { style: { height: '300px', width: '100%' } },
|
|
85
|
+
React.createElement(ChartKit, { ref: chartkitRef, type: "d3", data: data })));
|
|
86
|
+
};
|
|
87
|
+
export const Timestamp = Template.bind({});
|
|
88
|
+
const meta = {
|
|
89
|
+
title: 'Plugins/D3/Scatter',
|
|
90
|
+
};
|
|
91
|
+
export default meta;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ChartKitWidgetRef } from '../../../types';
|
|
3
|
+
declare const D3Widget: React.ForwardRefExoticComponent<{
|
|
4
|
+
type: "d3";
|
|
5
|
+
data: import("../../../types/widget-data").ChartKitWidgetData<any>;
|
|
6
|
+
id?: string | undefined;
|
|
7
|
+
isMobile?: boolean | undefined;
|
|
8
|
+
onLoad?: ((data?: import("../../../types").ChartKitOnLoadData<"d3"> | undefined) => void) | undefined;
|
|
9
|
+
onRender?: ((data: import("../../../types").ChartKitOnRenderData) => void) | undefined;
|
|
10
|
+
onChartLoad?: ((data: import("../../../types").ChartKitOnChartLoad<"d3">) => void) | undefined;
|
|
11
|
+
onError?: import("../../../types").ChartKitOnError | undefined;
|
|
12
|
+
renderError?: import("../../../types").RenderError | undefined;
|
|
13
|
+
renderPluginLoader?: (() => React.ReactNode) | undefined;
|
|
14
|
+
} & {} & React.RefAttributes<ChartKitWidgetRef | undefined>>;
|
|
15
|
+
export default D3Widget;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { select } from 'd3';
|
|
3
|
+
import debounce from 'lodash/debounce';
|
|
4
|
+
import { Chart } from './components';
|
|
5
|
+
const D3Widget = React.forwardRef(function D3Widget(props, forwardedRef) {
|
|
6
|
+
const ref = React.useRef(null);
|
|
7
|
+
const debounced = React.useRef();
|
|
8
|
+
const [dimensions, setDimensions] = React.useState();
|
|
9
|
+
const handleResize = React.useCallback(() => {
|
|
10
|
+
if (ref.current) {
|
|
11
|
+
const { width, height } = ref.current.getBoundingClientRect();
|
|
12
|
+
setDimensions({ width, height });
|
|
13
|
+
}
|
|
14
|
+
}, []);
|
|
15
|
+
const debuncedHandleResize = React.useMemo(() => {
|
|
16
|
+
var _a;
|
|
17
|
+
(_a = debounced.current) === null || _a === void 0 ? void 0 : _a.cancel();
|
|
18
|
+
debounced.current = debounce(handleResize, 200);
|
|
19
|
+
return debounced.current;
|
|
20
|
+
}, [handleResize]);
|
|
21
|
+
React.useImperativeHandle(forwardedRef, () => ({
|
|
22
|
+
reflow() {
|
|
23
|
+
handleResize();
|
|
24
|
+
},
|
|
25
|
+
}), [handleResize]);
|
|
26
|
+
React.useEffect(() => {
|
|
27
|
+
const selection = select(window);
|
|
28
|
+
selection.on('resize', debuncedHandleResize);
|
|
29
|
+
return () => {
|
|
30
|
+
// https://d3js.org/d3-selection/events#selection_on
|
|
31
|
+
selection.on('resize', null);
|
|
32
|
+
};
|
|
33
|
+
}, [debuncedHandleResize]);
|
|
34
|
+
React.useEffect(() => {
|
|
35
|
+
// dimensions initialize
|
|
36
|
+
handleResize();
|
|
37
|
+
}, [handleResize]);
|
|
38
|
+
return (React.createElement("div", { ref: ref, style: { width: '100%', height: '100%' } }, (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) && (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) && (React.createElement(Chart, { width: dimensions.width, height: dimensions.height, data: props.data }))));
|
|
39
|
+
});
|
|
40
|
+
export default D3Widget;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ChartOptions, ChartScale } from '../hooks';
|
|
3
|
+
type Props = {
|
|
4
|
+
axis: ChartOptions['xAxis'];
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
scale: ChartScale;
|
|
8
|
+
};
|
|
9
|
+
export declare const AxisX: ({ axis, width, height, scale }: Props) => React.JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import block from 'bem-cn-lite';
|
|
3
|
+
import { axisBottom, select } from 'd3';
|
|
4
|
+
import { formatAxisTickLabel, parseTransformStyle } from '../utils';
|
|
5
|
+
const b = block('chartkit-d3-axis');
|
|
6
|
+
const EMPTY_SPACE_BETWEEN_LABELS = 10;
|
|
7
|
+
// Note: this method do not prepared for rotated labels
|
|
8
|
+
const removeOverlappingXTicks = (axis) => {
|
|
9
|
+
var _a;
|
|
10
|
+
const a = axis.selectAll('g.tick').nodes();
|
|
11
|
+
if (a.length <= 1) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
for (let i = 0, x = 0; i < a.length; i++) {
|
|
15
|
+
const node = a[i];
|
|
16
|
+
const r = node.getBoundingClientRect();
|
|
17
|
+
if (r.left < x) {
|
|
18
|
+
(_a = node === null || node === void 0 ? void 0 : node.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(node);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
x = r.right + EMPTY_SPACE_BETWEEN_LABELS;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
// FIXME: add overflow ellipsis for the labels that out of boundaries
|
|
26
|
+
export const AxisX = ({ axis, width, height, scale }) => {
|
|
27
|
+
const ref = React.useRef(null);
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
if (!ref.current) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const svgElement = select(ref.current);
|
|
33
|
+
svgElement.selectAll('*').remove();
|
|
34
|
+
const xAxisGenerator = axisBottom(scale)
|
|
35
|
+
.tickSize(height * -1)
|
|
36
|
+
.tickPadding(axis.labels.padding)
|
|
37
|
+
.tickFormat((value) => {
|
|
38
|
+
return formatAxisTickLabel({
|
|
39
|
+
axisType: axis.type,
|
|
40
|
+
value,
|
|
41
|
+
dateFormat: axis.labels.dateFormat,
|
|
42
|
+
numberFormat: axis.labels.numberFormat,
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
svgElement.call(xAxisGenerator).attr('class', b());
|
|
46
|
+
svgElement.select('.domain').attr('d', `M0,0V0H${width}`);
|
|
47
|
+
svgElement.selectAll('.tick text').style('font-size', axis.labels.style.fontSize);
|
|
48
|
+
const transformStyle = svgElement.select('.tick').attr('transform');
|
|
49
|
+
const { x } = parseTransformStyle(transformStyle);
|
|
50
|
+
if (x === 0) {
|
|
51
|
+
// Remove tick that has the same x coordinate like domain
|
|
52
|
+
svgElement.select('.tick').remove();
|
|
53
|
+
}
|
|
54
|
+
if (axis.title.text) {
|
|
55
|
+
const textY = axis.title.height + parseInt(axis.labels.style.fontSize) + axis.labels.padding;
|
|
56
|
+
svgElement
|
|
57
|
+
.append('text')
|
|
58
|
+
.attr('class', b('title'))
|
|
59
|
+
.attr('text-anchor', 'middle')
|
|
60
|
+
.attr('x', width / 2)
|
|
61
|
+
.attr('y', textY)
|
|
62
|
+
.attr('font-size', axis.title.style.fontSize)
|
|
63
|
+
.text(axis.title.text);
|
|
64
|
+
}
|
|
65
|
+
removeOverlappingXTicks(svgElement);
|
|
66
|
+
}, [axis, width, height, scale]);
|
|
67
|
+
return React.createElement("g", { ref: ref });
|
|
68
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ChartOptions, ChartScale } from '../hooks';
|
|
3
|
+
type Props = {
|
|
4
|
+
axises: ChartOptions['yAxis'];
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
scale: ChartScale;
|
|
8
|
+
};
|
|
9
|
+
export declare const AxisY: ({ axises, width, height, scale }: Props) => React.JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import block from 'bem-cn-lite';
|
|
3
|
+
import { axisLeft, select } from 'd3';
|
|
4
|
+
import { formatAxisTickLabel, parseTransformStyle } from '../utils';
|
|
5
|
+
const b = block('chartkit-d3-axis');
|
|
6
|
+
const EMPTY_SPACE_BETWEEN_LABELS = 10;
|
|
7
|
+
// Note: this method do not prepared for rotated labels
|
|
8
|
+
const removeOverlappingYTicks = (axis) => {
|
|
9
|
+
var _a;
|
|
10
|
+
const a = axis.selectAll('g.tick').nodes();
|
|
11
|
+
if (a.length <= 1) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
for (let i = 0, x = 0; i < a.length; i++) {
|
|
15
|
+
const node = a[i];
|
|
16
|
+
const r = node.getBoundingClientRect();
|
|
17
|
+
if (r.bottom > x && i !== 0) {
|
|
18
|
+
(_a = node === null || node === void 0 ? void 0 : node.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(node);
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
x = r.top - EMPTY_SPACE_BETWEEN_LABELS;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
// FIXME: add overflow ellipsis for the labels that out of boundaries
|
|
26
|
+
export const AxisY = ({ axises, width, height, scale }) => {
|
|
27
|
+
const ref = React.useRef(null);
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
if (!ref.current) {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const axis = axises[0];
|
|
33
|
+
const svgElement = select(ref.current);
|
|
34
|
+
svgElement.selectAll('*').remove();
|
|
35
|
+
const yAxisGenerator = axisLeft(scale)
|
|
36
|
+
.tickSize(width * -1)
|
|
37
|
+
.tickPadding(axis.labels.padding)
|
|
38
|
+
.tickFormat((value) => {
|
|
39
|
+
return formatAxisTickLabel({
|
|
40
|
+
axisType: axis.type,
|
|
41
|
+
value,
|
|
42
|
+
dateFormat: axis.labels.dateFormat,
|
|
43
|
+
numberFormat: axis.labels.numberFormat,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
svgElement.call(yAxisGenerator).attr('class', b());
|
|
47
|
+
svgElement.select('.domain').attr('d', `M0,${height}H0V0`);
|
|
48
|
+
svgElement
|
|
49
|
+
.selectAll('.tick text')
|
|
50
|
+
.style('font-size', axis.labels.style.fontSize)
|
|
51
|
+
.style('transform', 'translateY(-1px)');
|
|
52
|
+
const transformStyle = svgElement.select('.tick').attr('transform');
|
|
53
|
+
const { y } = parseTransformStyle(transformStyle);
|
|
54
|
+
if (y === height) {
|
|
55
|
+
// Remove stroke from tick that has the same y coordinate like domain
|
|
56
|
+
svgElement.select('.tick line').style('stroke', 'none');
|
|
57
|
+
}
|
|
58
|
+
if (axis.title.text) {
|
|
59
|
+
const textY = axis.title.height + axis.labels.padding;
|
|
60
|
+
svgElement
|
|
61
|
+
.append('text')
|
|
62
|
+
.attr('class', b('title'))
|
|
63
|
+
.attr('text-anchor', 'middle')
|
|
64
|
+
.attr('dy', -textY)
|
|
65
|
+
.attr('dx', -height / 2)
|
|
66
|
+
.attr('font-size', axis.title.style.fontSize)
|
|
67
|
+
.attr('transform', 'rotate(-90)')
|
|
68
|
+
.text(axis.title.text);
|
|
69
|
+
}
|
|
70
|
+
removeOverlappingYTicks(svgElement);
|
|
71
|
+
}, [axises, width, height, scale]);
|
|
72
|
+
return React.createElement("g", { ref: ref });
|
|
73
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ChartKitWidgetData } from '../../../../types/widget-data';
|
|
3
|
+
import './styles.css';
|
|
4
|
+
type Props = {
|
|
5
|
+
width: number;
|
|
6
|
+
height: number;
|
|
7
|
+
data: ChartKitWidgetData;
|
|
8
|
+
};
|
|
9
|
+
export declare const Chart: ({ width, height, data }: Props) => React.JSX.Element;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import block from 'bem-cn-lite';
|
|
3
|
+
import { AxisY } from './AxisY';
|
|
4
|
+
import { AxisX } from './AxisX';
|
|
5
|
+
import { Legend } from './Legend';
|
|
6
|
+
import { Title } from './Title';
|
|
7
|
+
import { Tooltip } from './Tooltip';
|
|
8
|
+
import { useChartDimensions, useChartEvents, useChartOptions, useLegend, useScales, useSeries, useShapes, useTooltip, } from '../hooks';
|
|
9
|
+
import { isAxisRelatedSeries } from '../utils';
|
|
10
|
+
import './styles.css';
|
|
11
|
+
const b = block('chartkit-d3');
|
|
12
|
+
export const Chart = ({ width, height, data }) => {
|
|
13
|
+
// FIXME: add data validation
|
|
14
|
+
const { series } = data;
|
|
15
|
+
const svgRef = React.createRef();
|
|
16
|
+
const hasAxisRelatedSeries = series.data.some(isAxisRelatedSeries);
|
|
17
|
+
const { chartHovered, handleMouseEnter, handleMouseLeave } = useChartEvents();
|
|
18
|
+
const { chart, legend, title, tooltip, xAxis, yAxis } = useChartOptions(data);
|
|
19
|
+
const { boundsWidth, boundsHeight, legendHeight } = useChartDimensions({
|
|
20
|
+
width,
|
|
21
|
+
height,
|
|
22
|
+
margin: chart.margin,
|
|
23
|
+
legend,
|
|
24
|
+
title,
|
|
25
|
+
xAxis,
|
|
26
|
+
yAxis,
|
|
27
|
+
});
|
|
28
|
+
const { activeLegendItems, handleLegendItemClick } = useLegend({ series: series.data });
|
|
29
|
+
const { chartSeries } = useSeries({ activeLegendItems, series: series.data });
|
|
30
|
+
const { xScale, yScale } = useScales({
|
|
31
|
+
boundsWidth,
|
|
32
|
+
boundsHeight,
|
|
33
|
+
series: chartSeries,
|
|
34
|
+
xAxis,
|
|
35
|
+
yAxis,
|
|
36
|
+
});
|
|
37
|
+
const { hovered, pointerPosition, handleSeriesMouseMove, handleSeriesMouseLeave } = useTooltip({
|
|
38
|
+
tooltip,
|
|
39
|
+
});
|
|
40
|
+
const { shapes } = useShapes({
|
|
41
|
+
series: chartSeries,
|
|
42
|
+
xAxis,
|
|
43
|
+
xScale,
|
|
44
|
+
yAxis,
|
|
45
|
+
yScale,
|
|
46
|
+
svgContainer: svgRef.current,
|
|
47
|
+
onSeriesMouseMove: handleSeriesMouseMove,
|
|
48
|
+
onSeriesMouseLeave: handleSeriesMouseLeave,
|
|
49
|
+
});
|
|
50
|
+
return (React.createElement(React.Fragment, null,
|
|
51
|
+
React.createElement("svg", { ref: svgRef, className: b({ hovered: chartHovered }), width: width, height: height, onMouseEnter: handleMouseEnter, onMouseLeave: handleMouseLeave },
|
|
52
|
+
title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
|
|
53
|
+
React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[
|
|
54
|
+
chart.margin.left,
|
|
55
|
+
chart.margin.top + ((title === null || title === void 0 ? void 0 : title.height) || 0),
|
|
56
|
+
].join(',')})` },
|
|
57
|
+
hasAxisRelatedSeries && (React.createElement(React.Fragment, null,
|
|
58
|
+
React.createElement(AxisY, { axises: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale }),
|
|
59
|
+
React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
60
|
+
React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale })))),
|
|
61
|
+
shapes),
|
|
62
|
+
legend.enabled && (React.createElement(Legend, { width: boundsWidth, offsetWidth: chart.margin.left, height: legendHeight, offsetHeight: height - legendHeight / 2, chartSeries: chartSeries, onItemClick: handleLegendItemClick }))),
|
|
63
|
+
React.createElement(Tooltip, { hovered: hovered, pointerPosition: pointerPosition, tooltip: tooltip, xAxis: xAxis, yAxis: yAxis[0] })));
|
|
64
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { ChartSeries, OnLegendItemClick } from '../hooks';
|
|
3
|
+
type Props = {
|
|
4
|
+
width: number;
|
|
5
|
+
height: number;
|
|
6
|
+
offsetWidth: number;
|
|
7
|
+
offsetHeight: number;
|
|
8
|
+
chartSeries: ChartSeries[];
|
|
9
|
+
onItemClick: OnLegendItemClick;
|
|
10
|
+
};
|
|
11
|
+
export declare const Legend: (props: Props) => React.JSX.Element;
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import block from 'bem-cn-lite';
|
|
3
|
+
import { select } from 'd3';
|
|
4
|
+
const b = block('chartkit-d3-legend');
|
|
5
|
+
export const Legend = (props) => {
|
|
6
|
+
const { width, offsetWidth, height, offsetHeight, chartSeries, onItemClick } = props;
|
|
7
|
+
return (React.createElement("g", { width: width, height: height, ref: (node) => {
|
|
8
|
+
if (!node) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const size = 10;
|
|
12
|
+
const textWidths = [0];
|
|
13
|
+
const svgElement = select(node);
|
|
14
|
+
svgElement.selectAll('*').remove();
|
|
15
|
+
const legendItemTemplate = svgElement
|
|
16
|
+
.selectAll('legend-history')
|
|
17
|
+
.data(chartSeries)
|
|
18
|
+
.enter()
|
|
19
|
+
.append('g')
|
|
20
|
+
.attr('class', b('item'))
|
|
21
|
+
.on('click', function (e, d) {
|
|
22
|
+
onItemClick({ name: d.name, metaKey: e.metaKey });
|
|
23
|
+
});
|
|
24
|
+
svgElement
|
|
25
|
+
.selectAll('*')
|
|
26
|
+
.data(chartSeries)
|
|
27
|
+
.append('text')
|
|
28
|
+
.text(function (d) {
|
|
29
|
+
return d.name;
|
|
30
|
+
})
|
|
31
|
+
.each(function () {
|
|
32
|
+
textWidths.push(this.getComputedTextLength());
|
|
33
|
+
})
|
|
34
|
+
.remove();
|
|
35
|
+
legendItemTemplate
|
|
36
|
+
.append('rect')
|
|
37
|
+
.attr('x', function (_d, i) {
|
|
38
|
+
return (offsetWidth +
|
|
39
|
+
i * size +
|
|
40
|
+
textWidths.slice(0, i + 1).reduce((acc, tw) => acc + tw, 0));
|
|
41
|
+
})
|
|
42
|
+
.attr('y', offsetHeight - size / 2)
|
|
43
|
+
.attr('width', size)
|
|
44
|
+
.attr('height', size)
|
|
45
|
+
.style('fill', function (d) {
|
|
46
|
+
return d.color;
|
|
47
|
+
});
|
|
48
|
+
legendItemTemplate
|
|
49
|
+
.append('text')
|
|
50
|
+
.attr('x', function (_d, i) {
|
|
51
|
+
return (offsetWidth +
|
|
52
|
+
i * size +
|
|
53
|
+
size +
|
|
54
|
+
textWidths.slice(0, i + 1).reduce((acc, tw) => acc + tw, 0));
|
|
55
|
+
})
|
|
56
|
+
.attr('y', offsetHeight)
|
|
57
|
+
.attr('class', function (d) {
|
|
58
|
+
const mods = { selected: d.visible, unselected: !d.visible };
|
|
59
|
+
return b('item-text', mods);
|
|
60
|
+
})
|
|
61
|
+
.text(function (d) {
|
|
62
|
+
return ('name' in d && d.name);
|
|
63
|
+
})
|
|
64
|
+
.style('alignment-baseline', 'middle');
|
|
65
|
+
} }));
|
|
66
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import block from 'bem-cn-lite';
|
|
3
|
+
const b = block('chartkit-d3-title');
|
|
4
|
+
export const Title = (props) => {
|
|
5
|
+
const { chartWidth, text, height, style } = props;
|
|
6
|
+
return (React.createElement("text", { className: b(), dx: chartWidth / 2, dy: height / 2, dominantBaseline: "middle", textAnchor: "middle", style: { fontSize: style === null || style === void 0 ? void 0 : style.fontSize, lineHeight: `${height}px` } },
|
|
7
|
+
React.createElement("tspan", null, text)));
|
|
8
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { TooltipHoveredData } from '../../../../../types/widget-data';
|
|
3
|
+
import type { PreparedAxis } from '../../hooks';
|
|
4
|
+
type Props = {
|
|
5
|
+
hovered: TooltipHoveredData;
|
|
6
|
+
xAxis: PreparedAxis;
|
|
7
|
+
yAxis: PreparedAxis;
|
|
8
|
+
};
|
|
9
|
+
export declare const DefaultContent: ({ hovered, xAxis, yAxis }: Props) => React.JSX.Element | null;
|
|
10
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
|
|
3
|
+
const { data, series } = hovered;
|
|
4
|
+
switch (series.type) {
|
|
5
|
+
case 'scatter': {
|
|
6
|
+
const scatterData = data;
|
|
7
|
+
const xRow = xAxis.type === 'category' ? scatterData.category : scatterData.x;
|
|
8
|
+
const yRow = yAxis.type === 'category' ? scatterData.category : scatterData.y;
|
|
9
|
+
return (React.createElement("div", null,
|
|
10
|
+
React.createElement("div", null,
|
|
11
|
+
React.createElement("span", null, "X:\u00A0"),
|
|
12
|
+
React.createElement("b", null, xRow)),
|
|
13
|
+
React.createElement("div", null,
|
|
14
|
+
React.createElement("span", null, "Y:\u00A0"),
|
|
15
|
+
React.createElement("b", null, yRow))));
|
|
16
|
+
}
|
|
17
|
+
default: {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|