@gravity-ui/chartkit 4.11.0 → 4.13.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/line/LineWithMarkers.d.ts +2 -0
- package/build/plugins/d3/examples/line/LineWithMarkers.js +67 -0
- package/build/plugins/d3/renderer/components/Legend.js +54 -20
- package/build/plugins/d3/renderer/components/styles.css +4 -4
- package/build/plugins/d3/renderer/hooks/useSeries/constants.d.ts +1 -0
- package/build/plugins/d3/renderer/hooks/useSeries/constants.js +1 -0
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-legend.d.ts +1 -1
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-line-series.d.ts +11 -2
- package/build/plugins/d3/renderer/hooks/useSeries/prepare-line-series.js +49 -5
- package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +28 -2
- package/build/plugins/d3/renderer/hooks/useSeries/utils.js +2 -2
- package/build/plugins/d3/renderer/hooks/useShapes/line/index.js +91 -3
- package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.js +11 -0
- package/build/plugins/d3/renderer/hooks/useShapes/line/types.d.ts +7 -0
- package/build/types/widget-data/legend.d.ts +8 -0
- package/build/types/widget-data/line.d.ts +7 -0
- package/build/types/widget-data/marker.d.ts +10 -0
- package/build/types/widget-data/marker.js +1 -0
- package/build/types/widget-data/series.d.ts +14 -2
- package/package.json +2 -2
|
@@ -0,0 +1,67 @@
|
|
|
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 && new Date(d.date) > new Date(2022, 0, 1));
|
|
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 LineWithMarkers = () => {
|
|
22
|
+
const { series } = prepareData();
|
|
23
|
+
const widgetData = {
|
|
24
|
+
series: {
|
|
25
|
+
data: series.map((s) => ({
|
|
26
|
+
type: 'line',
|
|
27
|
+
data: s.data.filter((d) => d.x),
|
|
28
|
+
name: s.name,
|
|
29
|
+
marker: { enabled: true, symbol: 'square' },
|
|
30
|
+
})),
|
|
31
|
+
},
|
|
32
|
+
yAxis: [
|
|
33
|
+
{
|
|
34
|
+
title: {
|
|
35
|
+
text: 'User score',
|
|
36
|
+
},
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
xAxis: {
|
|
40
|
+
type: 'datetime',
|
|
41
|
+
title: {
|
|
42
|
+
text: 'Release dates',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
tooltip: {
|
|
46
|
+
renderer: (d) => {
|
|
47
|
+
var _a;
|
|
48
|
+
const point = (_a = d.hovered[0]) === null || _a === void 0 ? void 0 : _a.data;
|
|
49
|
+
if (!point) {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const title = point.custom.title;
|
|
53
|
+
const score = point.custom.user_score;
|
|
54
|
+
const date = dateTime({ input: point.custom.date }).format('DD MMM YYYY');
|
|
55
|
+
return (React.createElement(React.Fragment, null,
|
|
56
|
+
React.createElement("b", null, title),
|
|
57
|
+
React.createElement("br", null),
|
|
58
|
+
"Release date: ",
|
|
59
|
+
date,
|
|
60
|
+
React.createElement("br", null),
|
|
61
|
+
"User score: ",
|
|
62
|
+
score));
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
return React.createElement(ChartKit, { type: "d3", data: widgetData });
|
|
67
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { select } from 'd3';
|
|
2
|
+
import { select, line as lineGenerator } from 'd3';
|
|
3
3
|
import { block } from '../../../../utils/cn';
|
|
4
4
|
const b = block('d3-legend');
|
|
5
5
|
const getLegendPosition = (args) => {
|
|
@@ -56,6 +56,58 @@ const appendPaginator = (args) => {
|
|
|
56
56
|
});
|
|
57
57
|
paginationLine.attr('transform', transform);
|
|
58
58
|
};
|
|
59
|
+
const legendSymbolGenerator = lineGenerator()
|
|
60
|
+
.x((d) => d.x)
|
|
61
|
+
.y((d) => d.y);
|
|
62
|
+
function renderLegendSymbol(args) {
|
|
63
|
+
const { selection, legend } = args;
|
|
64
|
+
const line = selection.data();
|
|
65
|
+
const getXPosition = (i) => {
|
|
66
|
+
return line.slice(0, i).reduce((acc, legendItem) => {
|
|
67
|
+
return (acc +
|
|
68
|
+
legendItem.symbol.width +
|
|
69
|
+
legendItem.symbol.padding +
|
|
70
|
+
legendItem.textWidth +
|
|
71
|
+
legend.itemDistance);
|
|
72
|
+
}, 0);
|
|
73
|
+
};
|
|
74
|
+
selection.each(function (d, i) {
|
|
75
|
+
const element = select(this);
|
|
76
|
+
const x = getXPosition(i);
|
|
77
|
+
const className = b('item-symbol', { shape: d.symbol.shape, unselected: !d.visible });
|
|
78
|
+
const color = d.visible ? d.color : '';
|
|
79
|
+
switch (d.symbol.shape) {
|
|
80
|
+
case 'path': {
|
|
81
|
+
const y = legend.lineHeight / 2;
|
|
82
|
+
const points = [
|
|
83
|
+
{ x: x, y },
|
|
84
|
+
{ x: x + d.symbol.width, y },
|
|
85
|
+
];
|
|
86
|
+
element
|
|
87
|
+
.append('path')
|
|
88
|
+
.attr('d', legendSymbolGenerator(points))
|
|
89
|
+
.attr('fill', 'none')
|
|
90
|
+
.attr('stroke-width', d.symbol.strokeWidth)
|
|
91
|
+
.attr('class', className)
|
|
92
|
+
.style('stroke', color);
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
case 'rect': {
|
|
96
|
+
const y = (legend.lineHeight - d.symbol.height) / 2;
|
|
97
|
+
element
|
|
98
|
+
.append('rect')
|
|
99
|
+
.attr('x', x)
|
|
100
|
+
.attr('y', y)
|
|
101
|
+
.attr('width', d.symbol.width)
|
|
102
|
+
.attr('height', d.symbol.height)
|
|
103
|
+
.attr('rx', d.symbol.radius)
|
|
104
|
+
.attr('class', className)
|
|
105
|
+
.style('fill', color);
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
59
111
|
export const Legend = (props) => {
|
|
60
112
|
const { boundsWidth, chartSeries, legend, items, config, onItemClick } = props;
|
|
61
113
|
const ref = React.useRef(null);
|
|
@@ -95,25 +147,7 @@ export const Legend = (props) => {
|
|
|
95
147
|
legend.itemDistance);
|
|
96
148
|
}, 0);
|
|
97
149
|
};
|
|
98
|
-
legendItemTemplate
|
|
99
|
-
.append('rect')
|
|
100
|
-
.attr('x', function (_d, i) {
|
|
101
|
-
return getXPosition(i);
|
|
102
|
-
})
|
|
103
|
-
.attr('y', (legendItem) => {
|
|
104
|
-
return (legend.lineHeight - legendItem.symbol.height) / 2;
|
|
105
|
-
})
|
|
106
|
-
.attr('width', (legendItem) => {
|
|
107
|
-
return legendItem.symbol.width;
|
|
108
|
-
})
|
|
109
|
-
.attr('height', (legendItem) => legendItem.symbol.height)
|
|
110
|
-
.attr('rx', (legendItem) => legendItem.symbol.radius)
|
|
111
|
-
.attr('class', function (d) {
|
|
112
|
-
return b('item-shape', { unselected: !d.visible });
|
|
113
|
-
})
|
|
114
|
-
.style('fill', function (d) {
|
|
115
|
-
return d.visible ? d.color : '';
|
|
116
|
-
});
|
|
150
|
+
renderLegendSymbol({ selection: legendItemTemplate, legend });
|
|
117
151
|
legendItemTemplate
|
|
118
152
|
.append('text')
|
|
119
153
|
.attr('x', function (legendItem, i) {
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
user-select: none;
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
.chartkit-d3-legend__item-
|
|
25
|
-
fill: var(--g-color-
|
|
24
|
+
.chartkit-d3-legend__item-symbol_shape_rect.chartkit-d3-legend__item-symbol_unselected {
|
|
25
|
+
fill: var(--g-color-text-hint);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
.chartkit-d3-legend__item-
|
|
29
|
-
|
|
28
|
+
.chartkit-d3-legend__item-symbol_shape_path.chartkit-d3-legend__item-symbol_unselected {
|
|
29
|
+
stroke: var(--g-color-text-hint);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
32
|
.chartkit-d3-legend__item-text {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BaseTextStyle } from '../../../../../types';
|
|
2
2
|
export declare const DEFAULT_LEGEND_SYMBOL_SIZE = 8;
|
|
3
|
+
export declare const DEFAULT_LEGEND_SYMBOL_PADDING = 5;
|
|
3
4
|
export declare const DEFAULT_DATALABELS_PADDING = 5;
|
|
4
5
|
export declare const DEFAULT_DATALABELS_STYLE: BaseTextStyle;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ChartKitWidgetData } from '../../../../../types
|
|
1
|
+
import type { ChartKitWidgetData } from '../../../../../types';
|
|
2
2
|
import type { PreparedAxis, PreparedChart } from '../useChartOptions/types';
|
|
3
3
|
import type { PreparedLegend, PreparedSeries, LegendItem } from './types';
|
|
4
4
|
export declare const getPreparedLegend: (args: {
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import { ScaleOrdinal } from 'd3';
|
|
2
2
|
import { ChartKitWidgetSeriesOptions, LineSeries } from '../../../../../types';
|
|
3
|
-
import {
|
|
3
|
+
import { PreparedLineSeries, PreparedLegend } from './types';
|
|
4
|
+
export declare const DEFAULT_LEGEND_SYMBOL_SIZE = 16;
|
|
5
|
+
export declare const DEFAULT_LINE_WIDTH = 1;
|
|
6
|
+
export declare const DEFAULT_MARKER: {
|
|
7
|
+
enabled: boolean;
|
|
8
|
+
symbol: string;
|
|
9
|
+
radius: number;
|
|
10
|
+
borderWidth: number;
|
|
11
|
+
borderColor: string;
|
|
12
|
+
};
|
|
4
13
|
type PrepareLineSeriesArgs = {
|
|
5
14
|
colorScale: ScaleOrdinal<string, string>;
|
|
6
15
|
series: LineSeries[];
|
|
7
16
|
seriesOptions?: ChartKitWidgetSeriesOptions;
|
|
8
17
|
legend: PreparedLegend;
|
|
9
18
|
};
|
|
10
|
-
export declare function prepareLineSeries(args: PrepareLineSeriesArgs):
|
|
19
|
+
export declare function prepareLineSeries(args: PrepareLineSeriesArgs): PreparedLineSeries[];
|
|
11
20
|
export {};
|
|
@@ -1,16 +1,58 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
2
|
+
import merge from 'lodash/merge';
|
|
3
|
+
import { DEFAULT_DATALABELS_PADDING, DEFAULT_DATALABELS_STYLE, DEFAULT_LEGEND_SYMBOL_PADDING, } from './constants';
|
|
4
4
|
import { getRandomCKId } from '../../../../../utils';
|
|
5
|
+
export const DEFAULT_LEGEND_SYMBOL_SIZE = 16;
|
|
6
|
+
export const DEFAULT_LINE_WIDTH = 1;
|
|
7
|
+
export const DEFAULT_MARKER = {
|
|
8
|
+
enabled: false,
|
|
9
|
+
symbol: 'circle',
|
|
10
|
+
radius: 4,
|
|
11
|
+
borderWidth: 0,
|
|
12
|
+
borderColor: '',
|
|
13
|
+
};
|
|
14
|
+
function prepareLineLegendSymbol(series, seriesOptions) {
|
|
15
|
+
var _a;
|
|
16
|
+
const symbolOptions = ((_a = series.legend) === null || _a === void 0 ? void 0 : _a.symbol) || {};
|
|
17
|
+
const defaultLineWidth = get(seriesOptions, 'line.lineWidth', DEFAULT_LINE_WIDTH);
|
|
18
|
+
return {
|
|
19
|
+
shape: 'path',
|
|
20
|
+
width: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.width) || DEFAULT_LEGEND_SYMBOL_SIZE,
|
|
21
|
+
padding: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.padding) || DEFAULT_LEGEND_SYMBOL_PADDING,
|
|
22
|
+
strokeWidth: get(series, 'lineWidth', defaultLineWidth),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function prepareMarker(series, seriesOptions) {
|
|
26
|
+
var _a;
|
|
27
|
+
const seriesHoverState = get(seriesOptions, 'line.states.hover');
|
|
28
|
+
const markerNormalState = Object.assign({}, DEFAULT_MARKER, (_a = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions.line) === null || _a === void 0 ? void 0 : _a.marker, series.marker);
|
|
29
|
+
const hoveredMarkerDefaultOptions = {
|
|
30
|
+
enabled: true,
|
|
31
|
+
radius: markerNormalState.radius,
|
|
32
|
+
borderWidth: 1,
|
|
33
|
+
borderColor: '#ffffff',
|
|
34
|
+
halo: {
|
|
35
|
+
enabled: true,
|
|
36
|
+
opacity: 0.25,
|
|
37
|
+
radius: 10,
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
states: {
|
|
42
|
+
normal: markerNormalState,
|
|
43
|
+
hover: merge(hoveredMarkerDefaultOptions, seriesHoverState === null || seriesHoverState === void 0 ? void 0 : seriesHoverState.marker),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
5
47
|
export function prepareLineSeries(args) {
|
|
6
48
|
const { colorScale, series: seriesList, seriesOptions, legend } = args;
|
|
7
|
-
const defaultLineWidth = get(seriesOptions, 'line.lineWidth',
|
|
49
|
+
const defaultLineWidth = get(seriesOptions, 'line.lineWidth', DEFAULT_LINE_WIDTH);
|
|
8
50
|
return seriesList.map((series) => {
|
|
9
51
|
var _a, _b;
|
|
10
52
|
const id = getRandomCKId();
|
|
11
53
|
const name = series.name || '';
|
|
12
54
|
const color = series.color || colorScale(name);
|
|
13
|
-
|
|
55
|
+
const prepared = {
|
|
14
56
|
type: series.type,
|
|
15
57
|
color,
|
|
16
58
|
lineWidth: get(series, 'lineWidth', defaultLineWidth),
|
|
@@ -19,7 +61,7 @@ export function prepareLineSeries(args) {
|
|
|
19
61
|
visible: get(series, 'visible', true),
|
|
20
62
|
legend: {
|
|
21
63
|
enabled: get(series, 'legend.enabled', legend.enabled),
|
|
22
|
-
symbol:
|
|
64
|
+
symbol: prepareLineLegendSymbol(series, seriesOptions),
|
|
23
65
|
},
|
|
24
66
|
data: series.data,
|
|
25
67
|
dataLabels: {
|
|
@@ -28,6 +70,8 @@ export function prepareLineSeries(args) {
|
|
|
28
70
|
padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
|
|
29
71
|
allowOverlap: get(series, 'dataLabels.allowOverlap', false),
|
|
30
72
|
},
|
|
73
|
+
marker: prepareMarker(series, seriesOptions),
|
|
31
74
|
};
|
|
75
|
+
return prepared;
|
|
32
76
|
}, []);
|
|
33
77
|
}
|
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { BarXSeries, BarXSeriesData, BaseTextStyle, ChartKitWidgetLegend, PieSeries, PieSeriesData, RectLegendSymbolOptions, ScatterSeries, ScatterSeriesData, BarYSeries, BarYSeriesData, LineSeries, LineSeriesData, ConnectorShape, ConnectorCurve } from '../../../../../types';
|
|
1
|
+
import { BarXSeries, BarXSeriesData, BaseTextStyle, ChartKitWidgetLegend, PieSeries, PieSeriesData, RectLegendSymbolOptions, ScatterSeries, ScatterSeriesData, BarYSeries, BarYSeriesData, LineSeries, LineSeriesData, ConnectorShape, ConnectorCurve, PathLegendSymbolOptions } from '../../../../../types';
|
|
2
2
|
import type { SeriesOptionsDefaults } from '../../constants';
|
|
3
3
|
export type RectLegendSymbol = {
|
|
4
4
|
shape: 'rect';
|
|
5
5
|
} & Required<RectLegendSymbolOptions>;
|
|
6
|
-
export type
|
|
6
|
+
export type PathLegendSymbol = {
|
|
7
|
+
shape: 'path';
|
|
8
|
+
strokeWidth: number;
|
|
9
|
+
} & Required<PathLegendSymbolOptions>;
|
|
10
|
+
export type PreparedLegendSymbol = RectLegendSymbol | PathLegendSymbol;
|
|
7
11
|
export type PreparedLegend = Required<ChartKitWidgetLegend> & {
|
|
8
12
|
height: number;
|
|
9
13
|
lineHeight: number;
|
|
@@ -100,6 +104,28 @@ export type PreparedLineSeries = {
|
|
|
100
104
|
padding: number;
|
|
101
105
|
allowOverlap: boolean;
|
|
102
106
|
};
|
|
107
|
+
marker: {
|
|
108
|
+
states: {
|
|
109
|
+
normal: {
|
|
110
|
+
symbol: string;
|
|
111
|
+
enabled: boolean;
|
|
112
|
+
radius: number;
|
|
113
|
+
borderWidth: number;
|
|
114
|
+
borderColor: string;
|
|
115
|
+
};
|
|
116
|
+
hover: {
|
|
117
|
+
enabled: boolean;
|
|
118
|
+
radius: number;
|
|
119
|
+
borderWidth: number;
|
|
120
|
+
borderColor: string;
|
|
121
|
+
halo: {
|
|
122
|
+
enabled: boolean;
|
|
123
|
+
opacity: number;
|
|
124
|
+
radius: number;
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
};
|
|
103
129
|
} & BasePreparedSeries;
|
|
104
130
|
export type PreparedSeries = PreparedScatterSeries | PreparedBarXSeries | PreparedBarYSeries | PreparedPieSeries | PreparedLineSeries;
|
|
105
131
|
export type PreparedSeriesOptions = SeriesOptionsDefaults;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { DEFAULT_LEGEND_SYMBOL_SIZE } from './constants';
|
|
1
|
+
import { DEFAULT_LEGEND_SYMBOL_PADDING, DEFAULT_LEGEND_SYMBOL_SIZE } from './constants';
|
|
2
2
|
export const getActiveLegendItems = (series) => {
|
|
3
3
|
return series.reduce((acc, s) => {
|
|
4
4
|
if (s.legend.enabled && s.visible) {
|
|
@@ -19,6 +19,6 @@ export function prepareLegendSymbol(series) {
|
|
|
19
19
|
width: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.width) || DEFAULT_LEGEND_SYMBOL_SIZE,
|
|
20
20
|
height: symbolHeight,
|
|
21
21
|
radius: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.radius) || symbolHeight / 2,
|
|
22
|
-
padding: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.padding) ||
|
|
22
|
+
padding: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.padding) || DEFAULT_LEGEND_SYMBOL_PADDING,
|
|
23
23
|
};
|
|
24
24
|
}
|
|
@@ -1,10 +1,43 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { color, line as lineGenerator, select, symbol, symbolCircle, symbolSquare } from 'd3';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
4
|
import { block } from '../../../../../../utils/cn';
|
|
5
5
|
import { filterOverlappingLabels } from '../../../utils';
|
|
6
6
|
import { setActiveState } from '../utils';
|
|
7
7
|
const b = block('d3-line');
|
|
8
|
+
function setMarker(selection, state) {
|
|
9
|
+
selection
|
|
10
|
+
.attr('d', (d) => {
|
|
11
|
+
const radius = d.point.series.marker.states[state].radius +
|
|
12
|
+
d.point.series.marker.states[state].borderWidth;
|
|
13
|
+
return getMarkerSymbol(d.point.series.marker.states.normal.symbol, radius);
|
|
14
|
+
})
|
|
15
|
+
.attr('stroke-width', (d) => d.point.series.marker.states[state].borderWidth)
|
|
16
|
+
.attr('stroke', (d) => d.point.series.marker.states[state].borderColor);
|
|
17
|
+
}
|
|
18
|
+
function getMarkerSymbol(type, radius) {
|
|
19
|
+
switch (type) {
|
|
20
|
+
case 'square': {
|
|
21
|
+
const size = Math.pow(radius, 2) * Math.PI;
|
|
22
|
+
return symbol(symbolSquare, size)();
|
|
23
|
+
}
|
|
24
|
+
case 'circle':
|
|
25
|
+
default: {
|
|
26
|
+
const size = Math.pow(radius, 2) * Math.PI;
|
|
27
|
+
return symbol(symbolCircle, size)();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const getMarkerVisibility = (d) => {
|
|
32
|
+
const markerStates = d.point.series.marker.states;
|
|
33
|
+
const enabled = (markerStates.hover.enabled && d.hovered) || markerStates.normal.enabled;
|
|
34
|
+
return enabled ? '' : 'hidden';
|
|
35
|
+
};
|
|
36
|
+
const getMarkerHaloVisibility = (d) => {
|
|
37
|
+
const markerStates = d.point.series.marker.states;
|
|
38
|
+
const enabled = markerStates.hover.halo.enabled && d.hovered;
|
|
39
|
+
return enabled ? '' : 'hidden';
|
|
40
|
+
};
|
|
8
41
|
export const LineSeriesShapes = (args) => {
|
|
9
42
|
const { dispatcher, preparedData, seriesOptions } = args;
|
|
10
43
|
const ref = React.useRef(null);
|
|
@@ -48,11 +81,40 @@ export const LineSeriesShapes = (args) => {
|
|
|
48
81
|
.style('font-size', (d) => d.style.fontSize)
|
|
49
82
|
.style('font-weight', (d) => d.style.fontWeight || null)
|
|
50
83
|
.style('fill', (d) => d.style.fontColor || null);
|
|
84
|
+
const markers = preparedData.reduce((acc, d) => acc.concat(d.markers), []);
|
|
85
|
+
const markerSelection = svgElement
|
|
86
|
+
.selectAll('marker')
|
|
87
|
+
.data(markers)
|
|
88
|
+
.join('g')
|
|
89
|
+
.attr('class', b('marker'))
|
|
90
|
+
.attr('visibility', getMarkerVisibility)
|
|
91
|
+
.attr('transform', (d) => {
|
|
92
|
+
return `translate(${d.point.x},${d.point.y})`;
|
|
93
|
+
});
|
|
94
|
+
markerSelection
|
|
95
|
+
.append('path')
|
|
96
|
+
.attr('class', b('marker-halo'))
|
|
97
|
+
.attr('d', (d) => {
|
|
98
|
+
const type = d.point.series.marker.states.normal.symbol;
|
|
99
|
+
const radius = d.point.series.marker.states.hover.halo.radius;
|
|
100
|
+
return getMarkerSymbol(type, radius);
|
|
101
|
+
})
|
|
102
|
+
.attr('fill', (d) => d.point.series.color)
|
|
103
|
+
.attr('opacity', (d) => d.point.series.marker.states.hover.halo.opacity)
|
|
104
|
+
.attr('z-index', -1)
|
|
105
|
+
.attr('visibility', getMarkerHaloVisibility);
|
|
106
|
+
markerSelection
|
|
107
|
+
.append('path')
|
|
108
|
+
.attr('class', b('marker-symbol'))
|
|
109
|
+
.call(setMarker, 'normal')
|
|
110
|
+
.attr('fill', (d) => d.point.series.color);
|
|
51
111
|
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
52
112
|
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
53
113
|
dispatcher.on('hover-shape.line', (data) => {
|
|
54
|
-
var _a
|
|
55
|
-
const
|
|
114
|
+
var _a;
|
|
115
|
+
const selected = data === null || data === void 0 ? void 0 : data.find((d) => d.series.type === 'line');
|
|
116
|
+
const selectedDataItem = selected === null || selected === void 0 ? void 0 : selected.data;
|
|
117
|
+
const selectedSeriesId = (_a = selected === null || selected === void 0 ? void 0 : selected.series) === null || _a === void 0 ? void 0 : _a.id;
|
|
56
118
|
lineSelection.datum((d, index, list) => {
|
|
57
119
|
const elementSelection = select(list[index]);
|
|
58
120
|
const hovered = Boolean(hoverEnabled && d.id === selectedSeriesId);
|
|
@@ -82,6 +144,32 @@ export const LineSeriesShapes = (args) => {
|
|
|
82
144
|
datum: d,
|
|
83
145
|
});
|
|
84
146
|
});
|
|
147
|
+
markerSelection.datum((d, index, list) => {
|
|
148
|
+
const elementSelection = select(list[index]);
|
|
149
|
+
const hovered = Boolean(hoverEnabled && d.point.data === selectedDataItem);
|
|
150
|
+
if (d.hovered !== hovered) {
|
|
151
|
+
d.hovered = hovered;
|
|
152
|
+
elementSelection.attr('visibility', getMarkerVisibility(d));
|
|
153
|
+
elementSelection
|
|
154
|
+
.select(`.${b('marker-halo')}`)
|
|
155
|
+
.attr('visibility', getMarkerHaloVisibility);
|
|
156
|
+
elementSelection
|
|
157
|
+
.select(`.${b('marker-symbol')}`)
|
|
158
|
+
.call(setMarker, hovered ? 'hover' : 'normal');
|
|
159
|
+
}
|
|
160
|
+
if (d.point.series.marker.states.normal.enabled) {
|
|
161
|
+
const isActive = Boolean(!inactiveEnabled ||
|
|
162
|
+
!selectedSeriesId ||
|
|
163
|
+
selectedSeriesId === d.point.series.id);
|
|
164
|
+
setActiveState({
|
|
165
|
+
element: list[index],
|
|
166
|
+
state: inactiveOptions,
|
|
167
|
+
active: isActive,
|
|
168
|
+
datum: d,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return d;
|
|
172
|
+
});
|
|
85
173
|
});
|
|
86
174
|
return () => {
|
|
87
175
|
dispatcher.on('hover-shape.line', null);
|
|
@@ -35,14 +35,25 @@ export const prepareLineData = (args) => {
|
|
|
35
35
|
const points = s.data.map((d) => ({
|
|
36
36
|
x: getXValue({ point: d, xAxis, xScale }),
|
|
37
37
|
y: getYValue({ point: d, yAxis, yScale }),
|
|
38
|
+
active: true,
|
|
38
39
|
data: d,
|
|
40
|
+
series: s,
|
|
39
41
|
}));
|
|
40
42
|
let labels = [];
|
|
41
43
|
if (s.dataLabels.enabled) {
|
|
42
44
|
labels = points.map((p) => getLabelData(p, s, xMax));
|
|
43
45
|
}
|
|
46
|
+
let markers = [];
|
|
47
|
+
if (s.marker.states.normal.enabled || s.marker.states.hover.enabled) {
|
|
48
|
+
markers = points.map((p) => ({
|
|
49
|
+
point: p,
|
|
50
|
+
active: true,
|
|
51
|
+
hovered: false,
|
|
52
|
+
}));
|
|
53
|
+
}
|
|
44
54
|
acc.push({
|
|
45
55
|
points,
|
|
56
|
+
markers,
|
|
46
57
|
labels,
|
|
47
58
|
color: s.color,
|
|
48
59
|
width: s.lineWidth,
|
|
@@ -5,10 +5,17 @@ export type PointData = {
|
|
|
5
5
|
x: number;
|
|
6
6
|
y: number;
|
|
7
7
|
data: LineSeriesData;
|
|
8
|
+
series: PreparedLineSeries;
|
|
9
|
+
};
|
|
10
|
+
export type MarkerData = {
|
|
11
|
+
point: PointData;
|
|
12
|
+
active: boolean;
|
|
13
|
+
hovered: boolean;
|
|
8
14
|
};
|
|
9
15
|
export type PreparedLineData = {
|
|
10
16
|
id: string;
|
|
11
17
|
points: PointData[];
|
|
18
|
+
markers: MarkerData[];
|
|
12
19
|
color: string;
|
|
13
20
|
width: number;
|
|
14
21
|
series: PreparedLineSeries;
|
|
@@ -50,3 +50,11 @@ export type RectLegendSymbolOptions = BaseLegendSymbol & {
|
|
|
50
50
|
*/
|
|
51
51
|
radius?: number;
|
|
52
52
|
};
|
|
53
|
+
export type PathLegendSymbolOptions = BaseLegendSymbol & {
|
|
54
|
+
/**
|
|
55
|
+
* The pixel width of the symbol for series types that use a path in the legend
|
|
56
|
+
*
|
|
57
|
+
* @default 16
|
|
58
|
+
* */
|
|
59
|
+
width?: number;
|
|
60
|
+
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BaseSeries, BaseSeriesData } from './base';
|
|
2
2
|
import { ChartKitWidgetLegend, RectLegendSymbolOptions } from './legend';
|
|
3
|
+
import { PointMarkerOptions } from './marker';
|
|
3
4
|
export type LineSeriesData<T = any> = BaseSeriesData<T> & {
|
|
4
5
|
/**
|
|
5
6
|
* The `x` value of the point. Depending on the context , it may represents:
|
|
@@ -18,6 +19,10 @@ export type LineSeriesData<T = any> = BaseSeriesData<T> & {
|
|
|
18
19
|
/** Data label value of the point. If not specified, the y value is used. */
|
|
19
20
|
label?: string | number;
|
|
20
21
|
};
|
|
22
|
+
export type LineMarkerSymbol = 'circle' | 'square';
|
|
23
|
+
export type LineMarkerOptions = PointMarkerOptions & {
|
|
24
|
+
symbol?: LineMarkerSymbol;
|
|
25
|
+
};
|
|
21
26
|
export type LineSeries<T = any> = BaseSeries & {
|
|
22
27
|
type: 'line';
|
|
23
28
|
data: LineSeriesData<T>[];
|
|
@@ -34,4 +39,6 @@ export type LineSeries<T = any> = BaseSeries & {
|
|
|
34
39
|
legend?: ChartKitWidgetLegend & {
|
|
35
40
|
symbol?: RectLegendSymbolOptions;
|
|
36
41
|
};
|
|
42
|
+
/** Options for the point markers of line series */
|
|
43
|
+
marker?: LineMarkerOptions;
|
|
37
44
|
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type PointMarkerOptions = {
|
|
2
|
+
/** Enable or disable the point marker */
|
|
3
|
+
enabled?: boolean;
|
|
4
|
+
/** The radius of the point marker */
|
|
5
|
+
radius?: number;
|
|
6
|
+
/** The color of the point marker's border */
|
|
7
|
+
borderColor?: string;
|
|
8
|
+
/** The width of the point marker's border */
|
|
9
|
+
borderWidth?: number;
|
|
10
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -2,8 +2,9 @@ import React from 'react';
|
|
|
2
2
|
import type { PieSeries, PieSeriesData } from './pie';
|
|
3
3
|
import type { ScatterSeries, ScatterSeriesData } from './scatter';
|
|
4
4
|
import type { BarXSeries, BarXSeriesData } from './bar-x';
|
|
5
|
-
import type { LineSeries, LineSeriesData } from './line';
|
|
5
|
+
import type { LineSeries, LineSeriesData, LineMarkerOptions } from './line';
|
|
6
6
|
import type { BarYSeries, BarYSeriesData } from './bar-y';
|
|
7
|
+
import { PointMarkerOptions } from './marker';
|
|
7
8
|
export type ChartKitWidgetSeries<T = any> = ScatterSeries<T> | PieSeries<T> | BarXSeries<T> | BarYSeries<T> | LineSeries<T>;
|
|
8
9
|
export type ChartKitWidgetSeriesData<T = any> = ScatterSeriesData<T> | PieSeriesData<T> | BarXSeriesData<T> | BarYSeriesData<T> | LineSeriesData<T>;
|
|
9
10
|
export type DataLabelRendererData<T = any> = {
|
|
@@ -142,9 +143,20 @@ export type ChartKitWidgetSeriesOptions = {
|
|
|
142
143
|
lineWidth?: number;
|
|
143
144
|
/** Options for the series states that provide additional styling information to the series. */
|
|
144
145
|
states?: {
|
|
145
|
-
hover?: BasicHoverState
|
|
146
|
+
hover?: BasicHoverState & {
|
|
147
|
+
marker?: PointMarkerOptions & {
|
|
148
|
+
/** Options for the halo appearing around the hovered point */
|
|
149
|
+
halo?: {
|
|
150
|
+
enabled?: boolean;
|
|
151
|
+
opacity?: number;
|
|
152
|
+
radius?: number;
|
|
153
|
+
};
|
|
154
|
+
};
|
|
155
|
+
};
|
|
146
156
|
inactive?: BasicInactiveState;
|
|
147
157
|
};
|
|
158
|
+
/** Options for the point markers of line series */
|
|
159
|
+
marker?: LineMarkerOptions;
|
|
148
160
|
};
|
|
149
161
|
};
|
|
150
162
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravity-ui/chartkit",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.13.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",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@bem-react/classname": "^1.6.0",
|
|
50
50
|
"@gravity-ui/date-utils": "^1.4.1",
|
|
51
|
-
"@gravity-ui/yagr": "^4.0.
|
|
51
|
+
"@gravity-ui/yagr": "^4.0.3",
|
|
52
52
|
"afterframe": "^1.0.2",
|
|
53
53
|
"d3": "^7.8.5",
|
|
54
54
|
"lodash": "^4.17.21",
|