@gravity-ui/chartkit 4.15.0 → 4.16.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.
@@ -19,6 +19,13 @@ export declare enum DashStyle {
19
19
  ShortDot = "ShortDot",
20
20
  Solid = "Solid"
21
21
  }
22
+ export declare enum SymbolType {
23
+ Circle = "circle",
24
+ Diamond = "diamond",
25
+ Square = "square",
26
+ Triangle = "triangle",
27
+ TriangleDown = "triangle-down"
28
+ }
22
29
  export declare enum LineCap {
23
30
  Butt = "butt",
24
31
  Round = "round",
@@ -20,6 +20,14 @@ export var DashStyle;
20
20
  DashStyle["ShortDot"] = "ShortDot";
21
21
  DashStyle["Solid"] = "Solid";
22
22
  })(DashStyle || (DashStyle = {}));
23
+ export var SymbolType;
24
+ (function (SymbolType) {
25
+ SymbolType["Circle"] = "circle";
26
+ SymbolType["Diamond"] = "diamond";
27
+ SymbolType["Square"] = "square";
28
+ SymbolType["Triangle"] = "triangle";
29
+ SymbolType["TriangleDown"] = "triangle-down";
30
+ })(SymbolType || (SymbolType = {}));
23
31
  export var LineCap;
24
32
  (function (LineCap) {
25
33
  LineCap["Butt"] = "butt";
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
- import { select, line as lineGenerator } from 'd3';
2
+ import { symbol, select, line as lineGenerator } from 'd3';
3
+ import { getSymbol } from '../utils';
3
4
  import { block } from '../../../../utils/cn';
4
5
  import { getLineDashArray } from '../hooks/useShapes/utils';
5
6
  const b = block('d3-legend');
@@ -109,6 +110,23 @@ function renderLegendSymbol(args) {
109
110
  .style('fill', color);
110
111
  break;
111
112
  }
113
+ case 'symbol': {
114
+ const y = legend.lineHeight / 2;
115
+ element
116
+ .append('svg:path')
117
+ .attr('d', () => {
118
+ const scatterSymbol = getSymbol(d.symbol.symbolType);
119
+ // D3 takes size as square pixels, so we need to make square pixels size by multiplying
120
+ // https://d3js.org/d3-shape/symbol#symbol
121
+ return symbol(scatterSymbol, d.symbol.width * d.symbol.width)();
122
+ })
123
+ .attr('transform', () => {
124
+ return 'translate(' + x + ',' + y + ')';
125
+ })
126
+ .attr('class', className)
127
+ .style('fill', color);
128
+ break;
129
+ }
112
130
  }
113
131
  });
114
132
  }
@@ -29,6 +29,10 @@
29
29
  stroke: var(--g-color-text-hint);
30
30
  }
31
31
 
32
+ .chartkit-d3-legend__item-symbol_shape_symbol.chartkit-d3-legend__item-symbol_unselected {
33
+ fill: var(--g-color-text-hint);
34
+ }
35
+
32
36
  .chartkit-d3-legend__item-text {
33
37
  fill: var(--g-color-text-secondary);
34
38
  alignment-baseline: before-edge;
@@ -1,5 +1,6 @@
1
1
  import cloneDeep from 'lodash/cloneDeep';
2
2
  import get from 'lodash/get';
3
+ import { getSymbolType } from '../../utils';
3
4
  import { prepareLineSeries } from './prepare-line-series';
4
5
  import { prepareBarXSeries } from './prepare-bar-x';
5
6
  import { prepareBarYSeries } from './prepare-bar-y';
@@ -8,15 +9,17 @@ import { ChartKitError } from '../../../../../libs';
8
9
  import { preparePieSeries } from './prepare-pie';
9
10
  import { prepareArea } from './prepare-area';
10
11
  function prepareAxisRelatedSeries(args) {
11
- const { colorScale, series, legend } = args;
12
+ const { colorScale, series, legend, index } = args;
12
13
  const preparedSeries = cloneDeep(series);
13
14
  const name = 'name' in series && series.name ? series.name : '';
15
+ const symbolType = (series.symbolType || getSymbolType(index));
16
+ preparedSeries.symbolType = symbolType;
14
17
  preparedSeries.color = 'color' in series && series.color ? series.color : colorScale(name);
15
18
  preparedSeries.name = name;
16
19
  preparedSeries.visible = get(preparedSeries, 'visible', true);
17
20
  preparedSeries.legend = {
18
21
  enabled: get(preparedSeries, 'legend.enabled', legend.enabled),
19
- symbol: prepareLegendSymbol(series),
22
+ symbol: prepareLegendSymbol(series, symbolType),
20
23
  };
21
24
  return [preparedSeries];
22
25
  }
@@ -36,8 +39,8 @@ export function prepareSeries(args) {
36
39
  return prepareBarYSeries({ series: series, legend, colorScale });
37
40
  }
38
41
  case 'scatter': {
39
- return series.reduce((acc, singleSeries) => {
40
- acc.push(...prepareAxisRelatedSeries({ series: singleSeries, legend, colorScale }));
42
+ return series.reduce((acc, singleSeries, index) => {
43
+ acc.push(...prepareAxisRelatedSeries({ series: singleSeries, legend, colorScale, index }));
41
44
  return acc;
42
45
  }, []);
43
46
  }
@@ -1,6 +1,6 @@
1
- import { BarXSeries, BarXSeriesData, BaseTextStyle, ChartKitWidgetLegend, PieSeries, PieSeriesData, RectLegendSymbolOptions, ScatterSeries, ScatterSeriesData, BarYSeries, BarYSeriesData, LineSeries, LineSeriesData, ConnectorShape, ConnectorCurve, PathLegendSymbolOptions, AreaSeries, AreaSeriesData } from '../../../../../types';
1
+ import { BarXSeries, BarXSeriesData, BaseTextStyle, ChartKitWidgetLegend, PieSeries, PieSeriesData, RectLegendSymbolOptions, ScatterSeries, ScatterSeriesData, BarYSeries, BarYSeriesData, LineSeries, LineSeriesData, ConnectorShape, ConnectorCurve, PathLegendSymbolOptions, SymbolLegendSymbolOptions, AreaSeries, AreaSeriesData } from '../../../../../types';
2
2
  import type { SeriesOptionsDefaults } from '../../constants';
3
- import { DashStyle, LineCap } from '../../../../../constants';
3
+ import { DashStyle, LineCap, SymbolType } from '../../../../../constants';
4
4
  export type RectLegendSymbol = {
5
5
  shape: 'rect';
6
6
  } & Required<RectLegendSymbolOptions>;
@@ -8,7 +8,11 @@ export type PathLegendSymbol = {
8
8
  shape: 'path';
9
9
  strokeWidth: number;
10
10
  } & Required<PathLegendSymbolOptions>;
11
- export type PreparedLegendSymbol = RectLegendSymbol | PathLegendSymbol;
11
+ export type SymbolLegendSymbol = {
12
+ shape: 'symbol';
13
+ symbolType: SymbolType;
14
+ } & Required<SymbolLegendSymbolOptions>;
15
+ export type PreparedLegendSymbol = RectLegendSymbol | PathLegendSymbol | SymbolLegendSymbol;
12
16
  export type PreparedLegend = Required<ChartKitWidgetLegend> & {
13
17
  height: number;
14
18
  lineHeight: number;
@@ -53,6 +57,7 @@ type BasePreparedSeries = {
53
57
  export type PreparedScatterSeries = {
54
58
  type: ScatterSeries['type'];
55
59
  data: ScatterSeriesData[];
60
+ symbolType: SymbolType;
56
61
  } & BasePreparedSeries;
57
62
  export type PreparedBarXSeries = {
58
63
  type: BarXSeries['type'];
@@ -1,6 +1,7 @@
1
1
  import { PreparedLegendSymbol, PreparedSeries, StackedSeries } from './types';
2
2
  import { ChartKitWidgetSeries } from '../../../../../types';
3
+ import { SymbolType } from '../../../../../constants';
3
4
  export declare const getActiveLegendItems: (series: PreparedSeries[]) => string[];
4
5
  export declare const getAllLegendItems: (series: PreparedSeries[]) => string[];
5
- export declare function prepareLegendSymbol(series: ChartKitWidgetSeries): PreparedLegendSymbol;
6
+ export declare function prepareLegendSymbol(series: ChartKitWidgetSeries, symbolType?: SymbolType): PreparedLegendSymbol;
6
7
  export declare function getSeriesStackId(series: StackedSeries): string;
@@ -1,6 +1,7 @@
1
1
  import memoize from 'lodash/memoize';
2
- import { DEFAULT_LEGEND_SYMBOL_PADDING, DEFAULT_LEGEND_SYMBOL_SIZE } from './constants';
3
2
  import { getRandomCKId } from '../../../../../utils';
3
+ import { DEFAULT_LEGEND_SYMBOL_PADDING, DEFAULT_LEGEND_SYMBOL_SIZE } from './constants';
4
+ import { SymbolType } from '../../../../../constants';
4
5
  export const getActiveLegendItems = (series) => {
5
6
  return series.reduce((acc, s) => {
6
7
  if (s.legend.enabled && s.visible) {
@@ -12,15 +13,13 @@ export const getActiveLegendItems = (series) => {
12
13
  export const getAllLegendItems = (series) => {
13
14
  return series.map((s) => s.name);
14
15
  };
15
- export function prepareLegendSymbol(series) {
16
+ export function prepareLegendSymbol(series, symbolType) {
16
17
  var _a;
17
18
  const symbolOptions = ((_a = series.legend) === null || _a === void 0 ? void 0 : _a.symbol) || {};
18
- const symbolHeight = (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.height) || DEFAULT_LEGEND_SYMBOL_SIZE;
19
19
  return {
20
- shape: 'rect',
20
+ shape: 'symbol',
21
+ symbolType: symbolType || SymbolType.Circle,
21
22
  width: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.width) || DEFAULT_LEGEND_SYMBOL_SIZE,
22
- height: symbolHeight,
23
- radius: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.radius) || symbolHeight / 2,
24
23
  padding: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.padding) || DEFAULT_LEGEND_SYMBOL_PADDING,
25
24
  };
26
25
  }
@@ -1,12 +1,12 @@
1
1
  import React from 'react';
2
2
  import get from 'lodash/get';
3
- import { color, pointer, select } from 'd3';
3
+ import { symbol, color, pointer, select } from 'd3';
4
4
  import { block } from '../../../../../../utils/cn';
5
- import { extractD3DataFromNode, isNodeContainsD3Data } from '../../../utils';
5
+ import { extractD3DataFromNode, isNodeContainsD3Data, getSymbol } from '../../../utils';
6
6
  import { shapeKey } from '../utils';
7
+ import { SymbolType } from '../../../../../../constants';
7
8
  export { prepareScatterData } from './prepare-data';
8
9
  const b = block('d3-scatter');
9
- const DEFAULT_SCATTER_POINT_RADIUS = 4;
10
10
  const EMPTY_SELECTION = null;
11
11
  const isNodeContainsScatterData = (node) => {
12
12
  return isNodeContainsD3Data(node);
@@ -22,13 +22,20 @@ export function ScatterSeriesShape(props) {
22
22
  const hoverOptions = get(seriesOptions, 'scatter.states.hover');
23
23
  const inactiveOptions = get(seriesOptions, 'scatter.states.inactive');
24
24
  const selection = svgElement
25
- .selectAll('circle')
25
+ .selectAll('path')
26
26
  .data(preparedData, shapeKey)
27
- .join((enter) => enter.append('circle').attr('class', b('point')), (update) => update, (exit) => exit.remove())
28
- .attr('fill', (d) => d.data.color || d.series.color || '')
29
- .attr('r', (d) => d.data.radius || DEFAULT_SCATTER_POINT_RADIUS)
30
- .attr('cx', (d) => d.cx)
31
- .attr('cy', (d) => d.cy);
27
+ .join((enter) => enter.append('path').attr('class', b('point')), (update) => update, (exit) => exit.remove())
28
+ .attr('d', (d) => {
29
+ const symbolType = d.series.symbolType || SymbolType.Circle;
30
+ const scatterSymbol = getSymbol(symbolType);
31
+ // D3 takes size as square pixels, so we need to make square pixels size by multiplying
32
+ // https://d3js.org/d3-shape/symbol#symbol
33
+ return symbol(scatterSymbol, d.size * d.size)();
34
+ })
35
+ .attr('transform', (d) => {
36
+ return 'translate(' + d.cx + ',' + d.cy + ')';
37
+ })
38
+ .attr('fill', (d) => d.data.color || d.series.color || '');
32
39
  svgElement
33
40
  .on('mousemove', (e) => {
34
41
  const point = e.target;
@@ -9,6 +9,7 @@ export type PreparedScatterData = Omit<TooltipDataChunkScatter, 'series'> & {
9
9
  hovered: boolean;
10
10
  active: boolean;
11
11
  id: number;
12
+ size: number;
12
13
  };
13
14
  export declare const prepareScatterData: (args: {
14
15
  series: PreparedScatterSeries[];
@@ -1,4 +1,5 @@
1
1
  import { getXValue, getYValue } from '../utils';
2
+ const DEFAULT_SCATTER_POINT_SIZE = 7;
2
3
  const getFilteredLinearScatterData = (data) => {
3
4
  return data.filter((d) => typeof d.x === 'number' && typeof d.y === 'number');
4
5
  };
@@ -9,6 +10,7 @@ export const prepareScatterData = (args) => {
9
10
  ? s.data
10
11
  : getFilteredLinearScatterData(s.data);
11
12
  filteredData.forEach((d) => {
13
+ const size = d.radius ? d.radius * 2 : DEFAULT_SCATTER_POINT_SIZE;
12
14
  acc.push({
13
15
  data: d,
14
16
  series: s,
@@ -17,6 +19,7 @@ export const prepareScatterData = (args) => {
17
19
  hovered: false,
18
20
  active: true,
19
21
  id: acc.length - 1,
22
+ size,
20
23
  });
21
24
  });
22
25
  return acc;
@@ -6,6 +6,7 @@ export * from './text';
6
6
  export * from './time';
7
7
  export * from './axis';
8
8
  export * from './labels';
9
+ export * from './symbol';
9
10
  export type AxisDirection = 'x' | 'y';
10
11
  export type NodeWithD3Data<T = unknown> = Element & {
11
12
  __data__: T;
@@ -12,6 +12,7 @@ export * from './text';
12
12
  export * from './time';
13
13
  export * from './axis';
14
14
  export * from './labels';
15
+ export * from './symbol';
15
16
  const CHARTS_WITHOUT_AXIS = ['pie'];
16
17
  /**
17
18
  * Checks whether the series should be drawn with axes.
@@ -0,0 +1,5 @@
1
+ import { SymbolType } from '../../../../constants';
2
+ export declare const getSymbolType: (index: number) => SymbolType;
3
+ export declare const getSymbol: (symbolType: SymbolType) => {
4
+ draw: (context: CanvasPath, size: number) => void;
5
+ };
@@ -0,0 +1,36 @@
1
+ import { symbolDiamond2, symbolCircle, symbolSquare, symbolTriangle2 } from 'd3';
2
+ import { SymbolType } from '../../../../constants';
3
+ export const getSymbolType = (index) => {
4
+ const scatterStyles = Object.values(SymbolType);
5
+ return scatterStyles[index % scatterStyles.length];
6
+ };
7
+ // This is an inverted triangle
8
+ // Based on https://github.com/d3/d3-shape/blob/main/src/symbol/triangle2.js
9
+ const sqrt3 = Math.sqrt(3);
10
+ const triangleDown = {
11
+ draw: (context, size) => {
12
+ const s = Math.sqrt(size) * 0.6824;
13
+ const t = s / 2;
14
+ const u = (s * sqrt3) / 2;
15
+ context.moveTo(0, s);
16
+ context.lineTo(u, -t);
17
+ context.lineTo(-u, -t);
18
+ context.closePath();
19
+ },
20
+ };
21
+ export const getSymbol = (symbolType) => {
22
+ switch (symbolType) {
23
+ case SymbolType.Diamond:
24
+ return symbolDiamond2;
25
+ case SymbolType.Circle:
26
+ return symbolCircle;
27
+ case SymbolType.Square:
28
+ return symbolSquare;
29
+ case SymbolType.Triangle:
30
+ return symbolTriangle2;
31
+ case SymbolType.TriangleDown:
32
+ return triangleDown;
33
+ default:
34
+ return symbolCircle;
35
+ }
36
+ };
@@ -58,3 +58,11 @@ export type PathLegendSymbolOptions = BaseLegendSymbol & {
58
58
  * */
59
59
  width?: number;
60
60
  };
61
+ export type SymbolLegendSymbolOptions = BaseLegendSymbol & {
62
+ /**
63
+ * The pixel width of the symbol for series types that use a symbol in the legend
64
+ *
65
+ * @default 8
66
+ * */
67
+ width?: number;
68
+ };
@@ -1,4 +1,4 @@
1
- import { SeriesType } from '../../constants';
1
+ import { SeriesType, SymbolType } from '../../constants';
2
2
  import type { BaseSeries, BaseSeriesData } from './base';
3
3
  import type { ChartKitWidgetLegend, RectLegendSymbolOptions } from './legend';
4
4
  export type ScatterSeriesData<T = any> = BaseSeriesData<T> & {
@@ -32,7 +32,7 @@ export type ScatterSeries<T = any> = BaseSeries & {
32
32
  /** The main color of the series (hex, rgba) */
33
33
  color?: string;
34
34
  /** A predefined shape or symbol for the dot */
35
- symbol?: string;
35
+ symbolType?: `${SymbolType}`;
36
36
  /** Individual series legend options. Has higher priority than legend options in widget data */
37
37
  legend?: ChartKitWidgetLegend & {
38
38
  symbol?: RectLegendSymbolOptions;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/chartkit",
3
- "version": "4.15.0",
3
+ "version": "4.16.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.1.0",
51
+ "@gravity-ui/yagr": "^4.1.1",
52
52
  "afterframe": "^1.0.2",
53
53
  "d3": "^7.8.5",
54
54
  "lodash": "^4.17.21",
@@ -125,6 +125,7 @@
125
125
  "docs:start:ru": "cd ./documentation && npm run start:ru",
126
126
  "docs:build": "cd ./documentation && npm run build",
127
127
  "docs:serve": "cd ./documentation && npm run serve",
128
+ "docs:deploy": "cd ./documentation && npm run deploy",
128
129
  "prepublishOnly": "npm run build"
129
130
  },
130
131
  "husky": {