@gravity-ui/chartkit 3.5.0 → 4.0.0-beta.1

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.
Files changed (49) hide show
  1. package/build/plugins/d3/renderer/components/Chart.js +7 -11
  2. package/build/plugins/d3/renderer/components/Legend.d.ts +3 -2
  3. package/build/plugins/d3/renderer/components/Legend.js +42 -21
  4. package/build/plugins/d3/renderer/constants.d.ts +3 -0
  5. package/build/plugins/d3/renderer/constants.js +3 -0
  6. package/build/plugins/d3/renderer/hooks/index.d.ts +1 -1
  7. package/build/plugins/d3/renderer/hooks/index.js +1 -1
  8. package/build/plugins/d3/renderer/hooks/useAxisScales/index.d.ts +2 -2
  9. package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +2 -2
  10. package/build/plugins/d3/renderer/hooks/useChartDimensions/index.d.ts +2 -2
  11. package/build/plugins/d3/renderer/hooks/useChartDimensions/index.js +2 -4
  12. package/build/plugins/d3/renderer/hooks/useChartOptions/chart.js +5 -6
  13. package/build/plugins/d3/renderer/hooks/useChartOptions/legend.js +7 -2
  14. package/build/plugins/d3/renderer/hooks/useChartOptions/title.js +2 -2
  15. package/build/plugins/d3/renderer/hooks/useChartOptions/types.d.ts +3 -1
  16. package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +3 -3
  17. package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +3 -3
  18. package/build/plugins/d3/renderer/hooks/useSeries/constants.d.ts +1 -0
  19. package/build/plugins/d3/renderer/hooks/useSeries/constants.js +1 -0
  20. package/build/plugins/d3/renderer/hooks/useSeries/index.d.ts +10 -8
  21. package/build/plugins/d3/renderer/hooks/useSeries/index.js +44 -53
  22. package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.d.ts +9 -0
  23. package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.js +85 -0
  24. package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +29 -0
  25. package/build/plugins/d3/renderer/hooks/useSeries/types.js +1 -0
  26. package/build/plugins/d3/renderer/hooks/useSeries/utils.d.ts +3 -0
  27. package/build/plugins/d3/renderer/hooks/useSeries/utils.js +11 -0
  28. package/build/plugins/d3/renderer/hooks/useShapes/bar-x.js +2 -1
  29. package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +2 -2
  30. package/build/plugins/d3/renderer/hooks/useShapes/index.js +4 -1
  31. package/build/plugins/d3/renderer/hooks/useShapes/pie.d.ts +2 -2
  32. package/build/plugins/d3/renderer/hooks/useShapes/pie.js +87 -12
  33. package/build/plugins/d3/renderer/hooks/useShapes/scatter.d.ts +0 -1
  34. package/build/plugins/d3/renderer/hooks/useShapes/scatter.js +4 -2
  35. package/build/plugins/d3/renderer/hooks/useShapes/styles.css +6 -0
  36. package/build/plugins/d3/renderer/utils/index.d.ts +42 -6
  37. package/build/plugins/d3/renderer/utils/index.js +43 -6
  38. package/build/types/widget-data/bar-x.d.ts +5 -0
  39. package/build/types/widget-data/base.d.ts +12 -3
  40. package/build/types/widget-data/legend.d.ts +32 -0
  41. package/build/types/widget-data/pie.d.ts +23 -4
  42. package/build/types/widget-data/scatter.d.ts +5 -0
  43. package/package.json +1 -1
  44. package/build/plugins/d3/renderer/hooks/useChartOptions/constants.d.ts +0 -3
  45. package/build/plugins/d3/renderer/hooks/useChartOptions/constants.js +0 -3
  46. package/build/plugins/d3/renderer/hooks/useChartOptions/utils.d.ts +0 -5
  47. package/build/plugins/d3/renderer/hooks/useChartOptions/utils.js +0 -18
  48. package/build/plugins/d3/renderer/hooks/useLegend/index.d.ts +0 -13
  49. package/build/plugins/d3/renderer/hooks/useLegend/index.js +0 -59
@@ -5,19 +5,16 @@ import { AxisX } from './AxisX';
5
5
  import { Legend } from './Legend';
6
6
  import { Title } from './Title';
7
7
  import { Tooltip } from './Tooltip';
8
- import { useChartDimensions, useChartEvents, useChartOptions, useLegend, useAxisScales, useSeries, useShapes, useTooltip, } from '../hooks';
9
- import { isAxisRelatedSeries } from '../utils';
8
+ import { useChartDimensions, useChartEvents, useChartOptions, useAxisScales, useSeries, useShapes, useTooltip, } from '../hooks';
10
9
  import './styles.css';
11
10
  const b = block('d3');
12
11
  export const Chart = (props) => {
13
12
  const { top, left, width, height, data } = props;
14
13
  // FIXME: add data validation
15
- const { series } = data;
16
14
  const svgRef = React.createRef();
17
- const hasAxisRelatedSeries = series.data.some(isAxisRelatedSeries);
18
15
  const { chartHovered, handleMouseEnter, handleMouseLeave } = useChartEvents();
19
16
  const { chart, legend, title, tooltip, xAxis, yAxis } = useChartOptions(data);
20
- const { boundsWidth, boundsHeight, legendHeight } = useChartDimensions({
17
+ const { boundsWidth, boundsHeight } = useChartDimensions({
21
18
  width,
22
19
  height,
23
20
  margin: chart.margin,
@@ -26,12 +23,11 @@ export const Chart = (props) => {
26
23
  xAxis,
27
24
  yAxis,
28
25
  });
29
- const { activeLegendItems, handleLegendItemClick } = useLegend({ series: series.data });
30
- const { chartSeries } = useSeries({ activeLegendItems, series: series.data });
26
+ const { preparedSeries, handleLegendItemClick } = useSeries({ series: data.series, legend });
31
27
  const { xScale, yScale } = useAxisScales({
32
28
  boundsWidth,
33
29
  boundsHeight,
34
- series: chartSeries,
30
+ series: preparedSeries,
35
31
  xAxis,
36
32
  yAxis,
37
33
  });
@@ -43,7 +39,7 @@ export const Chart = (props) => {
43
39
  left,
44
40
  boundsWidth,
45
41
  boundsHeight,
46
- series: chartSeries,
42
+ series: preparedSeries,
47
43
  xAxis,
48
44
  xScale,
49
45
  yAxis,
@@ -59,11 +55,11 @@ export const Chart = (props) => {
59
55
  chart.margin.left,
60
56
  chart.margin.top + ((title === null || title === void 0 ? void 0 : title.height) || 0),
61
57
  ].join(',')})` },
62
- hasAxisRelatedSeries && xScale && yScale && (React.createElement(React.Fragment, null,
58
+ xScale && yScale && (React.createElement(React.Fragment, null,
63
59
  React.createElement(AxisY, { axises: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale }),
64
60
  React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
65
61
  React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale })))),
66
62
  shapes),
67
- legend.enabled && (React.createElement(Legend, { width: boundsWidth, offsetWidth: chart.margin.left, height: legendHeight, offsetHeight: height - legendHeight / 2, chartSeries: chartSeries, onItemClick: handleLegendItemClick }))),
63
+ legend.enabled && (React.createElement(Legend, { width: boundsWidth, offsetWidth: chart.margin.left, height: legend.height, legend: legend, offsetHeight: height - legend.height / 2, chartSeries: preparedSeries, onItemClick: handleLegendItemClick }))),
68
64
  React.createElement(Tooltip, { hovered: hovered, pointerPosition: pointerPosition, tooltip: tooltip, xAxis: xAxis, yAxis: yAxis[0] })));
69
65
  };
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
- import type { ChartSeries, OnLegendItemClick } from '../hooks';
2
+ import type { OnLegendItemClick, PreparedLegend, PreparedSeries } from '../hooks';
3
3
  type Props = {
4
4
  width: number;
5
5
  height: number;
6
+ legend: PreparedLegend;
6
7
  offsetWidth: number;
7
8
  offsetHeight: number;
8
- chartSeries: ChartSeries[];
9
+ chartSeries: PreparedSeries[];
9
10
  onItemClick: OnLegendItemClick;
10
11
  };
11
12
  export declare const Legend: (props: Props) => React.JSX.Element;
@@ -1,31 +1,36 @@
1
1
  import React from 'react';
2
- import { select } from 'd3';
2
+ import { select, sum } from 'd3';
3
3
  import get from 'lodash/get';
4
4
  import { block } from '../../../../utils/cn';
5
- import { isAxisRelatedSeries } from '../utils';
6
5
  const b = block('d3-legend');
7
6
  const getLegendItems = (series) => {
8
7
  return series.reduce((acc, s) => {
9
- const isAxisRelated = isAxisRelatedSeries(s);
10
8
  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);
9
+ if (legendEnabled) {
10
+ acc.push(Object.assign(Object.assign({}, s), { symbol: s.legend.symbol }));
16
11
  }
17
12
  return acc;
18
13
  }, []);
19
14
  };
15
+ function getLegendPosition(args) {
16
+ const { align, offsetWidth, width, contentWidth } = args;
17
+ const top = 0;
18
+ if (align === 'left') {
19
+ return { top, left: offsetWidth };
20
+ }
21
+ if (align === 'right') {
22
+ return { top, left: offsetWidth + width - contentWidth };
23
+ }
24
+ return { top, left: offsetWidth + width / 2 - contentWidth / 2 };
25
+ }
20
26
  export const Legend = (props) => {
21
- const { width, offsetWidth, height, offsetHeight, chartSeries, onItemClick } = props;
27
+ const { width, offsetWidth, height, offsetHeight, chartSeries, legend, onItemClick } = props;
22
28
  const ref = React.useRef(null);
23
29
  React.useEffect(() => {
24
30
  if (!ref.current) {
25
31
  return;
26
32
  }
27
33
  const legendItems = getLegendItems(chartSeries);
28
- const size = 10;
29
34
  const textWidths = [0];
30
35
  const svgElement = select(ref.current);
31
36
  svgElement.selectAll('*').remove();
@@ -51,24 +56,30 @@ export const Legend = (props) => {
51
56
  .remove();
52
57
  legendItemTemplate
53
58
  .append('rect')
54
- .attr('x', function (_d, i) {
55
- return (offsetWidth +
56
- i * size +
59
+ .attr('x', function (legendItem, i) {
60
+ return (i * legendItem.symbol.width +
61
+ i * legend.itemDistance +
62
+ i * legendItem.symbol.padding +
57
63
  textWidths.slice(0, i + 1).reduce((acc, tw) => acc + tw, 0));
58
64
  })
59
- .attr('y', offsetHeight - size / 2)
60
- .attr('width', size)
61
- .attr('height', size)
65
+ .attr('y', (legendItem) => offsetHeight - legendItem.symbol.height / 2)
66
+ .attr('width', (legendItem) => {
67
+ return legendItem.symbol.width;
68
+ })
69
+ .attr('height', (legendItem) => legendItem.symbol.height)
70
+ .attr('rx', (legendItem) => legendItem.symbol.radius)
62
71
  .attr('class', b('item-shape'))
63
72
  .style('fill', function (d) {
64
73
  return d.color;
65
74
  });
66
75
  legendItemTemplate
67
76
  .append('text')
68
- .attr('x', function (_d, i) {
69
- return (offsetWidth +
70
- i * size +
71
- size +
77
+ .attr('x', function (legendItem, i) {
78
+ return (i * legendItem.symbol.width +
79
+ i * legend.itemDistance +
80
+ i * legendItem.symbol.padding +
81
+ legendItem.symbol.width +
82
+ legendItem.symbol.padding +
72
83
  textWidths.slice(0, i + 1).reduce((acc, tw) => acc + tw, 0));
73
84
  })
74
85
  .attr('y', offsetHeight)
@@ -80,6 +91,16 @@ export const Legend = (props) => {
80
91
  return ('name' in d && d.name);
81
92
  })
82
93
  .style('alignment-baseline', 'middle');
83
- }, [width, offsetWidth, height, offsetHeight, chartSeries, onItemClick]);
94
+ const contentWidth = sum(textWidths) +
95
+ sum(legendItems, (item) => item.symbol.width + item.symbol.padding) +
96
+ legend.itemDistance * (legendItems.length - 1);
97
+ const { left } = getLegendPosition({
98
+ align: legend.align,
99
+ width,
100
+ offsetWidth,
101
+ contentWidth,
102
+ });
103
+ svgElement.attr('transform', `translate(${[left, 0].join(',')})`);
104
+ }, [width, offsetWidth, height, offsetHeight, chartSeries, onItemClick, legend]);
84
105
  return React.createElement("g", { ref: ref, width: width, height: height });
85
106
  };
@@ -1 +1,4 @@
1
1
  export declare const DEFAULT_PALETTE: string[];
2
+ export declare const DEFAULT_AXIS_LABEL_FONT_SIZE = "11px";
3
+ export declare const DEFAULT_AXIS_LABEL_PADDING = 10;
4
+ export declare const DEFAULT_AXIS_TITLE_FONT_SIZE = "14px";
@@ -20,3 +20,6 @@ export const DEFAULT_PALETTE = [
20
20
  '#FFB46C',
21
21
  '#DCA3D7',
22
22
  ];
23
+ export const DEFAULT_AXIS_LABEL_FONT_SIZE = '11px';
24
+ export const DEFAULT_AXIS_LABEL_PADDING = 10;
25
+ export const DEFAULT_AXIS_TITLE_FONT_SIZE = '14px';
@@ -2,9 +2,9 @@ export * from './useChartDimensions';
2
2
  export * from './useChartEvents';
3
3
  export * from './useChartOptions';
4
4
  export * from './useChartOptions/types';
5
- export * from './useLegend';
6
5
  export * from './useAxisScales';
7
6
  export * from './useSeries';
7
+ export * from './useSeries/types';
8
8
  export * from './useShapes';
9
9
  export * from './useTooltip';
10
10
  export * from './useTooltip/types';
@@ -2,9 +2,9 @@ export * from './useChartDimensions';
2
2
  export * from './useChartEvents';
3
3
  export * from './useChartOptions';
4
4
  export * from './useChartOptions/types';
5
- export * from './useLegend';
6
5
  export * from './useAxisScales';
7
6
  export * from './useSeries';
7
+ export * from './useSeries/types';
8
8
  export * from './useShapes';
9
9
  export * from './useTooltip';
10
10
  export * from './useTooltip/types';
@@ -1,11 +1,11 @@
1
1
  import type { ScaleBand, ScaleLinear, ScaleTime } from 'd3';
2
- import type { ChartKitWidgetSeries } from '../../../../../types/widget-data';
3
2
  import type { ChartOptions } from '../useChartOptions/types';
3
+ import { PreparedSeries } from '../useSeries/types';
4
4
  export type ChartScale = ScaleLinear<number, number> | ScaleBand<string> | ScaleTime<number, number>;
5
5
  type Args = {
6
6
  boundsWidth: number;
7
7
  boundsHeight: number;
8
- series: ChartKitWidgetSeries[];
8
+ series: PreparedSeries[];
9
9
  xAxis: ChartOptions['xAxis'];
10
10
  yAxis: ChartOptions['yAxis'];
11
11
  };
@@ -1,14 +1,14 @@
1
1
  import React from 'react';
2
2
  import { scaleBand, scaleLinear, scaleUtc, extent } from 'd3';
3
3
  import get from 'lodash/get';
4
- import { getOnlyVisibleSeries, getDomainDataXBySeries, getDomainDataYBySeries, isAxisRelatedSeries, } from '../../utils';
4
+ import { getOnlyVisibleSeries, getDomainDataYBySeries, isAxisRelatedSeries, getDomainDataXBySeries, isSeriesWithCategoryValues, } from '../../utils';
5
5
  const isNumericalArrayData = (data) => {
6
6
  return data.every((d) => typeof d === 'number' || d === null);
7
7
  };
8
8
  const filterCategoriesByVisibleSeries = (categories, series) => {
9
9
  return categories.filter((category) => {
10
10
  return series.some((s) => {
11
- return s.data.some((d) => 'category' in d && d.category === category);
11
+ return isSeriesWithCategoryValues(s) && s.data.some((d) => d.category === category);
12
12
  });
13
13
  });
14
14
  };
@@ -1,10 +1,10 @@
1
1
  import type { ChartMargin } from '../../../../../types/widget-data';
2
- import type { ChartOptions, PreparedAxis, PreparedTitle } from '../useChartOptions/types';
2
+ import type { PreparedAxis, PreparedLegend, PreparedTitle } from '../useChartOptions/types';
3
3
  type Args = {
4
4
  width: number;
5
5
  height: number;
6
6
  margin: ChartMargin;
7
- legend: ChartOptions['legend'];
7
+ legend: PreparedLegend;
8
8
  title?: PreparedTitle;
9
9
  xAxis?: PreparedAxis;
10
10
  yAxis?: PreparedAxis[];
@@ -1,13 +1,11 @@
1
- const LEGEND_LINE_HEIGHT = 15;
2
1
  export const useChartDimensions = (args) => {
3
2
  const { margin, legend, title, width, height, xAxis, yAxis } = args;
4
- const legendHeight = legend.enabled ? LEGEND_LINE_HEIGHT : 0;
5
3
  const titleHeight = (title === null || title === void 0 ? void 0 : title.height) || 0;
6
4
  const xAxisTitleHeight = (xAxis === null || xAxis === void 0 ? void 0 : xAxis.title.height) || 0;
7
5
  const yAxisTitleHeight = (yAxis === null || yAxis === void 0 ? void 0 : yAxis.reduce((acc, axis) => {
8
6
  return acc + (axis.title.height || 0);
9
7
  }, 0)) || 0;
10
8
  const boundsWidth = width - margin.right - margin.left - yAxisTitleHeight;
11
- const boundsHeight = height - margin.top - margin.bottom - legendHeight - titleHeight - xAxisTitleHeight;
12
- return { boundsWidth, boundsHeight, legendHeight };
9
+ const boundsHeight = height - margin.top - margin.bottom - legend.height - titleHeight - xAxisTitleHeight;
10
+ return { boundsWidth, boundsHeight, legendHeight: legend.height };
13
11
  };
@@ -1,7 +1,6 @@
1
1
  import { select, max } from 'd3';
2
2
  import get from 'lodash/get';
3
- import { formatAxisTickLabel, getDomainDataYBySeries, isAxisRelatedSeries } from '../../utils';
4
- import { getHorisontalSvgTextDimensions } from './utils';
3
+ import { formatAxisTickLabel, getDomainDataYBySeries, getHorisontalSvgTextHeight, isAxisRelatedSeries, } from '../../utils';
5
4
  const AXIS_WIDTH = 1;
6
5
  const getAxisLabelMaxWidth = (args) => {
7
6
  const { axis, series } = args;
@@ -9,8 +8,8 @@ const getAxisLabelMaxWidth = (args) => {
9
8
  let width = 0;
10
9
  switch (axis.type) {
11
10
  case 'category': {
12
- const yCatigories = get(axis, 'categories', []);
13
- maxDomainValue = [...yCatigories].sort((c1, c2) => c2.length - c1.length)[0];
11
+ const yCategories = get(axis, 'categories', []);
12
+ maxDomainValue = [...yCategories].sort((c1, c2) => c2.length - c1.length)[0];
14
13
  break;
15
14
  }
16
15
  case 'datetime': {
@@ -53,14 +52,14 @@ export const getPreparedChart = (args) => {
53
52
  if (hasAxisRelatedSeries) {
54
53
  marginBottom +=
55
54
  preparedXAxis.labels.padding +
56
- getHorisontalSvgTextDimensions({ text: 'Tmp', style: preparedXAxis.labels.style });
55
+ getHorisontalSvgTextHeight({ text: 'Tmp', style: preparedXAxis.labels.style });
57
56
  marginLeft +=
58
57
  AXIS_WIDTH +
59
58
  preparedY1Axis.labels.padding +
60
59
  getAxisLabelMaxWidth({ axis: preparedY1Axis, series: series.data }) +
61
60
  (preparedY1Axis.title.height || 0);
62
61
  marginTop +=
63
- getHorisontalSvgTextDimensions({ text: 'Tmp', style: preparedY1Axis.labels.style }) / 2;
62
+ getHorisontalSvgTextHeight({ text: 'Tmp', style: preparedY1Axis.labels.style }) / 2;
64
63
  marginRight += getAxisLabelMaxWidth({ axis: preparedXAxis, series: series.data }) / 2;
65
64
  }
66
65
  return {
@@ -1,7 +1,12 @@
1
+ const LEGEND_LINE_HEIGHT = 15;
1
2
  export const getPreparedLegend = (args) => {
2
3
  const { legend, series } = args;
3
- const enabled = legend === null || legend === void 0 ? void 0 : legend.enabled;
4
+ const enabled = typeof (legend === null || legend === void 0 ? void 0 : legend.enabled) === 'boolean' ? legend === null || legend === void 0 ? void 0 : legend.enabled : series.data.length > 1;
5
+ const height = enabled ? LEGEND_LINE_HEIGHT : 0;
4
6
  return {
5
- enabled: typeof enabled === 'boolean' ? enabled : series.data.length > 1,
7
+ align: (legend === null || legend === void 0 ? void 0 : legend.align) || 'center',
8
+ enabled,
9
+ itemDistance: (legend === null || legend === void 0 ? void 0 : legend.itemDistance) || 20,
10
+ height,
6
11
  };
7
12
  };
@@ -1,5 +1,5 @@
1
1
  import get from 'lodash/get';
2
- import { getHorisontalSvgTextDimensions } from './utils';
2
+ import { getHorisontalSvgTextHeight } from '../../utils';
3
3
  const DEFAULT_TITLE_FONT_SIZE = '15px';
4
4
  const TITLE_PADDINGS = 8 * 2;
5
5
  export const getPreparedTitle = ({ title, }) => {
@@ -8,7 +8,7 @@ export const getPreparedTitle = ({ title, }) => {
8
8
  fontSize: get(title, 'style.fontSize', DEFAULT_TITLE_FONT_SIZE),
9
9
  };
10
10
  const titleHeight = titleText
11
- ? getHorisontalSvgTextDimensions({ text: titleText, style: titleStyle }) + TITLE_PADDINGS
11
+ ? getHorisontalSvgTextHeight({ text: titleText, style: titleStyle }) + TITLE_PADDINGS
12
12
  : 0;
13
13
  const preparedTitle = titleText
14
14
  ? { text: titleText, style: titleStyle, height: titleHeight }
@@ -5,7 +5,9 @@ type PreparedAxisLabels = Omit<ChartKitWidgetAxisLabels, 'enabled' | 'padding' |
5
5
  export type PreparedChart = {
6
6
  margin: ChartMargin;
7
7
  };
8
- export type PreparedLegend = Required<ChartKitWidgetLegend>;
8
+ export type PreparedLegend = Required<ChartKitWidgetLegend> & {
9
+ height: number;
10
+ };
9
11
  export type PreparedAxis = Omit<ChartKitWidgetAxis, 'type' | 'labels'> & {
10
12
  type: ChartKitWidgetAxisType;
11
13
  labels: PreparedAxisLabels;
@@ -1,6 +1,6 @@
1
1
  import get from 'lodash/get';
2
- import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_LABEL_PADDING, DEFAULT_AXIS_TITLE_FONT_SIZE, } from './constants';
3
- import { getHorisontalSvgTextDimensions } from './utils';
2
+ import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_LABEL_PADDING, DEFAULT_AXIS_TITLE_FONT_SIZE, } from '../../constants';
3
+ import { getHorisontalSvgTextHeight } from '../../utils';
4
4
  export const getPreparedXAxis = ({ xAxis }) => {
5
5
  const titleText = get(xAxis, 'title.text', '');
6
6
  const titleStyle = {
@@ -21,7 +21,7 @@ export const getPreparedXAxis = ({ xAxis }) => {
21
21
  text: titleText,
22
22
  style: titleStyle,
23
23
  height: titleText
24
- ? getHorisontalSvgTextDimensions({ text: titleText, style: titleStyle })
24
+ ? getHorisontalSvgTextHeight({ text: titleText, style: titleStyle })
25
25
  : 0,
26
26
  },
27
27
  min: get(xAxis, 'min'),
@@ -1,6 +1,6 @@
1
1
  import get from 'lodash/get';
2
- import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_LABEL_PADDING, DEFAULT_AXIS_TITLE_FONT_SIZE, } from './constants';
3
- import { getHorisontalSvgTextDimensions } from './utils';
2
+ import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_LABEL_PADDING, DEFAULT_AXIS_TITLE_FONT_SIZE, } from '../../constants';
3
+ import { getHorisontalSvgTextHeight } from '../../utils';
4
4
  export const getPreparedYAxis = ({ yAxis }) => {
5
5
  // FIXME: add support for n axises
6
6
  const yAxis1 = yAxis === null || yAxis === void 0 ? void 0 : yAxis[0];
@@ -26,7 +26,7 @@ export const getPreparedYAxis = ({ yAxis }) => {
26
26
  text: y1TitleText,
27
27
  style: y1TitleStyle,
28
28
  height: y1TitleText
29
- ? getHorisontalSvgTextDimensions({ text: y1TitleText, style: y1TitleStyle })
29
+ ? getHorisontalSvgTextHeight({ text: y1TitleText, style: y1TitleStyle })
30
30
  : 0,
31
31
  },
32
32
  min: get(yAxis1, 'min'),
@@ -0,0 +1 @@
1
+ export declare const DEFAULT_LEGEND_SYMBOL_SIZE = 10;
@@ -0,0 +1 @@
1
+ export const DEFAULT_LEGEND_SYMBOL_SIZE = 10;
@@ -1,14 +1,16 @@
1
- import type { ChartKitWidgetSeries } from '../../../../../types/widget-data';
2
- export type ChartSeries = ChartKitWidgetSeries & {
3
- color: string;
1
+ import type { ChartKitWidgetData } from '../../../../../types/widget-data';
2
+ import { PreparedLegend } from '../useChartOptions/types';
3
+ import { PreparedSeries } from './types';
4
+ export type OnLegendItemClick = (data: {
4
5
  name: string;
5
- visible: boolean;
6
- };
6
+ metaKey: boolean;
7
+ }) => void;
7
8
  type Args = {
8
- activeLegendItems: string[];
9
- series: ChartKitWidgetSeries[];
9
+ legend: PreparedLegend;
10
+ series: ChartKitWidgetData['series'];
10
11
  };
11
12
  export declare const useSeries: (args: Args) => {
12
- chartSeries: ChartSeries[];
13
+ preparedSeries: PreparedSeries[];
14
+ handleLegendItemClick: OnLegendItemClick;
13
15
  };
14
16
  export {};
@@ -1,61 +1,52 @@
1
1
  import React from 'react';
2
- import cloneDeep from 'lodash/cloneDeep';
3
- import get from 'lodash/get';
4
2
  import { scaleOrdinal } from 'd3';
5
3
  import { DEFAULT_PALETTE } from '../../constants';
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
- };
4
+ import { getSeriesNames } from '../../utils';
5
+ import { getActiveLegendItems, getAllLegendItems } from './utils';
6
+ import { prepareSeries } from './prepareSeries';
46
7
  export const useSeries = (args) => {
47
- const { activeLegendItems, series } = args;
48
- const chartSeries = React.useMemo(() => {
8
+ const { series: { data: series }, legend, } = args;
9
+ const preparedSeries = React.useMemo(() => {
49
10
  const seriesNames = getSeriesNames(series);
50
11
  const colorScale = scaleOrdinal(seriesNames, DEFAULT_PALETTE);
51
- return series.map((s) => {
52
- return isAxisRelatedSeries(s)
53
- ? prepareAxisRelatedSeries({ activeLegendItems, colorScale, series: s })
54
- : prepareNotAxisRelatedSeries({
55
- activeLegendItems,
56
- series: s,
57
- });
12
+ return series.reduce((acc, singleSeries) => {
13
+ acc.push(...prepareSeries({
14
+ series: singleSeries,
15
+ legend,
16
+ colorScale,
17
+ }));
18
+ return acc;
19
+ }, []);
20
+ }, [series, legend]);
21
+ const [activeLegendItems, setActiveLegendItems] = React.useState(getActiveLegendItems(preparedSeries));
22
+ const chartSeries = React.useMemo(() => {
23
+ return preparedSeries.map((singleSeries) => {
24
+ if (singleSeries.legend.enabled) {
25
+ return Object.assign(Object.assign({}, singleSeries), { visible: activeLegendItems.includes(singleSeries.name) });
26
+ }
27
+ return singleSeries;
58
28
  });
59
- }, [activeLegendItems, series]);
60
- return { chartSeries };
29
+ }, [preparedSeries, activeLegendItems]);
30
+ // FIXME: remove effect. It initiates extra rerender
31
+ React.useEffect(() => {
32
+ setActiveLegendItems(getActiveLegendItems(preparedSeries));
33
+ }, [preparedSeries]);
34
+ const handleLegendItemClick = React.useCallback(({ name, metaKey }) => {
35
+ const onlyItemSelected = activeLegendItems.length === 1 && activeLegendItems.includes(name);
36
+ let nextActiveLegendItems;
37
+ if (metaKey && activeLegendItems.includes(name)) {
38
+ nextActiveLegendItems = activeLegendItems.filter((item) => item !== name);
39
+ }
40
+ else if (metaKey && !activeLegendItems.includes(name)) {
41
+ nextActiveLegendItems = activeLegendItems.concat(name);
42
+ }
43
+ else if (onlyItemSelected) {
44
+ nextActiveLegendItems = getAllLegendItems(preparedSeries);
45
+ }
46
+ else {
47
+ nextActiveLegendItems = [name];
48
+ }
49
+ setActiveLegendItems(nextActiveLegendItems);
50
+ }, [preparedSeries, activeLegendItems]);
51
+ return { preparedSeries: chartSeries, handleLegendItemClick };
61
52
  };
@@ -0,0 +1,9 @@
1
+ import type { ScaleOrdinal } from 'd3';
2
+ import type { ChartKitWidgetSeries } from '../../../../../types/widget-data';
3
+ import type { PreparedLegend } from '../useChartOptions/types';
4
+ import type { PreparedSeries } from './types';
5
+ export declare function prepareSeries(args: {
6
+ series: ChartKitWidgetSeries;
7
+ legend: PreparedLegend;
8
+ colorScale: ScaleOrdinal<string, string>;
9
+ }): PreparedSeries[];
@@ -0,0 +1,85 @@
1
+ import { scaleOrdinal } from 'd3';
2
+ import cloneDeep from 'lodash/cloneDeep';
3
+ import get from 'lodash/get';
4
+ import { DEFAULT_PALETTE } from '../../constants';
5
+ import { DEFAULT_LEGEND_SYMBOL_SIZE } from './constants';
6
+ import { getRandomCKId } from '../../../../../utils';
7
+ function prepareLegendSymbol(series) {
8
+ var _a;
9
+ switch (series.type) {
10
+ default: {
11
+ const symbolOptions = ((_a = series.legend) === null || _a === void 0 ? void 0 : _a.symbol) || {};
12
+ const symbolHeight = (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.height) || DEFAULT_LEGEND_SYMBOL_SIZE;
13
+ return {
14
+ shape: 'rect',
15
+ width: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.width) || DEFAULT_LEGEND_SYMBOL_SIZE,
16
+ height: symbolHeight,
17
+ radius: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.radius) || symbolHeight / 2,
18
+ padding: (symbolOptions === null || symbolOptions === void 0 ? void 0 : symbolOptions.padding) || 5,
19
+ };
20
+ }
21
+ }
22
+ }
23
+ function prepareAxisRelatedSeries(args) {
24
+ const { colorScale, series, legend } = args;
25
+ const preparedSeries = cloneDeep(series);
26
+ const name = 'name' in series && series.name ? series.name : '';
27
+ const color = 'color' in series && series.color ? series.color : colorScale(name);
28
+ preparedSeries.color = color;
29
+ preparedSeries.name = name;
30
+ preparedSeries.visible = get(preparedSeries, 'visible', true);
31
+ preparedSeries.legend = {
32
+ enabled: get(preparedSeries, 'legend.enabled', legend.enabled),
33
+ symbol: prepareLegendSymbol(series),
34
+ };
35
+ return [preparedSeries];
36
+ }
37
+ function preparePieSeries(args) {
38
+ const { series, legend } = args;
39
+ const dataNames = series.data.map((d) => d.name);
40
+ const colorScale = scaleOrdinal(dataNames, DEFAULT_PALETTE);
41
+ const stackId = getRandomCKId();
42
+ const preparedSeries = series.data.map((dataItem) => {
43
+ var _a, _b;
44
+ const result = {
45
+ type: 'pie',
46
+ data: dataItem.value,
47
+ dataLabels: {
48
+ enabled: get(series, 'dataLabels.enabled', true),
49
+ },
50
+ label: dataItem.label,
51
+ visible: typeof dataItem.visible === 'boolean' ? dataItem.visible : true,
52
+ name: dataItem.name,
53
+ color: dataItem.color || colorScale(dataItem.name),
54
+ legend: {
55
+ enabled: get(series, 'legend.enabled', legend.enabled),
56
+ symbol: prepareLegendSymbol(series),
57
+ },
58
+ center: series.center || ['50%', '50%'],
59
+ borderColor: series.borderColor || '',
60
+ borderRadius: (_a = series.borderRadius) !== null && _a !== void 0 ? _a : 0,
61
+ borderWidth: (_b = series.borderWidth) !== null && _b !== void 0 ? _b : 1,
62
+ radius: series.radius || '100%',
63
+ innerRadius: series.innerRadius || 0,
64
+ stackId,
65
+ };
66
+ return result;
67
+ });
68
+ return preparedSeries;
69
+ }
70
+ export function prepareSeries(args) {
71
+ const { series, legend, colorScale } = args;
72
+ switch (series.type) {
73
+ case 'pie': {
74
+ return preparePieSeries({ series, legend });
75
+ }
76
+ case 'scatter':
77
+ case 'bar-x': {
78
+ return prepareAxisRelatedSeries({ series, legend, colorScale });
79
+ }
80
+ default: {
81
+ const seriesType = get(series, 'type');
82
+ throw new Error(`Series type ${seriesType} does not support data preparation for series that do not support the presence of axes`);
83
+ }
84
+ }
85
+ }