@gravity-ui/chartkit 4.0.0-beta.6 → 4.0.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/index.d.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { ChartKit } from './components/ChartKit';
2
2
  import { settings } from './libs';
3
+ export * from './types/widget-data';
4
+ export * from './libs/chartkit-error/chartkit-error';
3
5
  export type { ChartKitLang, ChartKitOnLoadData, ChartKitOnRenderData, ChartKitOnChartLoad, ChartKitOnError, ChartKitPlugin, ChartKitProps, ChartKitRef, ChartKitWidgetRef, ChartKitType, ChartKitWidget, } from './types';
4
6
  export { settings };
5
7
  export default ChartKit;
package/build/index.js CHANGED
@@ -1,4 +1,6 @@
1
1
  import { ChartKit } from './components/ChartKit';
2
2
  import { settings } from './libs';
3
+ export * from './types/widget-data';
4
+ export * from './libs/chartkit-error/chartkit-error';
3
5
  export { settings };
4
6
  export default ChartKit;
@@ -1,5 +1,5 @@
1
1
  import React from 'react';
2
- import { scaleOrdinal } from 'd3';
2
+ import { group, scaleOrdinal } from 'd3';
3
3
  import { DEFAULT_PALETTE } from '../../constants';
4
4
  import { getSeriesNames } from '../../utils';
5
5
  import { getActiveLegendItems, getAllLegendItems } from './utils';
@@ -9,9 +9,11 @@ export const useSeries = (args) => {
9
9
  const preparedSeries = React.useMemo(() => {
10
10
  const seriesNames = getSeriesNames(series);
11
11
  const colorScale = scaleOrdinal(seriesNames, DEFAULT_PALETTE);
12
- return series.reduce((acc, singleSeries) => {
12
+ const groupedSeries = group(series, (item) => item.type);
13
+ return Array.from(groupedSeries).reduce((acc, [seriesType, seriesList]) => {
13
14
  acc.push(...prepareSeries({
14
- series: singleSeries,
15
+ type: seriesType,
16
+ series: seriesList,
15
17
  legend,
16
18
  colorScale,
17
19
  }));
@@ -3,7 +3,8 @@ import type { ChartKitWidgetSeries } from '../../../../../types/widget-data';
3
3
  import type { PreparedLegend } from '../useChartOptions/types';
4
4
  import type { PreparedSeries } from './types';
5
5
  export declare function prepareSeries(args: {
6
- series: ChartKitWidgetSeries;
6
+ type: ChartKitWidgetSeries['type'];
7
+ series: ChartKitWidgetSeries[];
7
8
  legend: PreparedLegend;
8
9
  colorScale: ScaleOrdinal<string, string>;
9
10
  }): PreparedSeries[];
@@ -4,6 +4,10 @@ import get from 'lodash/get';
4
4
  import { DEFAULT_PALETTE } from '../../constants';
5
5
  import { DEFAULT_LEGEND_SYMBOL_SIZE } from './constants';
6
6
  import { getRandomCKId } from '../../../../../utils';
7
+ const DEFAULT_DATALABELS_STYLE = {
8
+ fontSize: '11px',
9
+ fontWeight: 'bold',
10
+ };
7
11
  function prepareLegendSymbol(series) {
8
12
  var _a;
9
13
  switch (series.type) {
@@ -34,6 +38,35 @@ function prepareAxisRelatedSeries(args) {
34
38
  };
35
39
  return [preparedSeries];
36
40
  }
41
+ function prepareBarXSeries(args) {
42
+ const { colorScale, series, legend } = args;
43
+ const commonStackId = getRandomCKId();
44
+ return series.map((singleSeries) => {
45
+ var _a, _b, _c, _d;
46
+ const name = singleSeries.name || '';
47
+ const color = singleSeries.color || colorScale(name);
48
+ return {
49
+ type: singleSeries.type,
50
+ color: color,
51
+ name: name,
52
+ visible: get(singleSeries, 'visible', true),
53
+ legend: {
54
+ enabled: get(singleSeries, 'legend.enabled', legend.enabled),
55
+ symbol: prepareLegendSymbol(singleSeries),
56
+ },
57
+ data: singleSeries.data,
58
+ stacking: singleSeries.stacking,
59
+ stackId: singleSeries.stacking === 'normal' ? commonStackId : getRandomCKId(),
60
+ dataLabels: {
61
+ enabled: ((_a = singleSeries.dataLabels) === null || _a === void 0 ? void 0 : _a.enabled) || false,
62
+ inside: typeof ((_b = singleSeries.dataLabels) === null || _b === void 0 ? void 0 : _b.inside) === 'boolean'
63
+ ? (_c = singleSeries.dataLabels) === null || _c === void 0 ? void 0 : _c.inside
64
+ : false,
65
+ style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_d = singleSeries.dataLabels) === null || _d === void 0 ? void 0 : _d.style),
66
+ },
67
+ };
68
+ }, []);
69
+ }
37
70
  function preparePieSeries(args) {
38
71
  const { series, legend } = args;
39
72
  const dataNames = series.data.map((d) => d.name);
@@ -68,14 +101,22 @@ function preparePieSeries(args) {
68
101
  return preparedSeries;
69
102
  }
70
103
  export function prepareSeries(args) {
71
- const { series, legend, colorScale } = args;
72
- switch (series.type) {
104
+ const { type, series, legend, colorScale } = args;
105
+ switch (type) {
73
106
  case 'pie': {
74
- return preparePieSeries({ series, legend });
107
+ return series.reduce((acc, singleSeries) => {
108
+ acc.push(...preparePieSeries({ series: singleSeries, legend }));
109
+ return acc;
110
+ }, []);
75
111
  }
76
- case 'scatter':
77
112
  case 'bar-x': {
78
- return prepareAxisRelatedSeries({ series, legend, colorScale });
113
+ return prepareBarXSeries({ series: series, legend, colorScale });
114
+ }
115
+ case 'scatter': {
116
+ return series.reduce((acc, singleSeries) => {
117
+ acc.push(...prepareAxisRelatedSeries({ series: singleSeries, legend, colorScale }));
118
+ return acc;
119
+ }, []);
79
120
  }
80
121
  default: {
81
122
  const seriesType = get(series, 'type');
@@ -1,4 +1,4 @@
1
- import { BarXSeries, BarXSeriesData, PieSeries, PieSeriesData, RectLegendSymbolOptions, ScatterSeries, ScatterSeriesData } from '../../../../../types/widget-data';
1
+ import { BarXSeries, BarXSeriesData, BaseTextStyle, PieSeries, PieSeriesData, RectLegendSymbolOptions, ScatterSeries, ScatterSeriesData } from '../../../../../types/widget-data';
2
2
  export type RectLegendSymbol = {
3
3
  shape: 'rect';
4
4
  } & Required<RectLegendSymbolOptions>;
@@ -19,6 +19,12 @@ export type PreparedScatterSeries = {
19
19
  export type PreparedBarXSeries = {
20
20
  type: BarXSeries['type'];
21
21
  data: BarXSeriesData[];
22
+ stackId: string;
23
+ dataLabels: {
24
+ enabled: boolean;
25
+ inside: boolean;
26
+ style: BaseTextStyle;
27
+ };
22
28
  } & BasePreparedSeries;
23
29
  export type PreparedPieSeries = BasePreparedSeries & Required<Omit<PieSeries, 'data'>> & {
24
30
  data: PieSeriesData['value'];
@@ -2,11 +2,11 @@ import React from 'react';
2
2
  import { ChartOptions } from '../useChartOptions/types';
3
3
  import { ChartScale } from '../useAxisScales';
4
4
  import { OnSeriesMouseLeave, OnSeriesMouseMove } from '../useTooltip/types';
5
- import { BarXSeries } from '../../../../../types/widget-data';
5
+ import { PreparedBarXSeries } from '../useSeries/types';
6
6
  type Args = {
7
7
  top: number;
8
8
  left: number;
9
- series: BarXSeries[];
9
+ series: PreparedBarXSeries[];
10
10
  xAxis: ChartOptions['xAxis'];
11
11
  xScale: ChartScale;
12
12
  yAxis: ChartOptions['yAxis'];
@@ -15,5 +15,5 @@ type Args = {
15
15
  onSeriesMouseLeave?: OnSeriesMouseLeave;
16
16
  svgContainer: SVGSVGElement | null;
17
17
  };
18
- export declare function prepareBarXSeries(args: Args): React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
18
+ export declare function BarXSeriesShapes(args: Args): React.JSX.Element;
19
19
  export {};
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
2
  import { block } from '../../../../../utils/cn';
3
- import { pointer } from 'd3';
4
- import { getRandomCKId } from '../../../../../utils';
3
+ import { group, pointer, select } from 'd3';
5
4
  const DEFAULT_BAR_RECT_WIDTH = 50;
6
5
  const DEFAULT_LINEAR_BAR_RECT_WIDTH = 20;
7
6
  const MIN_RECT_GAP = 1;
8
- const b = block('d3-bar');
7
+ const DEFAULT_LABEL_PADDING = 7;
8
+ const b = block('d3-bar-x');
9
9
  const getRectProperties = (args) => {
10
10
  const { point, xAxis, xScale, yAxis, yScale, minPointDistance } = args;
11
11
  let cx;
@@ -48,32 +48,103 @@ function minDiff(arr) {
48
48
  }
49
49
  return result;
50
50
  }
51
- export function prepareBarXSeries(args) {
51
+ export function BarXSeriesShapes(args) {
52
52
  const { top, left, series, xAxis, xScale, yAxis, yScale, onSeriesMouseMove, onSeriesMouseLeave, svgContainer, } = args;
53
- const seriesData = series.map(({ data }) => data).flat(2);
54
- const minPointDistance = minDiff(seriesData.map((item) => Number(item.x)));
55
- return series.reduce((result, item) => {
56
- const randomKey = getRandomCKId();
57
- item.data.forEach((point, i) => {
58
- const rectProps = getRectProperties({
59
- point,
60
- xAxis,
61
- xScale,
62
- yAxis,
63
- yScale,
64
- minPointDistance,
65
- });
66
- result.push(React.createElement("rect", Object.assign({ key: `${i}-${randomKey}`, className: b('rect'), fill: item.color }, rectProps, { onMouseMove: function (e) {
53
+ const ref = React.useRef(null);
54
+ React.useEffect(() => {
55
+ if (!ref.current) {
56
+ return;
57
+ }
58
+ const svgElement = select(ref.current);
59
+ svgElement.selectAll('*').remove();
60
+ const xValues = xAxis.type === 'category'
61
+ ? []
62
+ : series.reduce((acc, { data }) => {
63
+ data.forEach((dataItem) => acc.push(Number(dataItem.x)));
64
+ return acc;
65
+ }, []);
66
+ const minPointDistance = minDiff(xValues);
67
+ const stackedSeriesMap = group(series, (item) => item.stackId);
68
+ Array.from(stackedSeriesMap).forEach(([, stackedSeries]) => {
69
+ const stackHeights = {};
70
+ stackedSeries.forEach((item) => {
71
+ const shapes = item.data.map((dataItem) => {
72
+ const rectProps = getRectProperties({
73
+ point: dataItem,
74
+ xAxis,
75
+ xScale,
76
+ yAxis,
77
+ yScale,
78
+ minPointDistance,
79
+ });
80
+ if (!stackHeights[rectProps.x]) {
81
+ stackHeights[rectProps.x] = 0;
82
+ }
83
+ const rectY = rectProps.y - stackHeights[rectProps.x];
84
+ stackHeights[rectProps.x] += rectProps.height + 1;
85
+ return Object.assign(Object.assign({}, rectProps), { y: rectY, data: dataItem });
86
+ });
87
+ svgElement
88
+ .selectAll('allRects')
89
+ .data(shapes)
90
+ .join('rect')
91
+ .attr('class', b('segment'))
92
+ .attr('x', (d) => d.x)
93
+ .attr('y', (d) => d.y)
94
+ .attr('height', (d) => d.height)
95
+ .attr('width', (d) => d.width)
96
+ .attr('fill', item.color)
97
+ .on('mousemove', (e, point) => {
67
98
  const [x, y] = pointer(e, svgContainer);
68
99
  onSeriesMouseMove === null || onSeriesMouseMove === void 0 ? void 0 : onSeriesMouseMove({
69
100
  hovered: {
70
- data: point,
101
+ data: point.data,
71
102
  series: item,
72
103
  },
73
104
  pointerPosition: [x - left, y - top],
74
105
  });
75
- }, onMouseLeave: onSeriesMouseLeave })));
106
+ })
107
+ .on('mouseleave', () => {
108
+ if (onSeriesMouseLeave) {
109
+ onSeriesMouseLeave();
110
+ }
111
+ });
112
+ if (item.dataLabels.enabled) {
113
+ const selection = svgElement
114
+ .selectAll('allLabels')
115
+ .data(shapes)
116
+ .join('text')
117
+ .text((d) => String(d.data.label || d.data.y))
118
+ .attr('class', b('label'))
119
+ .attr('x', (d) => d.x + d.width / 2)
120
+ .attr('y', (d) => {
121
+ if (item.dataLabels.inside) {
122
+ return d.y + d.height / 2;
123
+ }
124
+ return d.y - DEFAULT_LABEL_PADDING;
125
+ })
126
+ .attr('text-anchor', 'middle')
127
+ .style('font-size', item.dataLabels.style.fontSize);
128
+ if (item.dataLabels.style.fontWeight) {
129
+ selection.style('font-weight', item.dataLabels.style.fontWeight);
130
+ }
131
+ if (item.dataLabels.style.fontColor) {
132
+ selection.style('fill', item.dataLabels.style.fontColor);
133
+ }
134
+ }
135
+ });
76
136
  });
77
- return result;
78
- }, []);
137
+ }, [
138
+ onSeriesMouseMove,
139
+ onSeriesMouseLeave,
140
+ svgContainer,
141
+ xAxis,
142
+ xScale,
143
+ yAxis,
144
+ yScale,
145
+ series,
146
+ left,
147
+ top,
148
+ ]);
149
+ return React.createElement("g", { ref: ref, className: b() });
79
150
  }
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { group } from 'd3';
3
3
  import { getOnlyVisibleSeries } from '../../utils';
4
- import { prepareBarXSeries } from './bar-x';
4
+ import { BarXSeriesShapes } from './bar-x';
5
5
  import { prepareScatterSeries } from './scatter';
6
6
  import { PieSeriesComponent } from './pie';
7
7
  import './styles.css';
@@ -15,18 +15,7 @@ export const useShapes = (args) => {
15
15
  switch (seriesType) {
16
16
  case 'bar-x': {
17
17
  if (xScale && yScale) {
18
- acc.push(...prepareBarXSeries({
19
- top,
20
- left,
21
- series: chartSeries,
22
- xAxis,
23
- xScale,
24
- yAxis,
25
- yScale,
26
- onSeriesMouseMove,
27
- onSeriesMouseLeave,
28
- svgContainer,
29
- }));
18
+ acc.push(React.createElement(BarXSeriesShapes, Object.assign({}, args, { key: "bar-x", series: chartSeries, xScale: xScale, yScale: yScale })));
30
19
  }
31
20
  break;
32
21
  }
@@ -19,4 +19,8 @@
19
19
  fill: var(--g-color-text-complementary);
20
20
  font-size: 11px;
21
21
  font-weight: bold;
22
+ }
23
+
24
+ .chartkit-d3-bar-x__label {
25
+ fill: var(--g-color-text-complementary);
22
26
  }
@@ -1,4 +1,4 @@
1
- import { select } from 'd3';
1
+ import { group, select } from 'd3';
2
2
  import get from 'lodash/get';
3
3
  import { dateTime } from '@gravity-ui/date-utils';
4
4
  import { formatNumber } from '../../../shared';
@@ -32,8 +32,35 @@ export const getDomainDataXBySeries = (series) => {
32
32
  }, []);
33
33
  };
34
34
  export const getDomainDataYBySeries = (series) => {
35
- return series.filter(isSeriesWithNumericalYValues).reduce((acc, s) => {
36
- acc.push(...s.data.map((d) => d.y));
35
+ const groupedSeries = group(series, (item) => item.type);
36
+ return Array.from(groupedSeries).reduce((acc, [type, seriesList]) => {
37
+ switch (type) {
38
+ case 'bar-x': {
39
+ const barXSeries = seriesList;
40
+ const stackedSeries = group(barXSeries, (item) => item.stackId);
41
+ Array.from(stackedSeries).forEach(([, stack]) => {
42
+ const values = {};
43
+ stack.forEach((singleSeries) => {
44
+ singleSeries.data.forEach((point) => {
45
+ const key = String(point.x || point.category);
46
+ if (typeof values[key] === 'undefined') {
47
+ values[key] = 0;
48
+ }
49
+ if (point.y) {
50
+ values[key] += point.y;
51
+ }
52
+ });
53
+ });
54
+ acc.push(...Object.values(values));
55
+ });
56
+ break;
57
+ }
58
+ default: {
59
+ seriesList.filter(isSeriesWithNumericalYValues).forEach((s) => {
60
+ acc.push(...s.data.map((d) => d.y));
61
+ });
62
+ }
63
+ }
37
64
  return acc;
38
65
  }, []);
39
66
  };
@@ -1,3 +1,4 @@
1
1
  import type { ChartKitPlugin } from '../../types';
2
2
  export * from './types';
3
+ export { HighchartsReact } from './renderer/components/HighchartsReact';
3
4
  export declare const HighchartsPlugin: ChartKitPlugin;
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  export * from './types';
3
+ export { HighchartsReact } from './renderer/components/HighchartsReact';
3
4
  export const HighchartsPlugin = {
4
5
  type: 'highcharts',
5
6
  renderer: React.lazy(() => import('./renderer/HighchartsWidget')),
@@ -8,6 +8,8 @@ export type BarXSeriesData<T = any> = BaseSeriesData<T> & {
8
8
  y?: number;
9
9
  /** Corresponding value of axis category */
10
10
  category?: string;
11
+ /** Data label value of the bar-x column. If not specified, the y value is used. */
12
+ label?: string | number;
11
13
  };
12
14
  export type BarXSeries<T = any> = BaseSeries & {
13
15
  type: 'bar-x';
@@ -16,6 +18,11 @@ export type BarXSeries<T = any> = BaseSeries & {
16
18
  name: string;
17
19
  /** The main color of the series (hex, rgba) */
18
20
  color?: string;
21
+ /** Whether to stack the values of each series on top of each other.
22
+ * Possible values are undefined to disable, "normal" to stack by value or "percent"
23
+ *
24
+ * @default undefined
25
+ * */
19
26
  stacking?: 'normal' | 'percent';
20
27
  /** This option allows grouping series in a stacked chart */
21
28
  stackId?: string;
@@ -26,7 +33,11 @@ export type BarXSeries<T = any> = BaseSeries & {
26
33
  * */
27
34
  grouping?: boolean;
28
35
  dataLabels?: ChartKitWidgetSeriesOptions['dataLabels'] & {
29
- /** Whether to align the data label inside the box or to the actual value point */
36
+ /**
37
+ * Whether to align the data label inside or outside the box
38
+ *
39
+ * @default false
40
+ * */
30
41
  inside?: boolean;
31
42
  };
32
43
  /** Individual series legend options. Has higher priority than legend options in widget data */
@@ -12,6 +12,7 @@ export type BaseSeries = {
12
12
  * @default true
13
13
  */
14
14
  enabled?: boolean;
15
+ style?: Partial<BaseTextStyle>;
15
16
  };
16
17
  };
17
18
  export type BaseSeriesData<T = any> = {
@@ -24,4 +25,6 @@ export type BaseSeriesData<T = any> = {
24
25
  };
25
26
  export type BaseTextStyle = {
26
27
  fontSize: string;
28
+ fontWeight?: string;
29
+ fontColor?: string;
27
30
  };
package/package.json CHANGED
@@ -1,34 +1,34 @@
1
1
  {
2
2
  "name": "@gravity-ui/chartkit",
3
- "version": "4.0.0-beta.6",
3
+ "version": "4.0.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",
7
- "main": "index.js",
8
- "types": "index.d.ts",
7
+ "main": "build/index.js",
8
+ "types": "build/index.d.ts",
9
9
  "exports": {
10
- ".": "./index.js",
11
- "./d3": "./plugins/d3/index.js",
12
- "./highcharts": "./plugins/highcharts/index.js",
13
- "./indicator": "./plugins/indicator/index.js",
14
- "./yagr": "./plugins/yagr/index.js"
10
+ ".": "./build/index.js",
11
+ "./d3": "./build/plugins/d3/index.js",
12
+ "./highcharts": "./build/plugins/highcharts/index.js",
13
+ "./indicator": "./build/plugins/indicator/index.js",
14
+ "./yagr": "./build/plugins/yagr/index.js"
15
15
  },
16
16
  "typesVersions": {
17
17
  "*": {
18
18
  "index.d.ts": [
19
- "./index.d.ts"
19
+ "./build/index.d.ts"
20
20
  ],
21
21
  "d3": [
22
- "./plugins/d3/index.d.ts"
22
+ "./build/plugins/d3/index.d.ts"
23
23
  ],
24
24
  "highcharts": [
25
- "./plugins/highcharts/index.d.ts"
25
+ "./build/plugins/highcharts/index.d.ts"
26
26
  ],
27
27
  "indicator": [
28
- "./plugins/indicator/index.d.ts"
28
+ "./build/plugins/indicator/index.d.ts"
29
29
  ],
30
30
  "yagr": [
31
- "./plugins/yagr/index.d.ts"
31
+ "./build/plugins/yagr/index.d.ts"
32
32
  ]
33
33
  }
34
34
  },
@@ -41,6 +41,10 @@
41
41
  "publishConfig": {
42
42
  "access": "public"
43
43
  },
44
+ "sideEffects": [
45
+ "*.css",
46
+ "*.scss"
47
+ ],
44
48
  "dependencies": {
45
49
  "@bem-react/classname": "^1.6.0",
46
50
  "@gravity-ui/date-utils": "^1.4.1",