@gravity-ui/chartkit 4.6.0 → 4.7.0
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/examples/bar-x/Basic.d.ts +4 -0
- package/build/plugins/d3/examples/bar-x/Basic.js +78 -0
- package/build/plugins/d3/examples/bar-x/GroupedColumns.d.ts +2 -0
- package/build/plugins/d3/examples/bar-x/GroupedColumns.js +44 -0
- package/build/plugins/d3/examples/bar-x/StackedColumns.d.ts +2 -0
- package/build/plugins/d3/examples/bar-x/StackedColumns.js +45 -0
- package/build/plugins/d3/examples/nintendoGames.d.ts +62 -0
- package/build/plugins/d3/examples/nintendoGames.js +12037 -0
- package/build/plugins/d3/examples/pie/Basic.d.ts +2 -0
- package/build/plugins/d3/examples/pie/Basic.js +30 -0
- package/build/plugins/d3/examples/pie/Donut.d.ts +2 -0
- package/build/plugins/d3/examples/pie/Donut.js +31 -0
- package/build/plugins/d3/examples/scatter/Basic.d.ts +2 -0
- package/build/plugins/d3/examples/scatter/Basic.js +66 -0
- package/build/plugins/d3/renderer/D3Widget.js +11 -1
- package/build/plugins/d3/renderer/components/AxisX.d.ts +1 -2
- package/build/plugins/d3/renderer/components/AxisX.js +8 -21
- package/build/plugins/d3/renderer/components/AxisY.js +50 -18
- package/build/plugins/d3/renderer/components/Chart.js +25 -17
- package/build/plugins/d3/renderer/components/Legend.js +20 -22
- package/build/plugins/d3/renderer/components/Title.js +1 -1
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.d.ts +2 -2
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +8 -0
- package/build/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.d.ts +14 -0
- package/build/plugins/d3/renderer/components/Tooltip/TooltipTriggerArea.js +70 -0
- package/build/plugins/d3/renderer/components/Tooltip/index.d.ts +5 -3
- package/build/plugins/d3/renderer/components/Tooltip/index.js +4 -2
- package/build/plugins/d3/renderer/components/styles.css +3 -0
- package/build/plugins/d3/renderer/constants/defaults/axis.d.ts +9 -0
- package/build/plugins/d3/renderer/constants/defaults/axis.js +6 -0
- package/build/plugins/d3/renderer/constants/defaults/index.d.ts +1 -0
- package/build/plugins/d3/renderer/constants/defaults/index.js +1 -0
- package/build/plugins/d3/renderer/constants/defaults/series-options.d.ts +11 -0
- package/build/plugins/d3/renderer/constants/defaults/series-options.js +41 -0
- package/build/plugins/d3/renderer/constants/index.d.ts +0 -1
- package/build/plugins/d3/renderer/constants/index.js +0 -1
- package/build/plugins/d3/renderer/d3-dispatcher.d.ts +1 -0
- package/build/plugins/d3/renderer/d3-dispatcher.js +4 -0
- package/build/plugins/d3/renderer/hooks/index.d.ts +0 -1
- package/build/plugins/d3/renderer/hooks/index.js +0 -1
- package/build/plugins/d3/renderer/hooks/useAxisScales/index.d.ts +6 -5
- package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +2 -1
- package/build/plugins/d3/renderer/hooks/useChartDimensions/index.js +8 -41
- package/build/plugins/d3/renderer/hooks/useChartDimensions/utils.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useChartDimensions/utils.js +17 -4
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.d.ts +2 -4
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.js +4 -23
- package/build/plugins/d3/renderer/hooks/useChartOptions/index.d.ts +1 -1
- package/build/plugins/d3/renderer/hooks/useChartOptions/index.js +2 -10
- package/build/plugins/d3/renderer/hooks/useChartOptions/title.d.ts +1 -1
- package/build/plugins/d3/renderer/hooks/useChartOptions/title.js +1 -0
- package/build/plugins/d3/renderer/hooks/useChartOptions/types.d.ts +8 -5
- package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.d.ts +5 -3
- package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +61 -6
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.d.ts +1 -1
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +20 -12
- package/build/plugins/d3/renderer/hooks/useSeries/index.d.ts +1 -0
- package/build/plugins/d3/renderer/hooks/useSeries/index.js +8 -2
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-legend.js +8 -3
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-options.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-options.js +5 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.js +6 -3
- package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +5 -1
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x/index.d.ts +12 -0
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x/index.js +91 -0
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x/prepare-data.d.ts +19 -0
- package/build/plugins/d3/renderer/hooks/useShapes/{bar-x.js → bar-x/prepare-data.js} +9 -88
- package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +13 -10
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +28 -13
- package/build/plugins/d3/renderer/hooks/useShapes/pie.d.ts +6 -4
- package/build/plugins/d3/renderer/hooks/useShapes/pie.js +98 -20
- package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.d.ts +15 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter/index.js +89 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter/prepare-data.d.ts +19 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter/prepare-data.js +55 -0
- package/build/plugins/d3/renderer/hooks/useShapes/styles.css +1 -9
- package/build/plugins/d3/renderer/hooks/useTooltip/index.d.ts +6 -6
- package/build/plugins/d3/renderer/hooks/useTooltip/index.js +15 -17
- package/build/plugins/d3/renderer/hooks/useTooltip/types.d.ts +0 -6
- package/build/plugins/d3/renderer/utils/axis-generators/bottom.d.ts +3 -1
- package/build/plugins/d3/renderer/utils/axis-generators/bottom.js +77 -38
- package/build/plugins/d3/renderer/utils/axis.js +0 -6
- package/build/plugins/d3/renderer/utils/index.d.ts +5 -0
- package/build/plugins/d3/renderer/utils/index.js +13 -8
- package/build/plugins/d3/renderer/utils/math.d.ts +2 -0
- package/build/plugins/d3/renderer/utils/math.js +8 -0
- package/build/plugins/d3/renderer/utils/text.d.ts +6 -6
- package/build/plugins/d3/renderer/utils/text.js +25 -15
- package/build/types/widget-data/axis.d.ts +10 -0
- package/build/types/widget-data/series.d.ts +51 -0
- package/build/types/widget-data/tooltip.d.ts +18 -7
- package/package.json +2 -2
- package/build/plugins/d3/renderer/hooks/useChartEvents/index.d.ts +0 -5
- package/build/plugins/d3/renderer/hooks/useChartEvents/index.js +0 -15
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x.d.ts +0 -21
- package/build/plugins/d3/renderer/hooks/useShapes/defaults.d.ts +0 -5
- package/build/plugins/d3/renderer/hooks/useShapes/defaults.js +0 -5
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.d.ts +0 -19
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.js +0 -89
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ChartKit } from '../../../../components/ChartKit';
|
|
3
|
+
import nintendoGames from '../nintendoGames';
|
|
4
|
+
import { groups } from 'd3';
|
|
5
|
+
function prepareData() {
|
|
6
|
+
const gamesByPlatform = groups(nintendoGames, (item) => item.platform);
|
|
7
|
+
return gamesByPlatform.map(([platform, games]) => ({
|
|
8
|
+
name: platform,
|
|
9
|
+
value: games.length,
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
export const BasicPie = () => {
|
|
13
|
+
const data = prepareData();
|
|
14
|
+
const widgetData = {
|
|
15
|
+
series: {
|
|
16
|
+
data: [
|
|
17
|
+
{
|
|
18
|
+
type: 'pie',
|
|
19
|
+
data: data,
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
},
|
|
23
|
+
legend: { enabled: true },
|
|
24
|
+
title: {
|
|
25
|
+
text: 'Platforms',
|
|
26
|
+
style: { fontSize: '12px', fontWeight: 'normal' },
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
return React.createElement(ChartKit, { type: "d3", data: widgetData });
|
|
30
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ChartKit } from '../../../../components/ChartKit';
|
|
3
|
+
import nintendoGames from '../nintendoGames';
|
|
4
|
+
import { groups } from 'd3';
|
|
5
|
+
function prepareData() {
|
|
6
|
+
const gamesByPlatform = groups(nintendoGames, (d) => d.esrb_rating || 'unknown');
|
|
7
|
+
return gamesByPlatform.map(([value, games]) => ({
|
|
8
|
+
name: value,
|
|
9
|
+
value: games.length,
|
|
10
|
+
}));
|
|
11
|
+
}
|
|
12
|
+
export const Donut = () => {
|
|
13
|
+
const data = prepareData();
|
|
14
|
+
const widgetData = {
|
|
15
|
+
series: {
|
|
16
|
+
data: [
|
|
17
|
+
{
|
|
18
|
+
type: 'pie',
|
|
19
|
+
innerRadius: '50%',
|
|
20
|
+
data: data,
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
legend: { enabled: true },
|
|
25
|
+
title: {
|
|
26
|
+
text: 'ESRB ratings',
|
|
27
|
+
style: { fontSize: '12px', fontWeight: 'normal' },
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
return React.createElement(ChartKit, { type: "d3", data: widgetData });
|
|
31
|
+
};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { ChartKit } from '../../../../components/ChartKit';
|
|
3
|
+
import nintendoGames from '../nintendoGames';
|
|
4
|
+
import { dateTime } from '@gravity-ui/date-utils';
|
|
5
|
+
function prepareData() {
|
|
6
|
+
const dataset = nintendoGames.filter((d) => d.date && d.user_score);
|
|
7
|
+
const data = dataset.map((d) => ({
|
|
8
|
+
x: d.date || undefined,
|
|
9
|
+
y: d.user_score || undefined,
|
|
10
|
+
custom: d,
|
|
11
|
+
}));
|
|
12
|
+
return {
|
|
13
|
+
series: [
|
|
14
|
+
{
|
|
15
|
+
data,
|
|
16
|
+
name: 'Nintendo games',
|
|
17
|
+
},
|
|
18
|
+
],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
export const Basic = () => {
|
|
22
|
+
const { series } = prepareData();
|
|
23
|
+
const widgetData = {
|
|
24
|
+
series: {
|
|
25
|
+
data: series.map((s) => ({
|
|
26
|
+
type: 'scatter',
|
|
27
|
+
data: s.data.filter((d) => d.x),
|
|
28
|
+
name: s.name,
|
|
29
|
+
})),
|
|
30
|
+
},
|
|
31
|
+
yAxis: [
|
|
32
|
+
{
|
|
33
|
+
title: {
|
|
34
|
+
text: 'User score',
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
xAxis: {
|
|
39
|
+
type: 'datetime',
|
|
40
|
+
title: {
|
|
41
|
+
text: 'Release dates',
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
tooltip: {
|
|
45
|
+
renderer: (d) => {
|
|
46
|
+
var _a;
|
|
47
|
+
const point = (_a = d.hovered[0]) === null || _a === void 0 ? void 0 : _a.data;
|
|
48
|
+
if (!point) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const title = point.custom.title;
|
|
52
|
+
const score = point.custom.user_score;
|
|
53
|
+
const date = dateTime({ input: point.custom.date }).format('DD MMM YYYY');
|
|
54
|
+
return (React.createElement(React.Fragment, null,
|
|
55
|
+
React.createElement("b", null, title),
|
|
56
|
+
React.createElement("br", null),
|
|
57
|
+
"Release date: ",
|
|
58
|
+
date,
|
|
59
|
+
React.createElement("br", null),
|
|
60
|
+
"User score: ",
|
|
61
|
+
score));
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
return React.createElement(ChartKit, { type: "d3", data: widgetData });
|
|
66
|
+
};
|
|
@@ -4,9 +4,19 @@ import debounce from 'lodash/debounce';
|
|
|
4
4
|
import { getRandomCKId } from '../../../utils';
|
|
5
5
|
import { Chart } from './components';
|
|
6
6
|
const D3Widget = React.forwardRef(function D3Widget(props, forwardedRef) {
|
|
7
|
+
const { data, onLoad, onRender } = props;
|
|
7
8
|
const ref = React.useRef(null);
|
|
8
9
|
const debounced = React.useRef();
|
|
9
10
|
const [dimensions, setDimensions] = React.useState();
|
|
11
|
+
//FIXME: add chartPerfomance data to callbacks;
|
|
12
|
+
React.useLayoutEffect(() => {
|
|
13
|
+
if (onLoad) {
|
|
14
|
+
onLoad({});
|
|
15
|
+
}
|
|
16
|
+
if (onRender) {
|
|
17
|
+
onRender({});
|
|
18
|
+
}
|
|
19
|
+
}, []);
|
|
10
20
|
const handleResize = React.useCallback(() => {
|
|
11
21
|
var _a;
|
|
12
22
|
const parentElement = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.parentElement;
|
|
@@ -44,6 +54,6 @@ const D3Widget = React.forwardRef(function D3Widget(props, forwardedRef) {
|
|
|
44
54
|
width: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) || '100%',
|
|
45
55
|
height: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) || '100%',
|
|
46
56
|
position: 'relative',
|
|
47
|
-
} }, (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) && (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) && (React.createElement(Chart, { top: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.top) || 0, left: dimensions.left || 0, width: dimensions.width, height: dimensions.height, data:
|
|
57
|
+
} }, (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) && (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) && (React.createElement(Chart, { top: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.top) || 0, left: dimensions.left || 0, width: dimensions.width, height: dimensions.height, data: data }))));
|
|
48
58
|
});
|
|
49
59
|
export default D3Widget;
|
|
@@ -5,7 +5,6 @@ type Props = {
|
|
|
5
5
|
width: number;
|
|
6
6
|
height: number;
|
|
7
7
|
scale: ChartScale;
|
|
8
|
-
chartWidth: number;
|
|
9
8
|
};
|
|
10
|
-
export declare const AxisX: React.MemoExoticComponent<({ axis, width, height, scale
|
|
9
|
+
export declare const AxisX: React.MemoExoticComponent<({ axis, width, height, scale }: Props) => React.JSX.Element>;
|
|
11
10
|
export {};
|
|
@@ -18,14 +18,12 @@ function getLabelFormatter({ axis, scale }) {
|
|
|
18
18
|
});
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
-
export const AxisX = React.memo(({ axis, width, height, scale
|
|
21
|
+
export const AxisX = React.memo(({ axis, width, height, scale }) => {
|
|
22
22
|
const ref = React.useRef(null);
|
|
23
23
|
React.useEffect(() => {
|
|
24
24
|
if (!ref.current) {
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
-
const svgElement = select(ref.current);
|
|
28
|
-
svgElement.selectAll('*').remove();
|
|
29
27
|
const xAxisGenerator = axisBottom({
|
|
30
28
|
scale: scale,
|
|
31
29
|
ticks: {
|
|
@@ -34,40 +32,29 @@ export const AxisX = React.memo(({ axis, width, height, scale, chartWidth }) =>
|
|
|
34
32
|
labelsPaddings: axis.labels.padding,
|
|
35
33
|
labelsMargin: axis.labels.margin,
|
|
36
34
|
labelsStyle: axis.labels.style,
|
|
35
|
+
labelsMaxWidth: axis.labels.maxWidth,
|
|
36
|
+
labelsLineHeight: axis.labels.lineHeight,
|
|
37
37
|
count: getTicksCount({ axis, range: width }),
|
|
38
38
|
maxTickCount: getMaxTickCount({ axis, width }),
|
|
39
|
-
|
|
39
|
+
rotation: axis.labels.rotation,
|
|
40
40
|
},
|
|
41
41
|
domain: {
|
|
42
42
|
size: width,
|
|
43
43
|
color: axis.lineColor,
|
|
44
44
|
},
|
|
45
45
|
});
|
|
46
|
+
const svgElement = select(ref.current);
|
|
47
|
+
svgElement.selectAll('*').remove();
|
|
46
48
|
svgElement.call(xAxisGenerator).attr('class', b());
|
|
47
|
-
if (axis.labels.enabled) {
|
|
48
|
-
svgElement.style('font-size', axis.labels.style.fontSize);
|
|
49
|
-
}
|
|
50
|
-
// add an ellipsis to the labels on the right that go beyond the boundaries of the chart
|
|
51
|
-
svgElement.selectAll('.tick text').each(function () {
|
|
52
|
-
var _a;
|
|
53
|
-
const node = this;
|
|
54
|
-
const textRect = node.getBBox();
|
|
55
|
-
const matrix = ((_a = node.transform.baseVal.consolidate()) === null || _a === void 0 ? void 0 : _a.matrix) || {};
|
|
56
|
-
const right = matrix.a * textRect.right + matrix.c * textRect.bottom + matrix.e;
|
|
57
|
-
if (right > chartWidth) {
|
|
58
|
-
const maxWidth = textRect.width - (right - chartWidth) * 2;
|
|
59
|
-
select(node).call(setEllipsisForOverflowText, maxWidth);
|
|
60
|
-
}
|
|
61
|
-
});
|
|
62
49
|
// add an axis header if necessary
|
|
63
50
|
if (axis.title.text) {
|
|
64
|
-
const
|
|
51
|
+
const y = axis.title.height + axis.title.margin + axis.labels.height + axis.labels.margin;
|
|
65
52
|
svgElement
|
|
66
53
|
.append('text')
|
|
67
54
|
.attr('class', b('title'))
|
|
68
55
|
.attr('text-anchor', 'middle')
|
|
69
56
|
.attr('x', width / 2)
|
|
70
|
-
.attr('y',
|
|
57
|
+
.attr('y', y)
|
|
71
58
|
.attr('font-size', axis.title.style.fontSize)
|
|
72
59
|
.text(axis.title.text)
|
|
73
60
|
.call(setEllipsisForOverflowText, width);
|
|
@@ -1,9 +1,29 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { axisLeft, select } from 'd3';
|
|
3
3
|
import { block } from '../../../../utils/cn';
|
|
4
|
-
import { formatAxisTickLabel, getClosestPointsRange, parseTransformStyle, setEllipsisForOverflowText, setEllipsisForOverflowTexts, getTicksCount, getScaleTicks, } from '../utils';
|
|
4
|
+
import { formatAxisTickLabel, getClosestPointsRange, parseTransformStyle, setEllipsisForOverflowText, setEllipsisForOverflowTexts, getTicksCount, getScaleTicks, calculateSin, calculateCos, } from '../utils';
|
|
5
5
|
const b = block('d3-axis');
|
|
6
|
-
|
|
6
|
+
function transformLabel(node, axis) {
|
|
7
|
+
let topOffset = axis.labels.lineHeight / 2;
|
|
8
|
+
let leftOffset = -axis.labels.margin;
|
|
9
|
+
if (axis.labels.rotation) {
|
|
10
|
+
if (axis.labels.rotation > 0) {
|
|
11
|
+
leftOffset -= axis.labels.lineHeight * calculateSin(axis.labels.rotation);
|
|
12
|
+
topOffset = axis.labels.lineHeight * calculateCos(axis.labels.rotation);
|
|
13
|
+
if (axis.labels.rotation % 360 === 90) {
|
|
14
|
+
topOffset = ((node === null || node === void 0 ? void 0 : node.getBoundingClientRect().width) || 0) / 2;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
topOffset = 0;
|
|
19
|
+
if (axis.labels.rotation % 360 === -90) {
|
|
20
|
+
topOffset = -((node === null || node === void 0 ? void 0 : node.getBoundingClientRect().width) || 0) / 2;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return `translate(${leftOffset}px, ${topOffset}px) rotate(${axis.labels.rotation}deg)`;
|
|
24
|
+
}
|
|
25
|
+
return `translate(${leftOffset}px, ${topOffset}px)`;
|
|
26
|
+
}
|
|
7
27
|
export const AxisY = ({ axises, width, height, scale }) => {
|
|
8
28
|
const ref = React.useRef(null);
|
|
9
29
|
React.useEffect(() => {
|
|
@@ -40,9 +60,19 @@ export const AxisY = ({ axises, width, height, scale }) => {
|
|
|
40
60
|
if (axis.labels.enabled) {
|
|
41
61
|
const tickTexts = svgElement
|
|
42
62
|
.selectAll('.tick text')
|
|
63
|
+
// The offset must be applied before the labels are rotated.
|
|
64
|
+
// Therefore, we reset the values and make an offset in transform attribute.
|
|
65
|
+
// FIXME: give up axisLeft(d3) and switch to our own generation method
|
|
66
|
+
.attr('x', null)
|
|
67
|
+
.attr('dy', null)
|
|
43
68
|
.style('font-size', axis.labels.style.fontSize)
|
|
44
|
-
.style('transform',
|
|
45
|
-
|
|
69
|
+
.style('transform', function () {
|
|
70
|
+
return transformLabel(this, axis);
|
|
71
|
+
});
|
|
72
|
+
const textMaxWidth = !axis.labels.rotation || Math.abs(axis.labels.rotation) % 360 !== 90
|
|
73
|
+
? axis.labels.maxWidth
|
|
74
|
+
: (height - axis.labels.padding * (tickTexts.size() - 1)) / tickTexts.size();
|
|
75
|
+
tickTexts.call(setEllipsisForOverflowTexts, textMaxWidth);
|
|
46
76
|
}
|
|
47
77
|
const transformStyle = svgElement.select('.tick').attr('transform');
|
|
48
78
|
const { y } = parseTransformStyle(transformStyle);
|
|
@@ -52,21 +82,23 @@ export const AxisY = ({ axises, width, height, scale }) => {
|
|
|
52
82
|
}
|
|
53
83
|
// remove overlapping ticks
|
|
54
84
|
// Note: this method do not prepared for rotated labels
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
85
|
+
if (!axis.labels.rotation) {
|
|
86
|
+
let elementY = 0;
|
|
87
|
+
svgElement
|
|
88
|
+
.selectAll('.tick')
|
|
89
|
+
.filter(function (_d, index) {
|
|
90
|
+
const node = this;
|
|
91
|
+
const r = node.getBoundingClientRect();
|
|
92
|
+
if (r.bottom > elementY && index !== 0) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
elementY = r.top - axis.labels.padding;
|
|
96
|
+
return false;
|
|
97
|
+
})
|
|
98
|
+
.remove();
|
|
99
|
+
}
|
|
68
100
|
if (axis.title.text) {
|
|
69
|
-
const textY = axis.title.
|
|
101
|
+
const textY = axis.title.margin + axis.labels.margin + axis.labels.width;
|
|
70
102
|
svgElement
|
|
71
103
|
.append('text')
|
|
72
104
|
.attr('class', b('title'))
|
|
@@ -1,22 +1,30 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { block } from '../../../../utils/cn';
|
|
3
|
-
import {
|
|
3
|
+
import { getD3Dispatcher } from '../d3-dispatcher';
|
|
4
|
+
import { useAxisScales, useChartDimensions, useChartOptions, useSeries, useShapes, useTooltip, } from '../hooks';
|
|
4
5
|
import { AxisY } from './AxisY';
|
|
5
6
|
import { AxisX } from './AxisX';
|
|
6
7
|
import { Legend } from './Legend';
|
|
7
8
|
import { Title } from './Title';
|
|
8
|
-
import { Tooltip } from './Tooltip';
|
|
9
|
+
import { Tooltip, TooltipTriggerArea } from './Tooltip';
|
|
10
|
+
import { getPreparedXAxis } from '../hooks/useChartOptions/x-axis';
|
|
11
|
+
import { getWidthOccupiedByYAxis } from '../hooks/useChartDimensions/utils';
|
|
12
|
+
import { getPreparedYAxis } from '../hooks/useChartOptions/y-axis';
|
|
9
13
|
import './styles.css';
|
|
10
14
|
const b = block('d3');
|
|
11
15
|
export const Chart = (props) => {
|
|
12
16
|
// FIXME: add data validation
|
|
13
17
|
const { top, left, width, height, data } = props;
|
|
14
18
|
const svgRef = React.createRef();
|
|
15
|
-
const
|
|
16
|
-
|
|
19
|
+
const dispatcher = React.useMemo(() => {
|
|
20
|
+
return getD3Dispatcher();
|
|
21
|
+
}, []);
|
|
22
|
+
const { chart, title, tooltip } = useChartOptions({
|
|
17
23
|
data,
|
|
18
24
|
});
|
|
19
|
-
const {
|
|
25
|
+
const xAxis = React.useMemo(() => getPreparedXAxis({ xAxis: data.xAxis, width, series: data.series.data }), [data, width]);
|
|
26
|
+
const yAxis = React.useMemo(() => getPreparedYAxis({ series: data.series.data, yAxis: data.yAxis }), [data, width]);
|
|
27
|
+
const { legendItems, legendConfig, preparedSeries, preparedSeriesOptions, preparedLegend, handleLegendItemClick, } = useSeries({
|
|
20
28
|
chartWidth: width,
|
|
21
29
|
chartHeight: height,
|
|
22
30
|
chartMargin: chart.margin,
|
|
@@ -40,33 +48,33 @@ export const Chart = (props) => {
|
|
|
40
48
|
xAxis,
|
|
41
49
|
yAxis,
|
|
42
50
|
});
|
|
43
|
-
const { hovered, pointerPosition
|
|
44
|
-
|
|
45
|
-
});
|
|
46
|
-
const { shapes } = useShapes({
|
|
51
|
+
const { hovered, pointerPosition } = useTooltip({ dispatcher, tooltip });
|
|
52
|
+
const { shapes, shapesData } = useShapes({
|
|
47
53
|
top,
|
|
48
54
|
left,
|
|
49
55
|
boundsWidth,
|
|
50
56
|
boundsHeight,
|
|
57
|
+
dispatcher,
|
|
51
58
|
series: preparedSeries,
|
|
52
|
-
seriesOptions:
|
|
59
|
+
seriesOptions: preparedSeriesOptions,
|
|
53
60
|
xAxis,
|
|
54
61
|
xScale,
|
|
55
62
|
yAxis,
|
|
56
63
|
yScale,
|
|
57
64
|
svgContainer: svgRef.current,
|
|
58
|
-
onSeriesMouseMove: handleSeriesMouseMove,
|
|
59
|
-
onSeriesMouseLeave: handleSeriesMouseLeave,
|
|
60
65
|
});
|
|
66
|
+
const boundsOffsetTop = chart.margin.top;
|
|
67
|
+
const boundsOffsetLeft = chart.margin.left + getWidthOccupiedByYAxis({ preparedAxis: yAxis });
|
|
61
68
|
return (React.createElement(React.Fragment, null,
|
|
62
|
-
React.createElement("svg", { ref: svgRef, className: b(
|
|
69
|
+
React.createElement("svg", { ref: svgRef, className: b(), width: width, height: height },
|
|
63
70
|
title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
|
|
64
|
-
React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[
|
|
71
|
+
React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[boundsOffsetLeft, boundsOffsetTop].join(',')})` },
|
|
65
72
|
xScale && yScale && (React.createElement(React.Fragment, null,
|
|
66
73
|
React.createElement(AxisY, { axises: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale }),
|
|
67
74
|
React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
|
|
68
|
-
React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale
|
|
69
|
-
shapes
|
|
75
|
+
React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale })))),
|
|
76
|
+
shapes,
|
|
77
|
+
(tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled) && Boolean(shapesData.length) && (React.createElement(TooltipTriggerArea, { boundsWidth: boundsWidth, boundsHeight: boundsHeight, dispatcher: dispatcher, offsetLeft: left, offsetTop: top, shapesData: shapesData, svgContainer: svgRef.current }))),
|
|
70
78
|
preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick }))),
|
|
71
|
-
React.createElement(Tooltip, {
|
|
79
|
+
React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, xAxis: xAxis, yAxis: yAxis[0], hovered: hovered, pointerPosition: pointerPosition })));
|
|
72
80
|
};
|
|
@@ -76,7 +76,6 @@ export const Legend = (props) => {
|
|
|
76
76
|
: items;
|
|
77
77
|
pageItems.forEach((line, lineIndex) => {
|
|
78
78
|
var _a;
|
|
79
|
-
const textWidths = [];
|
|
80
79
|
const legendLine = svgElement.append('g').attr('class', b('line'));
|
|
81
80
|
const legendItemTemplate = legendLine
|
|
82
81
|
.selectAll('legend-history')
|
|
@@ -86,21 +85,23 @@ export const Legend = (props) => {
|
|
|
86
85
|
.attr('class', b('item'))
|
|
87
86
|
.on('click', function (e, d) {
|
|
88
87
|
onItemClick({ name: d.name, metaKey: e.metaKey });
|
|
89
|
-
})
|
|
90
|
-
.each(function (d) {
|
|
91
|
-
textWidths.push(d.textWidth);
|
|
92
88
|
});
|
|
89
|
+
const getXPosition = (i) => {
|
|
90
|
+
return line.slice(0, i).reduce((acc, legendItem) => {
|
|
91
|
+
return (acc +
|
|
92
|
+
legendItem.symbol.width +
|
|
93
|
+
legendItem.symbol.padding +
|
|
94
|
+
legendItem.textWidth +
|
|
95
|
+
legend.itemDistance);
|
|
96
|
+
}, 0);
|
|
97
|
+
};
|
|
93
98
|
legendItemTemplate
|
|
94
99
|
.append('rect')
|
|
95
|
-
.attr('x', function (
|
|
96
|
-
return (i
|
|
97
|
-
i * legend.itemDistance +
|
|
98
|
-
i * legendItem.symbol.padding +
|
|
99
|
-
textWidths.slice(0, i).reduce((acc, tw) => acc + tw, 0));
|
|
100
|
+
.attr('x', function (_d, i) {
|
|
101
|
+
return getXPosition(i);
|
|
100
102
|
})
|
|
101
103
|
.attr('y', (legendItem) => {
|
|
102
|
-
|
|
103
|
-
return config.offset.top + lineOffset - legendItem.symbol.height / 2;
|
|
104
|
+
return (legend.lineHeight - legendItem.symbol.height) / 2;
|
|
104
105
|
})
|
|
105
106
|
.attr('width', (legendItem) => {
|
|
106
107
|
return legendItem.symbol.width;
|
|
@@ -116,14 +117,9 @@ export const Legend = (props) => {
|
|
|
116
117
|
legendItemTemplate
|
|
117
118
|
.append('text')
|
|
118
119
|
.attr('x', function (legendItem, i) {
|
|
119
|
-
return (i
|
|
120
|
-
i * legend.itemDistance +
|
|
121
|
-
i * legendItem.symbol.padding +
|
|
122
|
-
legendItem.symbol.width +
|
|
123
|
-
legendItem.symbol.padding +
|
|
124
|
-
textWidths.slice(0, i).reduce((acc, tw) => acc + tw, 0));
|
|
120
|
+
return getXPosition(i) + legendItem.symbol.width + legendItem.symbol.padding;
|
|
125
121
|
})
|
|
126
|
-
.attr('
|
|
122
|
+
.attr('height', legend.lineHeight)
|
|
127
123
|
.attr('class', function (d) {
|
|
128
124
|
const mods = { selected: d.visible, unselected: !d.visible };
|
|
129
125
|
return b('item-text', mods);
|
|
@@ -131,8 +127,7 @@ export const Legend = (props) => {
|
|
|
131
127
|
.text(function (d) {
|
|
132
128
|
return ('name' in d && d.name);
|
|
133
129
|
})
|
|
134
|
-
.style('font-size', legend.itemStyle.fontSize)
|
|
135
|
-
.style('alignment-baseline', 'middle');
|
|
130
|
+
.style('font-size', legend.itemStyle.fontSize);
|
|
136
131
|
const contentWidth = ((_a = legendLine.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
|
|
137
132
|
const { left } = getLegendPosition({
|
|
138
133
|
align: legend.align,
|
|
@@ -140,12 +135,15 @@ export const Legend = (props) => {
|
|
|
140
135
|
offsetWidth: config.offset.left,
|
|
141
136
|
contentWidth,
|
|
142
137
|
});
|
|
143
|
-
|
|
138
|
+
const top = config.offset.top + legend.lineHeight * lineIndex;
|
|
139
|
+
legendLine.attr('transform', `translate(${[left, top].join(',')})`);
|
|
144
140
|
});
|
|
145
141
|
if (config.pagination) {
|
|
146
142
|
const transform = `translate(${[
|
|
147
143
|
config.offset.left,
|
|
148
|
-
config.offset.top +
|
|
144
|
+
config.offset.top +
|
|
145
|
+
legend.lineHeight * config.pagination.limit +
|
|
146
|
+
legend.lineHeight / 2,
|
|
149
147
|
].join(',')})`;
|
|
150
148
|
appendPaginator({
|
|
151
149
|
container: svgElement,
|
|
@@ -3,6 +3,6 @@ import { block } from '../../../../utils/cn';
|
|
|
3
3
|
const b = block('d3-title');
|
|
4
4
|
export const Title = (props) => {
|
|
5
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: {
|
|
6
|
+
return (React.createElement("text", { className: b(), dx: chartWidth / 2, dy: height / 2, dominantBaseline: "middle", textAnchor: "middle", style: Object.assign({ lineHeight: `${height}px` }, style) },
|
|
7
7
|
React.createElement("tspan", null, text)));
|
|
8
8
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { TooltipDataChunk } from '../../../../../types';
|
|
3
3
|
import type { PreparedAxis } from '../../hooks';
|
|
4
4
|
type Props = {
|
|
5
|
-
hovered:
|
|
5
|
+
hovered: TooltipDataChunk;
|
|
6
6
|
xAxis: PreparedAxis;
|
|
7
7
|
yAxis: PreparedAxis;
|
|
8
8
|
};
|
|
@@ -48,6 +48,14 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
|
|
|
48
48
|
": ",
|
|
49
49
|
yRow))));
|
|
50
50
|
}
|
|
51
|
+
case 'pie': {
|
|
52
|
+
const pieSeries = series;
|
|
53
|
+
return (React.createElement("div", null,
|
|
54
|
+
React.createElement("span", null,
|
|
55
|
+
pieSeries.name || pieSeries.id,
|
|
56
|
+
"\u00A0"),
|
|
57
|
+
React.createElement("span", null, pieSeries.value)));
|
|
58
|
+
}
|
|
51
59
|
default: {
|
|
52
60
|
return null;
|
|
53
61
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { Dispatch } from 'd3';
|
|
3
|
+
import type { ShapeData } from '../../hooks';
|
|
4
|
+
type Args = {
|
|
5
|
+
boundsWidth: number;
|
|
6
|
+
boundsHeight: number;
|
|
7
|
+
dispatcher: Dispatch<object>;
|
|
8
|
+
offsetTop: number;
|
|
9
|
+
offsetLeft: number;
|
|
10
|
+
shapesData: ShapeData[];
|
|
11
|
+
svgContainer: SVGSVGElement | null;
|
|
12
|
+
};
|
|
13
|
+
export declare const TooltipTriggerArea: (args: Args) => React.JSX.Element;
|
|
14
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import throttle from 'lodash/throttle';
|
|
3
|
+
import { bisector, pointer, sort } from 'd3';
|
|
4
|
+
import { extractD3DataFromNode, isNodeContainsD3Data } from '../../utils';
|
|
5
|
+
const THROTTLE_DELAY = 50;
|
|
6
|
+
const isNodeContainsData = (node) => {
|
|
7
|
+
return isNodeContainsD3Data(node);
|
|
8
|
+
};
|
|
9
|
+
const getCalculationType = (shapesData) => {
|
|
10
|
+
if (shapesData.every((d) => d.series.type === 'bar-x')) {
|
|
11
|
+
return 'x-primary';
|
|
12
|
+
}
|
|
13
|
+
return 'none';
|
|
14
|
+
};
|
|
15
|
+
export const TooltipTriggerArea = (args) => {
|
|
16
|
+
const { boundsWidth, boundsHeight, dispatcher, offsetTop, offsetLeft, shapesData, svgContainer } = args;
|
|
17
|
+
const rectRef = React.useRef(null);
|
|
18
|
+
const calculationType = React.useMemo(() => {
|
|
19
|
+
return getCalculationType(shapesData);
|
|
20
|
+
}, [shapesData]);
|
|
21
|
+
const xData = React.useMemo(() => {
|
|
22
|
+
return calculationType === 'x-primary'
|
|
23
|
+
? sort(new Set(shapesData.map((d) => d.x)))
|
|
24
|
+
: [];
|
|
25
|
+
}, [shapesData, calculationType]);
|
|
26
|
+
const handleXprimaryMouseMove = (e) => {
|
|
27
|
+
var _a, _b, _c;
|
|
28
|
+
const { left, top } = ((_a = rectRef.current) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) || { left: 0, top: 0 };
|
|
29
|
+
const [pointerX, pointerY] = pointer(e, svgContainer);
|
|
30
|
+
const barWidthOffset = shapesData[0].width / 2;
|
|
31
|
+
const xPosition = pointerX - left - barWidthOffset - window.pageXOffset;
|
|
32
|
+
const xDataIndex = bisector((d) => d).center(xData, xPosition);
|
|
33
|
+
const xNodes = Array.from(((_c = (_b = rectRef.current) === null || _b === void 0 ? void 0 : _b.parentElement) === null || _c === void 0 ? void 0 : _c.querySelectorAll(`[x="${xData[xDataIndex]}"]`)) || []);
|
|
34
|
+
let hoverShapeData;
|
|
35
|
+
if (xNodes.length === 1 && isNodeContainsData(xNodes[0])) {
|
|
36
|
+
hoverShapeData = [extractD3DataFromNode(xNodes[0])];
|
|
37
|
+
}
|
|
38
|
+
else if (xNodes.length > 1 && xNodes.every(isNodeContainsData)) {
|
|
39
|
+
const yPosition = pointerY - top - window.pageYOffset;
|
|
40
|
+
const xyNode = xNodes.find((node, i) => {
|
|
41
|
+
const { y, height } = extractD3DataFromNode(node);
|
|
42
|
+
if (i === xNodes.length - 1) {
|
|
43
|
+
return yPosition <= y + height;
|
|
44
|
+
}
|
|
45
|
+
return yPosition >= y && yPosition <= y + height;
|
|
46
|
+
});
|
|
47
|
+
if (xyNode) {
|
|
48
|
+
hoverShapeData = [extractD3DataFromNode(xyNode)];
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (hoverShapeData) {
|
|
52
|
+
const position = [pointerX - offsetLeft, pointerY - offsetTop];
|
|
53
|
+
dispatcher.call('hover-shape', e.target, hoverShapeData, position);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const handleMouseMove = (e) => {
|
|
57
|
+
switch (calculationType) {
|
|
58
|
+
case 'x-primary': {
|
|
59
|
+
handleXprimaryMouseMove(e);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const throttledHandleMouseMove = throttle(handleMouseMove, THROTTLE_DELAY);
|
|
65
|
+
const handleMouseLeave = () => {
|
|
66
|
+
throttledHandleMouseMove.cancel();
|
|
67
|
+
dispatcher.call('hover-shape', {}, undefined);
|
|
68
|
+
};
|
|
69
|
+
return (React.createElement("rect", { ref: rectRef, width: boundsWidth, height: boundsHeight, fill: "transparent", onMouseMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave }));
|
|
70
|
+
};
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { Dispatch } from 'd3';
|
|
3
|
+
import type { TooltipDataChunk } from '../../../../../types/widget-data';
|
|
3
4
|
import type { PointerPosition, PreparedAxis, PreparedTooltip } from '../../hooks';
|
|
5
|
+
export * from './TooltipTriggerArea';
|
|
4
6
|
type TooltipProps = {
|
|
7
|
+
dispatcher: Dispatch<object>;
|
|
5
8
|
tooltip: PreparedTooltip;
|
|
6
9
|
xAxis: PreparedAxis;
|
|
7
10
|
yAxis: PreparedAxis;
|
|
8
|
-
hovered?:
|
|
11
|
+
hovered?: TooltipDataChunk[];
|
|
9
12
|
pointerPosition?: PointerPosition;
|
|
10
13
|
};
|
|
11
14
|
export declare const Tooltip: (props: TooltipProps) => React.JSX.Element | null;
|
|
12
|
-
export {};
|