@gravity-ui/chartkit 3.4.3 → 3.5.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/renderer/D3Widget.js +3 -3
- package/build/plugins/d3/renderer/components/Chart.d.ts +3 -1
- package/build/plugins/d3/renderer/components/Chart.js +6 -1
- package/build/plugins/d3/renderer/components/Legend.js +78 -59
- package/build/plugins/d3/renderer/components/Tooltip/DefaultContent.js +12 -0
- package/build/plugins/d3/renderer/components/styles.css +4 -13
- package/build/plugins/d3/renderer/hooks/useChartOptions/chart.js +19 -12
- package/build/plugins/d3/renderer/hooks/useChartOptions/index.d.ts +3 -1
- package/build/plugins/d3/renderer/hooks/useLegend/index.js +35 -4
- package/build/plugins/d3/renderer/hooks/useSeries/index.js +48 -10
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x.d.ts +3 -0
- package/build/plugins/d3/renderer/hooks/useShapes/bar-x.js +5 -2
- package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +5 -0
- package/build/plugins/d3/renderer/hooks/useShapes/index.js +23 -2
- package/build/plugins/d3/renderer/hooks/useShapes/pie.d.ts +13 -0
- package/build/plugins/d3/renderer/hooks/useShapes/pie.js +51 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.d.ts +2 -0
- package/build/plugins/d3/renderer/hooks/useShapes/scatter.js +3 -2
- package/build/plugins/d3/renderer/hooks/useShapes/styles.css +16 -0
- package/build/plugins/d3/renderer/utils/index.d.ts +7 -1
- package/build/plugins/d3/renderer/utils/index.js +7 -12
- package/build/plugins/d3/renderer/utils/math.d.ts +23 -0
- package/build/plugins/d3/renderer/utils/math.js +43 -0
- package/build/plugins/highcharts/renderer/components/HighchartsComponent.js +1 -1
- package/build/plugins/highcharts/renderer/components/HighchartsReact.d.ts +18 -0
- package/build/plugins/highcharts/renderer/components/HighchartsReact.js +46 -0
- package/build/plugins/index.d.ts +1 -0
- package/build/plugins/index.js +1 -0
- package/build/types/widget-data/bar-x.d.ts +1 -1
- package/build/types/widget-data/base.d.ts +3 -0
- package/build/types/widget-data/pie.d.ts +18 -1
- package/build/types/widget-data/scatter.d.ts +1 -1
- package/package.json +1 -3
|
@@ -8,8 +8,8 @@ const D3Widget = React.forwardRef(function D3Widget(props, forwardedRef) {
|
|
|
8
8
|
const [dimensions, setDimensions] = React.useState();
|
|
9
9
|
const handleResize = React.useCallback(() => {
|
|
10
10
|
if (ref.current) {
|
|
11
|
-
const { width, height } = ref.current.getBoundingClientRect();
|
|
12
|
-
setDimensions({ width, height });
|
|
11
|
+
const { top, left, width, height } = ref.current.getBoundingClientRect();
|
|
12
|
+
setDimensions({ top, left, width, height });
|
|
13
13
|
}
|
|
14
14
|
}, []);
|
|
15
15
|
const debuncedHandleResize = React.useMemo(() => {
|
|
@@ -35,6 +35,6 @@ const D3Widget = React.forwardRef(function D3Widget(props, forwardedRef) {
|
|
|
35
35
|
// dimensions initialize
|
|
36
36
|
handleResize();
|
|
37
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 }))));
|
|
38
|
+
return (React.createElement("div", { ref: ref, style: { width: '100%', height: '100%', position: 'relative' } }, (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: props.data }))));
|
|
39
39
|
});
|
|
40
40
|
export default D3Widget;
|
|
@@ -2,9 +2,11 @@ import React from 'react';
|
|
|
2
2
|
import type { ChartKitWidgetData } from '../../../../types/widget-data';
|
|
3
3
|
import './styles.css';
|
|
4
4
|
type Props = {
|
|
5
|
+
top: number;
|
|
6
|
+
left: number;
|
|
5
7
|
width: number;
|
|
6
8
|
height: number;
|
|
7
9
|
data: ChartKitWidgetData;
|
|
8
10
|
};
|
|
9
|
-
export declare const Chart: (
|
|
11
|
+
export declare const Chart: (props: Props) => React.JSX.Element;
|
|
10
12
|
export {};
|
|
@@ -9,7 +9,8 @@ import { useChartDimensions, useChartEvents, useChartOptions, useLegend, useAxis
|
|
|
9
9
|
import { isAxisRelatedSeries } from '../utils';
|
|
10
10
|
import './styles.css';
|
|
11
11
|
const b = block('d3');
|
|
12
|
-
export const Chart = (
|
|
12
|
+
export const Chart = (props) => {
|
|
13
|
+
const { top, left, width, height, data } = props;
|
|
13
14
|
// FIXME: add data validation
|
|
14
15
|
const { series } = data;
|
|
15
16
|
const svgRef = React.createRef();
|
|
@@ -38,6 +39,10 @@ export const Chart = ({ width, height, data }) => {
|
|
|
38
39
|
tooltip,
|
|
39
40
|
});
|
|
40
41
|
const { shapes } = useShapes({
|
|
42
|
+
top,
|
|
43
|
+
left,
|
|
44
|
+
boundsWidth,
|
|
45
|
+
boundsHeight,
|
|
41
46
|
series: chartSeries,
|
|
42
47
|
xAxis,
|
|
43
48
|
xScale,
|
|
@@ -1,66 +1,85 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { select } from 'd3';
|
|
3
|
+
import get from 'lodash/get';
|
|
3
4
|
import { block } from '../../../../utils/cn';
|
|
5
|
+
import { isAxisRelatedSeries } from '../utils';
|
|
4
6
|
const b = block('d3-legend');
|
|
7
|
+
const getLegendItems = (series) => {
|
|
8
|
+
return series.reduce((acc, s) => {
|
|
9
|
+
const isAxisRelated = isAxisRelatedSeries(s);
|
|
10
|
+
const legendEnabled = get(s, 'legend.enabled', true);
|
|
11
|
+
if (isAxisRelated) {
|
|
12
|
+
acc.push(s);
|
|
13
|
+
}
|
|
14
|
+
else if (!isAxisRelated && legendEnabled) {
|
|
15
|
+
acc.push(...s.data);
|
|
16
|
+
}
|
|
17
|
+
return acc;
|
|
18
|
+
}, []);
|
|
19
|
+
};
|
|
5
20
|
export const Legend = (props) => {
|
|
6
21
|
const { width, offsetWidth, height, offsetHeight, chartSeries, onItemClick } = props;
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
.
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
22
|
+
const ref = React.useRef(null);
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
if (!ref.current) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const legendItems = getLegendItems(chartSeries);
|
|
28
|
+
const size = 10;
|
|
29
|
+
const textWidths = [0];
|
|
30
|
+
const svgElement = select(ref.current);
|
|
31
|
+
svgElement.selectAll('*').remove();
|
|
32
|
+
const legendItemTemplate = svgElement
|
|
33
|
+
.selectAll('legend-history')
|
|
34
|
+
.data(legendItems)
|
|
35
|
+
.enter()
|
|
36
|
+
.append('g')
|
|
37
|
+
.attr('class', b('item'))
|
|
38
|
+
.on('click', function (e, d) {
|
|
39
|
+
onItemClick({ name: d.name, metaKey: e.metaKey });
|
|
40
|
+
});
|
|
41
|
+
svgElement
|
|
42
|
+
.selectAll('*')
|
|
43
|
+
.data(legendItems)
|
|
44
|
+
.append('text')
|
|
45
|
+
.text(function (d) {
|
|
46
|
+
return d.name;
|
|
47
|
+
})
|
|
48
|
+
.each(function () {
|
|
49
|
+
textWidths.push(this.getComputedTextLength());
|
|
50
|
+
})
|
|
51
|
+
.remove();
|
|
52
|
+
legendItemTemplate
|
|
53
|
+
.append('rect')
|
|
54
|
+
.attr('x', function (_d, i) {
|
|
55
|
+
return (offsetWidth +
|
|
56
|
+
i * size +
|
|
57
|
+
textWidths.slice(0, i + 1).reduce((acc, tw) => acc + tw, 0));
|
|
58
|
+
})
|
|
59
|
+
.attr('y', offsetHeight - size / 2)
|
|
60
|
+
.attr('width', size)
|
|
61
|
+
.attr('height', size)
|
|
62
|
+
.attr('class', b('item-shape'))
|
|
63
|
+
.style('fill', function (d) {
|
|
64
|
+
return d.color;
|
|
65
|
+
});
|
|
66
|
+
legendItemTemplate
|
|
67
|
+
.append('text')
|
|
68
|
+
.attr('x', function (_d, i) {
|
|
69
|
+
return (offsetWidth +
|
|
70
|
+
i * size +
|
|
71
|
+
size +
|
|
72
|
+
textWidths.slice(0, i + 1).reduce((acc, tw) => acc + tw, 0));
|
|
73
|
+
})
|
|
74
|
+
.attr('y', offsetHeight)
|
|
75
|
+
.attr('class', function (d) {
|
|
76
|
+
const mods = { selected: d.visible, unselected: !d.visible };
|
|
77
|
+
return b('item-text', mods);
|
|
78
|
+
})
|
|
79
|
+
.text(function (d) {
|
|
80
|
+
return ('name' in d && d.name);
|
|
81
|
+
})
|
|
82
|
+
.style('alignment-baseline', 'middle');
|
|
83
|
+
}, [width, offsetWidth, height, offsetHeight, chartSeries, onItemClick]);
|
|
84
|
+
return React.createElement("g", { ref: ref, width: width, height: height });
|
|
66
85
|
};
|
|
@@ -14,6 +14,18 @@ export const DefaultContent = ({ hovered, xAxis, yAxis }) => {
|
|
|
14
14
|
React.createElement("span", null, "Y:\u00A0"),
|
|
15
15
|
React.createElement("b", null, yRow))));
|
|
16
16
|
}
|
|
17
|
+
case 'bar-x': {
|
|
18
|
+
const barXData = data;
|
|
19
|
+
const xRow = xAxis.type === 'category' ? barXData.category : barXData.x;
|
|
20
|
+
const yRow = yAxis.type === 'category' ? barXData.category : barXData.y;
|
|
21
|
+
return (React.createElement("div", null,
|
|
22
|
+
React.createElement("div", null, xRow),
|
|
23
|
+
React.createElement("div", null,
|
|
24
|
+
React.createElement("span", null,
|
|
25
|
+
React.createElement("b", null, series.name),
|
|
26
|
+
": ",
|
|
27
|
+
yRow))));
|
|
28
|
+
}
|
|
17
29
|
default: {
|
|
18
30
|
return null;
|
|
19
31
|
}
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
user-select: none;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
.chartkit-d3-legend__item-shape {
|
|
23
|
+
fill: var(--g-color-base-misc-medium);
|
|
24
|
+
}
|
|
25
|
+
|
|
22
26
|
.chartkit-d3-legend__item-text {
|
|
23
27
|
fill: var(--g-color-text-secondary);
|
|
24
28
|
}
|
|
@@ -31,19 +35,6 @@
|
|
|
31
35
|
fill: var(--g-color-text-complementary);
|
|
32
36
|
}
|
|
33
37
|
|
|
34
|
-
.chartkit-d3-scatter__point {
|
|
35
|
-
stroke-width: 1px;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
.chartkit-d3_hovered .chartkit-d3-scatter__point {
|
|
39
|
-
opacity: 0.5;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
.chartkit-d3-scatter__point:hover {
|
|
43
|
-
stroke: #fff;
|
|
44
|
-
opacity: 1;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
38
|
.chartkit-d3-title {
|
|
48
39
|
font-size: var(--g-text-subheader-2-font-size);
|
|
49
40
|
font-weight: var(--g-text-subheader-font-weight);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { select, max } from 'd3';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
|
-
import { formatAxisTickLabel, getDomainDataYBySeries } from '../../utils';
|
|
3
|
+
import { formatAxisTickLabel, getDomainDataYBySeries, isAxisRelatedSeries } from '../../utils';
|
|
4
4
|
import { getHorisontalSvgTextDimensions } from './utils';
|
|
5
5
|
const AXIS_WIDTH = 1;
|
|
6
6
|
const getAxisLabelMaxWidth = (args) => {
|
|
@@ -45,17 +45,24 @@ const getAxisLabelMaxWidth = (args) => {
|
|
|
45
45
|
};
|
|
46
46
|
export const getPreparedChart = (args) => {
|
|
47
47
|
const { chart, series, preparedXAxis, preparedY1Axis } = args;
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
48
|
+
const hasAxisRelatedSeries = series.data.some(isAxisRelatedSeries);
|
|
49
|
+
let marginBottom = get(chart, 'margin.bottom', 0);
|
|
50
|
+
let marginLeft = get(chart, 'margin.left', 0);
|
|
51
|
+
let marginTop = get(chart, 'margin.top', 0);
|
|
52
|
+
let marginRight = get(chart, 'margin.right', 0);
|
|
53
|
+
if (hasAxisRelatedSeries) {
|
|
54
|
+
marginBottom +=
|
|
55
|
+
preparedXAxis.labels.padding +
|
|
56
|
+
getHorisontalSvgTextDimensions({ text: 'Tmp', style: preparedXAxis.labels.style });
|
|
57
|
+
marginLeft +=
|
|
58
|
+
AXIS_WIDTH +
|
|
59
|
+
preparedY1Axis.labels.padding +
|
|
60
|
+
getAxisLabelMaxWidth({ axis: preparedY1Axis, series: series.data }) +
|
|
61
|
+
(preparedY1Axis.title.height || 0);
|
|
62
|
+
marginTop +=
|
|
63
|
+
getHorisontalSvgTextDimensions({ text: 'Tmp', style: preparedY1Axis.labels.style }) / 2;
|
|
64
|
+
marginRight += getAxisLabelMaxWidth({ axis: preparedXAxis, series: series.data }) / 2;
|
|
65
|
+
}
|
|
59
66
|
return {
|
|
60
67
|
margin: {
|
|
61
68
|
top: marginTop,
|
|
@@ -1,3 +1,5 @@
|
|
|
1
1
|
import type { ChartKitWidgetData } from '../../../../../types/widget-data';
|
|
2
2
|
import type { ChartOptions } from './types';
|
|
3
|
-
|
|
3
|
+
type Args = ChartKitWidgetData;
|
|
4
|
+
export declare const useChartOptions: (args: Args) => ChartOptions;
|
|
5
|
+
export {};
|
|
@@ -1,8 +1,39 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import get from 'lodash/get';
|
|
3
|
+
import { isAxisRelatedSeries } from '../../utils';
|
|
4
|
+
const getActiveLegendItems = (series) => {
|
|
5
|
+
return series.reduce((acc, s) => {
|
|
6
|
+
const isAxisRelated = isAxisRelatedSeries(s);
|
|
7
|
+
const isLegendEnabled = get(s, 'legend.enabled', true);
|
|
8
|
+
const isSeriesVisible = get(s, 'visible', true);
|
|
9
|
+
if (isLegendEnabled && isAxisRelated && isSeriesVisible && 'name' in s) {
|
|
10
|
+
acc.push(s.name);
|
|
11
|
+
}
|
|
12
|
+
else if (isLegendEnabled && !isAxisRelated) {
|
|
13
|
+
s.data.forEach((d) => {
|
|
14
|
+
const isDataVisible = get(d, 'visible', true);
|
|
15
|
+
if (isDataVisible && 'name' in d) {
|
|
16
|
+
acc.push(d.name);
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
return acc;
|
|
21
|
+
}, []);
|
|
22
|
+
};
|
|
23
|
+
const getAllLegendItems = (series) => {
|
|
24
|
+
return series.reduce((acc, s) => {
|
|
25
|
+
if (isAxisRelatedSeries(s) && 'name' in s) {
|
|
26
|
+
acc.push(s.name);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
acc.push(...s.data.map((d) => ('name' in d && d.name) || ''));
|
|
30
|
+
}
|
|
31
|
+
return acc;
|
|
32
|
+
}, []);
|
|
33
|
+
};
|
|
3
34
|
export const useLegend = (args) => {
|
|
4
35
|
const { series } = args;
|
|
5
|
-
const [activeLegendItems, setActiveLegendItems] = React.useState(
|
|
36
|
+
const [activeLegendItems, setActiveLegendItems] = React.useState(getActiveLegendItems(series));
|
|
6
37
|
const handleLegendItemClick = React.useCallback(({ name, metaKey }) => {
|
|
7
38
|
const onlyItemSelected = activeLegendItems.length === 1 && activeLegendItems.includes(name);
|
|
8
39
|
let nextActiveLegendItems;
|
|
@@ -13,7 +44,7 @@ export const useLegend = (args) => {
|
|
|
13
44
|
nextActiveLegendItems = activeLegendItems.concat(name);
|
|
14
45
|
}
|
|
15
46
|
else if (onlyItemSelected) {
|
|
16
|
-
nextActiveLegendItems =
|
|
47
|
+
nextActiveLegendItems = getAllLegendItems(series);
|
|
17
48
|
}
|
|
18
49
|
else {
|
|
19
50
|
nextActiveLegendItems = [name];
|
|
@@ -22,7 +53,7 @@ export const useLegend = (args) => {
|
|
|
22
53
|
}, [series, activeLegendItems]);
|
|
23
54
|
// FIXME: remove effect. It initiates extra rerender
|
|
24
55
|
React.useEffect(() => {
|
|
25
|
-
setActiveLegendItems(
|
|
56
|
+
setActiveLegendItems(getActiveLegendItems(series));
|
|
26
57
|
}, [series]);
|
|
27
58
|
return { activeLegendItems, handleLegendItemClick };
|
|
28
59
|
};
|
|
@@ -1,22 +1,60 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import
|
|
2
|
+
import cloneDeep from 'lodash/cloneDeep';
|
|
3
|
+
import get from 'lodash/get';
|
|
3
4
|
import { scaleOrdinal } from 'd3';
|
|
4
5
|
import { DEFAULT_PALETTE } from '../../constants';
|
|
5
|
-
import { getSeriesNames } from '../../utils';
|
|
6
|
+
import { getSeriesNames, isAxisRelatedSeries } from '../../utils';
|
|
7
|
+
const prepareAxisRelatedSeries = (args) => {
|
|
8
|
+
const { activeLegendItems, colorScale, series } = args;
|
|
9
|
+
const preparedSeries = cloneDeep(series);
|
|
10
|
+
const legendEnabled = get(preparedSeries, 'legend.enabled', true);
|
|
11
|
+
const defaultVisible = get(preparedSeries, 'visible', true);
|
|
12
|
+
const name = 'name' in series && series.name ? series.name : '';
|
|
13
|
+
const color = 'color' in series && series.color ? series.color : colorScale(name);
|
|
14
|
+
preparedSeries.color = color;
|
|
15
|
+
preparedSeries.name = name;
|
|
16
|
+
preparedSeries.visible = legendEnabled ? activeLegendItems.includes(name) : defaultVisible;
|
|
17
|
+
return preparedSeries;
|
|
18
|
+
};
|
|
19
|
+
const preparePieSeries = (args) => {
|
|
20
|
+
const { activeLegendItems, series } = args;
|
|
21
|
+
const preparedSeries = cloneDeep(series);
|
|
22
|
+
const legendEnabled = get(preparedSeries, 'legend.enabled', true);
|
|
23
|
+
const dataNames = series.data.map((d) => d.name);
|
|
24
|
+
const colorScale = scaleOrdinal(dataNames, DEFAULT_PALETTE);
|
|
25
|
+
preparedSeries.data = preparedSeries.data.map((d) => {
|
|
26
|
+
const defaultVisible = get(d, 'visible', true);
|
|
27
|
+
d.color = d.color || colorScale(d.name);
|
|
28
|
+
d.visible = legendEnabled ? activeLegendItems.includes(d.name) : defaultVisible;
|
|
29
|
+
return d;
|
|
30
|
+
});
|
|
31
|
+
// Not axis related series manages their own data visibility inside their data
|
|
32
|
+
preparedSeries.visible = true;
|
|
33
|
+
return preparedSeries;
|
|
34
|
+
};
|
|
35
|
+
const prepareNotAxisRelatedSeries = (args) => {
|
|
36
|
+
const { activeLegendItems, series } = args;
|
|
37
|
+
switch (series.type) {
|
|
38
|
+
case 'pie': {
|
|
39
|
+
return preparePieSeries({ activeLegendItems, series });
|
|
40
|
+
}
|
|
41
|
+
default: {
|
|
42
|
+
throw new Error(`Series type ${series.type} does not support data preparation for series that do not support the presence of axes`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
6
46
|
export const useSeries = (args) => {
|
|
7
47
|
const { activeLegendItems, series } = args;
|
|
8
|
-
// FIXME: handle case with one pie chart series
|
|
9
48
|
const chartSeries = React.useMemo(() => {
|
|
10
49
|
const seriesNames = getSeriesNames(series);
|
|
11
50
|
const colorScale = scaleOrdinal(seriesNames, DEFAULT_PALETTE);
|
|
12
51
|
return series.map((s) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
return preparedSeries;
|
|
52
|
+
return isAxisRelatedSeries(s)
|
|
53
|
+
? prepareAxisRelatedSeries({ activeLegendItems, colorScale, series: s })
|
|
54
|
+
: prepareNotAxisRelatedSeries({
|
|
55
|
+
activeLegendItems,
|
|
56
|
+
series: s,
|
|
57
|
+
});
|
|
20
58
|
});
|
|
21
59
|
}, [activeLegendItems, series]);
|
|
22
60
|
return { chartSeries };
|
|
@@ -4,6 +4,8 @@ import { ChartScale } from '../useAxisScales';
|
|
|
4
4
|
import { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
|
|
5
5
|
import { BarXSeries } from '../../../../../types/widget-data';
|
|
6
6
|
type Args = {
|
|
7
|
+
top: number;
|
|
8
|
+
left: number;
|
|
7
9
|
series: BarXSeries[];
|
|
8
10
|
xAxis: ChartOptions['xAxis'];
|
|
9
11
|
xScale: ChartScale;
|
|
@@ -11,6 +13,7 @@ type Args = {
|
|
|
11
13
|
yScale: ChartScale;
|
|
12
14
|
onSeriesMouseMove?: OnSeriesMouseMove;
|
|
13
15
|
onSeriesMouseLeave?: OnSeriesMouseLeave;
|
|
16
|
+
svgContainer: SVGSVGElement | null;
|
|
14
17
|
};
|
|
15
18
|
export declare function prepareBarXSeries(args: Args): React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
|
|
16
19
|
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { block } from '../../../../../utils/cn';
|
|
3
|
+
import { pointer } from 'd3';
|
|
3
4
|
const DEFAULT_BAR_RECT_WIDTH = 50;
|
|
4
5
|
const DEFAULT_LINEAR_BAR_RECT_WIDTH = 20;
|
|
5
6
|
const MIN_RECT_GAP = 1;
|
|
@@ -47,7 +48,7 @@ function minDiff(arr) {
|
|
|
47
48
|
return result;
|
|
48
49
|
}
|
|
49
50
|
export function prepareBarXSeries(args) {
|
|
50
|
-
const { series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave } = args;
|
|
51
|
+
const { top, left, series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave, svgContainer, } = args;
|
|
51
52
|
const seriesData = series.map(({ data }) => data).flat(2);
|
|
52
53
|
const minPointDistance = minDiff(seriesData.map((item) => Number(item.x)));
|
|
53
54
|
return series.reduce((result, item) => {
|
|
@@ -61,12 +62,14 @@ export function prepareBarXSeries(args) {
|
|
|
61
62
|
yScale,
|
|
62
63
|
minPointDistance,
|
|
63
64
|
});
|
|
64
|
-
result.push(React.createElement("rect", Object.assign({ key: `${i}-${randomKey}`, className: b('rect'), fill: item.color }, rectProps, { onMouseMove: function () {
|
|
65
|
+
result.push(React.createElement("rect", Object.assign({ key: `${i}-${randomKey}`, className: b('rect'), fill: item.color }, rectProps, { onMouseMove: function (e) {
|
|
66
|
+
const [x, y] = pointer(e, svgContainer);
|
|
65
67
|
onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
|
|
66
68
|
hovered: {
|
|
67
69
|
data: point,
|
|
68
70
|
series: item,
|
|
69
71
|
},
|
|
72
|
+
pointerPosition: [x - left, y - top],
|
|
70
73
|
});
|
|
71
74
|
}, onMouseLeave: onSeriesMouseLeave })));
|
|
72
75
|
});
|
|
@@ -3,7 +3,12 @@ import type { ChartOptions } from '../useChartOptions/types';
|
|
|
3
3
|
import type { ChartScale } from '../useAxisScales';
|
|
4
4
|
import type { ChartSeries } from '../useSeries';
|
|
5
5
|
import type { OnSeriesMouseMove, OnSeriesMouseLeave } from '../useTooltip/types';
|
|
6
|
+
import './styles.css';
|
|
6
7
|
type Args = {
|
|
8
|
+
top: number;
|
|
9
|
+
left: number;
|
|
10
|
+
boundsWidth: number;
|
|
11
|
+
boundsHeight: number;
|
|
7
12
|
series: ChartSeries[];
|
|
8
13
|
xAxis: ChartOptions['xAxis'];
|
|
9
14
|
yAxis: ChartOptions['yAxis'];
|
|
@@ -3,8 +3,10 @@ import { group } from 'd3';
|
|
|
3
3
|
import { getOnlyVisibleSeries } from '../../utils';
|
|
4
4
|
import { prepareBarXSeries } from './bar-x';
|
|
5
5
|
import { prepareScatterSeries } from './scatter';
|
|
6
|
+
import { PieSeriesComponent } from './pie';
|
|
7
|
+
import './styles.css';
|
|
6
8
|
export const useShapes = (args) => {
|
|
7
|
-
const { series, xAxis, xScale, yAxis, yScale, svgContainer, onSeriesMouseMove, onSeriesMouseLeave, } = args;
|
|
9
|
+
const { top, left, boundsWidth, boundsHeight, series, xAxis, xScale, yAxis, yScale, svgContainer, onSeriesMouseMove, onSeriesMouseLeave, } = args;
|
|
8
10
|
const shapes = React.useMemo(() => {
|
|
9
11
|
const visibleSeries = getOnlyVisibleSeries(series);
|
|
10
12
|
const groupedSeries = group(visibleSeries, (item) => item.type);
|
|
@@ -14,6 +16,8 @@ export const useShapes = (args) => {
|
|
|
14
16
|
case 'bar-x': {
|
|
15
17
|
if (xScale && yScale) {
|
|
16
18
|
acc.push(...prepareBarXSeries({
|
|
19
|
+
top,
|
|
20
|
+
left,
|
|
17
21
|
series: chartSeries,
|
|
18
22
|
xAxis,
|
|
19
23
|
xScale,
|
|
@@ -21,6 +25,7 @@ export const useShapes = (args) => {
|
|
|
21
25
|
yScale,
|
|
22
26
|
onSeriesMouseMove,
|
|
23
27
|
onSeriesMouseLeave,
|
|
28
|
+
svgContainer,
|
|
24
29
|
}));
|
|
25
30
|
}
|
|
26
31
|
break;
|
|
@@ -28,6 +33,8 @@ export const useShapes = (args) => {
|
|
|
28
33
|
case 'scatter': {
|
|
29
34
|
if (xScale && yScale) {
|
|
30
35
|
acc.push(...prepareScatterSeries({
|
|
36
|
+
top,
|
|
37
|
+
left,
|
|
31
38
|
series: chartSeries,
|
|
32
39
|
xAxis,
|
|
33
40
|
xScale,
|
|
@@ -40,9 +47,23 @@ export const useShapes = (args) => {
|
|
|
40
47
|
}
|
|
41
48
|
break;
|
|
42
49
|
}
|
|
50
|
+
case 'pie': {
|
|
51
|
+
acc.push(...chartSeries.map((cs, i) => (React.createElement(PieSeriesComponent, { key: `pie-${i}`, boundsWidth: boundsWidth, boundsHeight: boundsHeight, series: cs, onSeriesMouseMove: onSeriesMouseMove, onSeriesMouseLeave: onSeriesMouseLeave, svgContainer: svgContainer }))));
|
|
52
|
+
}
|
|
43
53
|
}
|
|
44
54
|
return acc;
|
|
45
55
|
}, []);
|
|
46
|
-
}, [
|
|
56
|
+
}, [
|
|
57
|
+
boundsWidth,
|
|
58
|
+
boundsHeight,
|
|
59
|
+
series,
|
|
60
|
+
xAxis,
|
|
61
|
+
xScale,
|
|
62
|
+
yAxis,
|
|
63
|
+
yScale,
|
|
64
|
+
svgContainer,
|
|
65
|
+
onSeriesMouseMove,
|
|
66
|
+
onSeriesMouseLeave,
|
|
67
|
+
]);
|
|
47
68
|
return { shapes };
|
|
48
69
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { PieSeries } from '../../../../../types/widget-data';
|
|
3
|
+
import type { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
|
|
4
|
+
type PreparePieSeriesArgs = {
|
|
5
|
+
boundsWidth: number;
|
|
6
|
+
boundsHeight: number;
|
|
7
|
+
series: PieSeries;
|
|
8
|
+
svgContainer: SVGSVGElement | null;
|
|
9
|
+
onSeriesMouseMove?: OnSeriesMouseMove;
|
|
10
|
+
onSeriesMouseLeave?: OnSeriesMouseLeave;
|
|
11
|
+
};
|
|
12
|
+
export declare function PieSeriesComponent(args: PreparePieSeriesArgs): React.JSX.Element;
|
|
13
|
+
export {};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { arc, pie, select } from 'd3';
|
|
3
|
+
import { block } from '../../../../../utils/cn';
|
|
4
|
+
import { calculateNumericProperty } from '../../utils';
|
|
5
|
+
const b = block('d3-pie');
|
|
6
|
+
const getCenter = (boundsWidth, boundsHeight, center) => {
|
|
7
|
+
var _a, _b;
|
|
8
|
+
const defaultX = boundsWidth * 0.5;
|
|
9
|
+
const defaultY = boundsHeight * 0.5;
|
|
10
|
+
if (!center) {
|
|
11
|
+
return [defaultX, defaultY];
|
|
12
|
+
}
|
|
13
|
+
const [x, y] = center;
|
|
14
|
+
const resultX = (_a = calculateNumericProperty({ value: x, base: boundsWidth })) !== null && _a !== void 0 ? _a : defaultX;
|
|
15
|
+
const resultY = (_b = calculateNumericProperty({ value: y, base: boundsHeight })) !== null && _b !== void 0 ? _b : defaultY;
|
|
16
|
+
return [resultX, resultY];
|
|
17
|
+
};
|
|
18
|
+
export function PieSeriesComponent(args) {
|
|
19
|
+
const { boundsWidth, boundsHeight, series, onSeriesMouseMove, onSeriesMouseLeave, svgContainer } = args;
|
|
20
|
+
const ref = React.useRef(null);
|
|
21
|
+
const [x, y] = getCenter(boundsWidth, boundsHeight, series.center);
|
|
22
|
+
React.useEffect(() => {
|
|
23
|
+
var _a, _b, _c, _d;
|
|
24
|
+
if (!ref.current) {
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const svgElement = select(ref.current);
|
|
28
|
+
const radiusRelatedToChart = Math.min(boundsWidth, boundsHeight) / 2;
|
|
29
|
+
const radius = (_a = calculateNumericProperty({ value: series.radius, base: radiusRelatedToChart })) !== null && _a !== void 0 ? _a : radiusRelatedToChart;
|
|
30
|
+
const innerRadius = (_b = calculateNumericProperty({ value: series.innerRadius, base: radius })) !== null && _b !== void 0 ? _b : 0;
|
|
31
|
+
const pieGenerator = pie().value((d) => d.value);
|
|
32
|
+
const visibleData = series.data.filter((d) => d.visible);
|
|
33
|
+
const dataReady = pieGenerator(visibleData);
|
|
34
|
+
const arcGenerator = arc()
|
|
35
|
+
.innerRadius(innerRadius)
|
|
36
|
+
.outerRadius(radius)
|
|
37
|
+
.cornerRadius((_c = series.borderRadius) !== null && _c !== void 0 ? _c : 0);
|
|
38
|
+
svgElement.selectAll('*').remove();
|
|
39
|
+
svgElement
|
|
40
|
+
.selectAll('*')
|
|
41
|
+
.data(dataReady)
|
|
42
|
+
.enter()
|
|
43
|
+
.append('path')
|
|
44
|
+
.attr('d', arcGenerator)
|
|
45
|
+
.attr('class', b('segment'))
|
|
46
|
+
.attr('fill', (d) => d.data.color || '')
|
|
47
|
+
.style('stroke', series.borderColor || '')
|
|
48
|
+
.style('stroke-width', (_d = series.borderWidth) !== null && _d !== void 0 ? _d : 1);
|
|
49
|
+
}, [boundsWidth, boundsHeight, series, onSeriesMouseMove, onSeriesMouseLeave, svgContainer]);
|
|
50
|
+
return React.createElement("g", { ref: ref, className: b(), transform: `translate(${x}, ${y})` });
|
|
51
|
+
}
|
|
@@ -4,6 +4,8 @@ import { ChartScale } from '../useAxisScales';
|
|
|
4
4
|
import { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
|
|
5
5
|
import { ScatterSeries } from '../../../../../types/widget-data';
|
|
6
6
|
type PrepareScatterSeriesArgs = {
|
|
7
|
+
top: number;
|
|
8
|
+
left: number;
|
|
7
9
|
series: ScatterSeries[];
|
|
8
10
|
xAxis: ChartOptions['xAxis'];
|
|
9
11
|
xScale: ChartScale;
|
|
@@ -33,7 +33,7 @@ const getPointProperties = (args) => {
|
|
|
33
33
|
return { r, cx, cy };
|
|
34
34
|
};
|
|
35
35
|
export function prepareScatterSeries(args) {
|
|
36
|
-
const { series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave, key, svgContainer, } = args;
|
|
36
|
+
const { top, left, series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave, key, svgContainer, } = args;
|
|
37
37
|
return series.reduce((result, s) => {
|
|
38
38
|
var _a;
|
|
39
39
|
const preparedData = xAxis.type === 'category' || ((_a = yAxis[0]) === null || _a === void 0 ? void 0 : _a.type) === 'category'
|
|
@@ -48,12 +48,13 @@ export function prepareScatterSeries(args) {
|
|
|
48
48
|
yScale,
|
|
49
49
|
});
|
|
50
50
|
return (React.createElement("circle", Object.assign({ key: `${i}-${key}`, className: b('point'), fill: s.color }, pointProps, { onMouseMove: function (e) {
|
|
51
|
+
const [x, y] = pointer(e, svgContainer);
|
|
51
52
|
onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
|
|
52
53
|
hovered: {
|
|
53
54
|
data: point,
|
|
54
55
|
series: s,
|
|
55
56
|
},
|
|
56
|
-
pointerPosition:
|
|
57
|
+
pointerPosition: [x - left, y - top],
|
|
57
58
|
});
|
|
58
59
|
}, onMouseLeave: onSeriesMouseLeave })));
|
|
59
60
|
}));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
.chartkit-d3-scatter__point {
|
|
2
|
+
stroke-width: 1px;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
.chartkit-d3_hovered .chartkit-d3-scatter__point {
|
|
6
|
+
opacity: 0.5;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
.chartkit-d3-scatter__point:hover {
|
|
10
|
+
stroke: #fff;
|
|
11
|
+
opacity: 1;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
.chartkit-d3-pie__segment {
|
|
15
|
+
stroke: var(--g-color-base-background);
|
|
16
|
+
}
|
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
import type { AxisDomain } from 'd3';
|
|
2
2
|
import type { ChartKitWidgetSeries, ChartKitWidgetAxisType, ChartKitWidgetAxisLabels } from '../../../../types/widget-data';
|
|
3
|
+
export * from './math';
|
|
4
|
+
/**
|
|
5
|
+
* Checks whether the series should be drawn with axes.
|
|
6
|
+
*
|
|
7
|
+
* @param series - The series object to check.
|
|
8
|
+
* @returns `true` if the series should be drawn with axes, `false` otherwise.
|
|
9
|
+
*/
|
|
3
10
|
export declare const isAxisRelatedSeries: (series: ChartKitWidgetSeries) => boolean;
|
|
4
11
|
export declare const getDomainDataXBySeries: (series: ChartKitWidgetSeries[]) => unknown[];
|
|
5
12
|
export declare const getDomainDataYBySeries: (series: ChartKitWidgetSeries[]) => unknown[];
|
|
6
13
|
export declare const getSeriesNames: (series: ChartKitWidgetSeries[]) => string[];
|
|
7
|
-
export declare const getVisibleSeriesNames: (series: ChartKitWidgetSeries[]) => string[];
|
|
8
14
|
export declare const getOnlyVisibleSeries: <T extends ChartKitWidgetSeries<any>>(series: T[]) => T[];
|
|
9
15
|
export declare const parseTransformStyle: (style: string | null) => {
|
|
10
16
|
x?: number | undefined;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { dateTime } from '@gravity-ui/date-utils';
|
|
2
2
|
import { formatNumber } from '../../../shared';
|
|
3
|
+
export * from './math';
|
|
3
4
|
const CHARTS_WITHOUT_AXIS = ['pie'];
|
|
4
|
-
|
|
5
|
+
/**
|
|
6
|
+
* Checks whether the series should be drawn with axes.
|
|
7
|
+
*
|
|
8
|
+
* @param series - The series object to check.
|
|
9
|
+
* @returns `true` if the series should be drawn with axes, `false` otherwise.
|
|
10
|
+
*/
|
|
5
11
|
export const isAxisRelatedSeries = (series) => {
|
|
6
12
|
return !CHARTS_WITHOUT_AXIS.includes(series.type);
|
|
7
13
|
};
|
|
@@ -26,17 +32,6 @@ export const getSeriesNames = (series) => {
|
|
|
26
32
|
return acc;
|
|
27
33
|
}, []);
|
|
28
34
|
};
|
|
29
|
-
// Uses to get all visible series names array (except `pie` charts)
|
|
30
|
-
export const getVisibleSeriesNames = (series) => {
|
|
31
|
-
return series.reduce((acc, s) => {
|
|
32
|
-
var _a;
|
|
33
|
-
const visible = (_a = s.visible) !== null && _a !== void 0 ? _a : true;
|
|
34
|
-
if ('name' in s && typeof s.name === 'string' && visible) {
|
|
35
|
-
acc.push(s.name);
|
|
36
|
-
}
|
|
37
|
-
return acc;
|
|
38
|
-
}, []);
|
|
39
|
-
};
|
|
40
35
|
export const getOnlyVisibleSeries = (series) => {
|
|
41
36
|
return series.filter((s) => s.visible);
|
|
42
37
|
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Calculates a numeric property based on the given arguments.
|
|
3
|
+
*
|
|
4
|
+
* @param {Object} args - The arguments for the calculation.
|
|
5
|
+
* @param {string | number | null} args.value - The value to calculate the property for.
|
|
6
|
+
* @param {number} args.base - The base value to use in the calculation.
|
|
7
|
+
* @return {number | undefined} The calculated numeric property, or undefined if the value is invalid.
|
|
8
|
+
* @example
|
|
9
|
+
* const result1 = calculateNumericProperty({value: 1});
|
|
10
|
+
* console.log(result1); // Output: 1
|
|
11
|
+
* const result2 = calculateNumericProperty({value: '10px'});
|
|
12
|
+
* console.log(result2); // Output: 10
|
|
13
|
+
* const result3 = calculateNumericProperty({value: '50%', base: 200});
|
|
14
|
+
* console.log(result3); // Output: 100
|
|
15
|
+
* const result4 = calculateNumericProperty({value: '50%'});
|
|
16
|
+
* console.log(result4); // Output: undefined
|
|
17
|
+
* const result5 = calculateNumericProperty({value: 'invalid_value'});
|
|
18
|
+
* console.log(result5); // Output: undefined
|
|
19
|
+
*/
|
|
20
|
+
export declare const calculateNumericProperty: (args: {
|
|
21
|
+
value?: string | number | null;
|
|
22
|
+
base?: number;
|
|
23
|
+
}) => number | undefined;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import isNil from 'lodash/isNil';
|
|
2
|
+
const isStringValueInPercent = (value = '') => {
|
|
3
|
+
return value.endsWith('%') && !Number.isNaN(Number.parseFloat(value));
|
|
4
|
+
};
|
|
5
|
+
const isStringValueInPixel = (value = '') => {
|
|
6
|
+
return value.endsWith('px') && !Number.isNaN(Number.parseFloat(value));
|
|
7
|
+
};
|
|
8
|
+
/**
|
|
9
|
+
* Calculates a numeric property based on the given arguments.
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} args - The arguments for the calculation.
|
|
12
|
+
* @param {string | number | null} args.value - The value to calculate the property for.
|
|
13
|
+
* @param {number} args.base - The base value to use in the calculation.
|
|
14
|
+
* @return {number | undefined} The calculated numeric property, or undefined if the value is invalid.
|
|
15
|
+
* @example
|
|
16
|
+
* const result1 = calculateNumericProperty({value: 1});
|
|
17
|
+
* console.log(result1); // Output: 1
|
|
18
|
+
* const result2 = calculateNumericProperty({value: '10px'});
|
|
19
|
+
* console.log(result2); // Output: 10
|
|
20
|
+
* const result3 = calculateNumericProperty({value: '50%', base: 200});
|
|
21
|
+
* console.log(result3); // Output: 100
|
|
22
|
+
* const result4 = calculateNumericProperty({value: '50%'});
|
|
23
|
+
* console.log(result4); // Output: undefined
|
|
24
|
+
* const result5 = calculateNumericProperty({value: 'invalid_value'});
|
|
25
|
+
* console.log(result5); // Output: undefined
|
|
26
|
+
*/
|
|
27
|
+
export const calculateNumericProperty = (args) => {
|
|
28
|
+
const { value = '', base } = args;
|
|
29
|
+
if (isNil(value)) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
if (typeof value === 'string') {
|
|
33
|
+
if (isStringValueInPercent(value) && typeof base === 'number') {
|
|
34
|
+
const fraction = Number.parseFloat(value) / 100;
|
|
35
|
+
return base * fraction;
|
|
36
|
+
}
|
|
37
|
+
if (isStringValueInPixel(value)) {
|
|
38
|
+
return Number.parseFloat(value);
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
return value;
|
|
43
|
+
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import Highcharts from 'highcharts';
|
|
3
|
-
import HighchartsReact from '
|
|
3
|
+
import HighchartsReact from './HighchartsReact';
|
|
4
4
|
import get from 'lodash/get';
|
|
5
5
|
import { settings } from '../../../../libs';
|
|
6
6
|
import { settingsEventEmitter } from '../../../../libs/settings/settings';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import Highcharts from 'highcharts';
|
|
3
|
+
interface HighchartsReactRefObject {
|
|
4
|
+
chart: Highcharts.Chart | null | undefined;
|
|
5
|
+
container: React.RefObject<HTMLDivElement | undefined>;
|
|
6
|
+
}
|
|
7
|
+
interface HighchartsReactProps {
|
|
8
|
+
[key: string]: any;
|
|
9
|
+
constructorType?: keyof typeof Highcharts;
|
|
10
|
+
containerProps?: {
|
|
11
|
+
[key: string]: any;
|
|
12
|
+
};
|
|
13
|
+
highcharts?: typeof Highcharts;
|
|
14
|
+
options: Highcharts.Options;
|
|
15
|
+
callback?: Highcharts.ChartCallbackFunction;
|
|
16
|
+
}
|
|
17
|
+
export declare const HighchartsReact: React.ForwardRefExoticComponent<React.PropsWithoutRef<HighchartsReactProps> & React.RefAttributes<HighchartsReactRefObject>>;
|
|
18
|
+
export default HighchartsReact;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/* eslint no-console: ["error", { allow: ["warn", "error"]}] */
|
|
2
|
+
import React from 'react';
|
|
3
|
+
const useIsomorphicLayoutEffect = typeof window === 'undefined' ? React.useEffect : React.useLayoutEffect;
|
|
4
|
+
export const HighchartsReact = React.memo(React.forwardRef(function HighchartsReact(props, ref) {
|
|
5
|
+
const containerRef = React.useRef(null);
|
|
6
|
+
const chartRef = React.useRef();
|
|
7
|
+
useIsomorphicLayoutEffect(() => {
|
|
8
|
+
function createChart() {
|
|
9
|
+
const { highcharts: HighchartsComponent } = props;
|
|
10
|
+
const constructorType = props.constructorType || 'chart';
|
|
11
|
+
if (!HighchartsComponent) {
|
|
12
|
+
console.warn('The "highcharts" property was not passed.');
|
|
13
|
+
}
|
|
14
|
+
else if (!HighchartsComponent[constructorType]) {
|
|
15
|
+
console.warn('The "constructorType" property is incorrect or some ' +
|
|
16
|
+
'required module is not imported.');
|
|
17
|
+
}
|
|
18
|
+
else if (props.options) {
|
|
19
|
+
chartRef.current = HighchartsComponent[constructorType](containerRef.current, props.options, props.callback);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
console.warn('The "options" property was not passed.');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
if (!chartRef.current) {
|
|
26
|
+
createChart();
|
|
27
|
+
}
|
|
28
|
+
}, [props.options, props.allowChartUpdate, props.containerProps, props.highcharts, props.constructorType]);
|
|
29
|
+
useIsomorphicLayoutEffect(() => {
|
|
30
|
+
return () => {
|
|
31
|
+
if (chartRef.current) {
|
|
32
|
+
chartRef.current.destroy();
|
|
33
|
+
chartRef.current = null;
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
}, []);
|
|
37
|
+
React.useImperativeHandle(ref, () => ({
|
|
38
|
+
get chart() {
|
|
39
|
+
return chartRef.current;
|
|
40
|
+
},
|
|
41
|
+
container: containerRef,
|
|
42
|
+
}), []);
|
|
43
|
+
return React.createElement("div", Object.assign({}, props.containerProps, { ref: containerRef }));
|
|
44
|
+
}));
|
|
45
|
+
HighchartsReact.displayName = 'HighchartsReact';
|
|
46
|
+
export default HighchartsReact;
|
package/build/plugins/index.d.ts
CHANGED
|
@@ -4,3 +4,4 @@ export { IndicatorPlugin } from './indicator';
|
|
|
4
4
|
export type { IndicatorWidgetData } from './indicator/types';
|
|
5
5
|
export { HighchartsPlugin } from './highcharts';
|
|
6
6
|
export * from './highcharts/types';
|
|
7
|
+
export { HighchartsReact } from './highcharts/renderer/components/HighchartsReact';
|
package/build/plugins/index.js
CHANGED
|
@@ -11,7 +11,7 @@ export type BarXSeriesData<T = any> = BaseSeriesData<T> & {
|
|
|
11
11
|
export type BarXSeries<T = any> = BaseSeries & {
|
|
12
12
|
type: 'bar-x';
|
|
13
13
|
data: BarXSeriesData<T>[];
|
|
14
|
-
/** The name of the series (used in legend) */
|
|
14
|
+
/** The name of the series (used in legend, tooltip etc) */
|
|
15
15
|
name: string;
|
|
16
16
|
/** The main color of the series (hex, rgba) */
|
|
17
17
|
color?: string;
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import type { ChartKitWidgetLegend } from './legend';
|
|
1
2
|
export type BaseSeries = {
|
|
3
|
+
/** Individual series legend options. Has higher priority than legend options in widget data */
|
|
4
|
+
legend?: ChartKitWidgetLegend;
|
|
2
5
|
/** Initial visibility of the series */
|
|
3
6
|
visible?: boolean;
|
|
4
7
|
};
|
|
@@ -1,10 +1,27 @@
|
|
|
1
1
|
import type { BaseSeries, BaseSeriesData } from './base';
|
|
2
2
|
export type PieSeriesData<T = any> = BaseSeriesData<T> & {
|
|
3
|
+
/** The value of the pie segment. */
|
|
3
4
|
value: number;
|
|
4
|
-
|
|
5
|
+
/** The name of the pie segment (used in legend, tooltip etc). */
|
|
5
6
|
name: string;
|
|
7
|
+
/** Individual color for the pie segment. */
|
|
8
|
+
color?: string;
|
|
9
|
+
/** Initial visibility of the pie segment. */
|
|
10
|
+
visible?: boolean;
|
|
6
11
|
};
|
|
7
12
|
export type PieSeries<T = any> = BaseSeries & {
|
|
8
13
|
type: 'pie';
|
|
9
14
|
data: PieSeriesData<T>[];
|
|
15
|
+
/** The color of the border surrounding each segment. Default `--g-color-base-background` from @gravity-ui/uikit. */
|
|
16
|
+
borderColor?: string;
|
|
17
|
+
/** The width of the border surrounding each segment. Default 1px. */
|
|
18
|
+
borderWidth?: number;
|
|
19
|
+
/** The corner radius of the border surrounding each segment. Default 0. */
|
|
20
|
+
borderRadius?: number;
|
|
21
|
+
/** The center of the pie chart relative to the chart area. */
|
|
22
|
+
center?: [string | number | null, string | number | null];
|
|
23
|
+
/** The inner radius of the pie. Default 0. */
|
|
24
|
+
innerRadius?: string | number;
|
|
25
|
+
/** The radius of the pie relative to the chart area. The default behaviour is to scale to the chart area. */
|
|
26
|
+
radius?: string | number;
|
|
10
27
|
};
|
|
@@ -11,7 +11,7 @@ export type ScatterSeriesData<T = any> = BaseSeriesData<T> & {
|
|
|
11
11
|
export type ScatterSeries<T = any> = BaseSeries & {
|
|
12
12
|
type: 'scatter';
|
|
13
13
|
data: ScatterSeriesData<T>[];
|
|
14
|
-
/** The name of the series (used in legend) */
|
|
14
|
+
/** The name of the series (used in legend, tooltip etc) */
|
|
15
15
|
name: string;
|
|
16
16
|
/** The main color of the series (hex, rgba) */
|
|
17
17
|
color?: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravity-ui/chartkit",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"description": "React component used to render charts based on any sources you need",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "git@github.com:gravity-ui/ChartKit.git",
|
|
@@ -54,7 +54,6 @@
|
|
|
54
54
|
"gulp-replace": "^1.1.3",
|
|
55
55
|
"gulp-typescript": "^5.0.1",
|
|
56
56
|
"highcharts": "^8.2.2",
|
|
57
|
-
"highcharts-react-official": "^3.2.0",
|
|
58
57
|
"husky": "^4.2.5",
|
|
59
58
|
"jest": "^28.1.3",
|
|
60
59
|
"jest-environment-jsdom": "^28.1.2",
|
|
@@ -77,7 +76,6 @@
|
|
|
77
76
|
"peerDependencies": {
|
|
78
77
|
"@gravity-ui/uikit": "^5.0.0",
|
|
79
78
|
"highcharts": "^8.2.2",
|
|
80
|
-
"highcharts-react-official": "^3.2.0",
|
|
81
79
|
"react": "^16.0.0 || ^17.0.0 || ^18.0.0"
|
|
82
80
|
},
|
|
83
81
|
"scripts": {
|