@gravity-ui/chartkit 5.7.0 → 5.8.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.
Files changed (40) hide show
  1. package/build/plugins/d3/renderer/components/AxisX.d.ts +2 -1
  2. package/build/plugins/d3/renderer/components/AxisX.js +13 -3
  3. package/build/plugins/d3/renderer/components/AxisY.d.ts +4 -3
  4. package/build/plugins/d3/renderer/components/AxisY.js +17 -8
  5. package/build/plugins/d3/renderer/components/Chart.js +14 -3
  6. package/build/plugins/d3/renderer/components/PlotTitle.d.ts +7 -0
  7. package/build/plugins/d3/renderer/components/PlotTitle.js +12 -0
  8. package/build/plugins/d3/renderer/components/styles.css +7 -1
  9. package/build/plugins/d3/renderer/hooks/index.d.ts +1 -0
  10. package/build/plugins/d3/renderer/hooks/index.js +1 -0
  11. package/build/plugins/d3/renderer/hooks/useAxisScales/index.d.ts +3 -1
  12. package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +21 -11
  13. package/build/plugins/d3/renderer/hooks/useChartDimensions/utils.js +13 -2
  14. package/build/plugins/d3/renderer/hooks/useChartOptions/types.d.ts +1 -0
  15. package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.d.ts +2 -2
  16. package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +14 -2
  17. package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.d.ts +2 -2
  18. package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +15 -6
  19. package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-y.js +2 -1
  20. package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +2 -1
  21. package/build/plugins/d3/renderer/hooks/useShapes/index.js +2 -1
  22. package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.d.ts +6 -4
  23. package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.js +6 -3
  24. package/build/plugins/d3/renderer/hooks/useSplit/index.d.ts +14 -0
  25. package/build/plugins/d3/renderer/hooks/useSplit/index.js +57 -0
  26. package/build/plugins/d3/renderer/hooks/useSplit/types.d.ts +17 -0
  27. package/build/plugins/d3/renderer/hooks/useSplit/types.js +1 -0
  28. package/build/plugins/d3/renderer/utils/axis-generators/bottom.d.ts +1 -1
  29. package/build/plugins/d3/renderer/utils/axis-generators/bottom.js +16 -8
  30. package/build/plugins/d3/renderer/utils/axis.d.ts +6 -2
  31. package/build/plugins/d3/renderer/utils/axis.js +7 -0
  32. package/build/plugins/d3/renderer/utils/index.d.ts +3 -2
  33. package/build/plugins/d3/renderer/utils/index.js +41 -26
  34. package/build/types/widget-data/axis.d.ts +10 -0
  35. package/build/types/widget-data/bar-y.d.ts +2 -1
  36. package/build/types/widget-data/index.d.ts +8 -3
  37. package/build/types/widget-data/index.js +1 -0
  38. package/build/types/widget-data/split.d.ts +13 -0
  39. package/build/types/widget-data/split.js +1 -0
  40. package/package.json +1 -1
@@ -1,10 +1,11 @@
1
1
  import React from 'react';
2
- import type { ChartScale, PreparedAxis } from '../hooks';
2
+ import type { ChartScale, PreparedAxis, PreparedSplit } from '../hooks';
3
3
  type Props = {
4
4
  axis: PreparedAxis;
5
5
  width: number;
6
6
  height: number;
7
7
  scale: ChartScale;
8
+ split: PreparedSplit;
8
9
  };
9
10
  export declare const AxisX: React.NamedExoticComponent<Props>;
10
11
  export {};
@@ -18,16 +18,26 @@ function getLabelFormatter({ axis, scale }) {
18
18
  });
19
19
  };
20
20
  }
21
- export const AxisX = React.memo(function AxisX({ axis, width, height, scale }) {
21
+ export const AxisX = React.memo(function AxisX(props) {
22
+ const { axis, width, height: totalHeight, scale, split } = props;
22
23
  const ref = React.useRef(null);
23
24
  React.useEffect(() => {
24
25
  if (!ref.current) {
25
26
  return;
26
27
  }
28
+ let tickItems = [];
29
+ if (axis.grid.enabled) {
30
+ tickItems = new Array(split.plots.length || 1).fill(null).map((_, index) => {
31
+ var _a, _b;
32
+ const top = ((_a = split.plots[index]) === null || _a === void 0 ? void 0 : _a.top) || 0;
33
+ const height = ((_b = split.plots[index]) === null || _b === void 0 ? void 0 : _b.height) || totalHeight;
34
+ return [-top, -(top + height)];
35
+ });
36
+ }
27
37
  const xAxisGenerator = axisBottom({
28
38
  scale: scale,
29
39
  ticks: {
30
- size: axis.grid.enabled ? height * -1 : 0,
40
+ items: tickItems,
31
41
  labelFormat: getLabelFormatter({ axis, scale }),
32
42
  labelsPaddings: axis.labels.padding,
33
43
  labelsMargin: axis.labels.margin,
@@ -59,6 +69,6 @@ export const AxisX = React.memo(function AxisX({ axis, width, height, scale }) {
59
69
  .text(axis.title.text)
60
70
  .call(setEllipsisForOverflowText, width);
61
71
  }
62
- }, [axis, width, height, scale]);
72
+ }, [axis, width, totalHeight, scale, split]);
63
73
  return React.createElement("g", { ref: ref });
64
74
  });
@@ -1,10 +1,11 @@
1
1
  import React from 'react';
2
- import type { ChartScale, PreparedAxis } from '../hooks';
2
+ import type { ChartScale, PreparedAxis, PreparedSplit } from '../hooks';
3
3
  type Props = {
4
- axises: PreparedAxis[];
4
+ axes: PreparedAxis[];
5
5
  scale: ChartScale[];
6
6
  width: number;
7
7
  height: number;
8
+ split: PreparedSplit;
8
9
  };
9
- export declare const AxisY: ({ axises, width, height, scale }: Props) => React.JSX.Element;
10
+ export declare const AxisY: (props: Props) => React.JSX.Element;
10
11
  export {};
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { axisLeft, axisRight, line, select } from 'd3';
3
3
  import { block } from '../../../../utils/cn';
4
- import { calculateCos, calculateSin, formatAxisTickLabel, getClosestPointsRange, getScaleTicks, getTicksCount, parseTransformStyle, setEllipsisForOverflowText, setEllipsisForOverflowTexts, } from '../utils';
4
+ import { calculateCos, calculateSin, formatAxisTickLabel, getAxisHeight, getClosestPointsRange, getScaleTicks, getTicksCount, parseTransformStyle, setEllipsisForOverflowText, setEllipsisForOverflowTexts, } from '../utils';
5
5
  const b = block('d3-axis');
6
6
  function transformLabel(args) {
7
7
  const { node, axis } = args;
@@ -51,7 +51,9 @@ function getAxisGenerator(args) {
51
51
  }
52
52
  return axisGenerator;
53
53
  }
54
- export const AxisY = ({ axises, width, height, scale }) => {
54
+ export const AxisY = (props) => {
55
+ const { axes, width, height: totalHeight, scale, split } = props;
56
+ const height = getAxisHeight({ split, boundsHeight: totalHeight });
55
57
  const ref = React.useRef(null);
56
58
  React.useEffect(() => {
57
59
  if (!ref.current) {
@@ -61,15 +63,22 @@ export const AxisY = ({ axises, width, height, scale }) => {
61
63
  svgElement.selectAll('*').remove();
62
64
  const axisSelection = svgElement
63
65
  .selectAll('axis')
64
- .data(axises)
66
+ .data(axes)
65
67
  .join('g')
66
68
  .attr('class', b())
67
- .style('transform', (_d, index) => (index === 0 ? '' : `translate(${width}px, 0)`));
69
+ .style('transform', (d) => {
70
+ var _a;
71
+ const top = ((_a = split.plots[d.plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
72
+ if (d.position === 'left') {
73
+ return `translate(0, ${top}px)`;
74
+ }
75
+ return `translate(${width}px, 0)`;
76
+ });
68
77
  axisSelection.each((d, index, node) => {
69
78
  const seriesScale = scale[index];
70
79
  const axisItem = select(node[index]);
71
80
  const yAxisGenerator = getAxisGenerator({
72
- axisGenerator: index === 0
81
+ axisGenerator: d.position === 'left'
73
82
  ? axisLeft(seriesScale)
74
83
  : axisRight(seriesScale),
75
84
  preparedAxis: d,
@@ -136,13 +145,13 @@ export const AxisY = ({ axises, width, height, scale }) => {
136
145
  .attr('class', b('title'))
137
146
  .attr('text-anchor', 'middle')
138
147
  .attr('dy', (d) => -(d.title.margin + d.labels.margin + d.labels.width))
139
- .attr('dx', (_d, index) => (index === 0 ? -height / 2 : height / 2))
148
+ .attr('dx', (d) => (d.position === 'left' ? -height / 2 : height / 2))
140
149
  .attr('font-size', (d) => d.title.style.fontSize)
141
- .attr('transform', (_d, index) => (index === 0 ? 'rotate(-90)' : 'rotate(90)'))
150
+ .attr('transform', (d) => (d.position === 'left' ? 'rotate(-90)' : 'rotate(90)'))
142
151
  .text((d) => d.title.text)
143
152
  .each((_d, index, node) => {
144
153
  return setEllipsisForOverflowText(select(node[index]), height);
145
154
  });
146
- }, [axises, width, height, scale]);
155
+ }, [axes, width, height, scale, split]);
147
156
  return React.createElement("g", { ref: ref, className: b('container') });
148
157
  };
@@ -7,10 +7,12 @@ import { useAxisScales, useChartDimensions, useChartOptions, useSeries, useShape
7
7
  import { getYAxisWidth } from '../hooks/useChartDimensions/utils';
8
8
  import { getPreparedXAxis } from '../hooks/useChartOptions/x-axis';
9
9
  import { getPreparedYAxis } from '../hooks/useChartOptions/y-axis';
10
+ import { useSplit } from '../hooks/useSplit';
10
11
  import { getClosestPoints } from '../utils/get-closest-data';
11
12
  import { AxisX } from './AxisX';
12
13
  import { AxisY } from './AxisY';
13
14
  import { Legend } from './Legend';
15
+ import { PlotTitle } from './PlotTitle';
14
16
  import { Title } from './Title';
15
17
  import { Tooltip } from './Tooltip';
16
18
  import './styles.css';
@@ -27,7 +29,10 @@ export const Chart = (props) => {
27
29
  data,
28
30
  });
29
31
  const xAxis = React.useMemo(() => getPreparedXAxis({ xAxis: data.xAxis, width, series: data.series.data }), [data, width]);
30
- const yAxis = React.useMemo(() => getPreparedYAxis({ series: data.series.data, yAxis: data.yAxis }), [data, width]);
32
+ const yAxis = React.useMemo(() => getPreparedYAxis({
33
+ series: data.series.data,
34
+ yAxis: data.yAxis,
35
+ }), [data]);
31
36
  const { legendItems, legendConfig, preparedSeries, preparedSeriesOptions, preparedLegend, handleLegendItemClick, } = useSeries({
32
37
  chartWidth: width,
33
38
  chartHeight: height,
@@ -45,12 +50,14 @@ export const Chart = (props) => {
45
50
  preparedYAxis: yAxis,
46
51
  preparedSeries: preparedSeries,
47
52
  });
53
+ const preparedSplit = useSplit({ split: data.split, boundsHeight, chartWidth: width });
48
54
  const { xScale, yScale } = useAxisScales({
49
55
  boundsWidth,
50
56
  boundsHeight,
51
57
  series: preparedSeries,
52
58
  xAxis,
53
59
  yAxis,
60
+ split: preparedSplit,
54
61
  });
55
62
  const { shapes, shapesData } = useShapes({
56
63
  boundsWidth,
@@ -62,6 +69,7 @@ export const Chart = (props) => {
62
69
  xScale,
63
70
  yAxis,
64
71
  yScale,
72
+ split: preparedSplit,
65
73
  });
66
74
  const clickHandler = (_b = (_a = data.chart) === null || _a === void 0 ? void 0 : _a.events) === null || _b === void 0 ? void 0 : _b.click;
67
75
  React.useEffect(() => {
@@ -97,11 +105,14 @@ export const Chart = (props) => {
97
105
  return (React.createElement(React.Fragment, null,
98
106
  React.createElement("svg", { ref: svgRef, className: b(), width: width, height: height, onMouseMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave },
99
107
  title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
108
+ React.createElement("g", { transform: `translate(0, ${boundsOffsetTop})` }, preparedSplit.plots.map((plot, index) => {
109
+ return React.createElement(PlotTitle, { key: `plot-${index}`, title: plot.title });
110
+ })),
100
111
  React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[boundsOffsetLeft, boundsOffsetTop].join(',')})` },
101
112
  xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length) && (React.createElement(React.Fragment, null,
102
- React.createElement(AxisY, { axises: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale }),
113
+ React.createElement(AxisY, { axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit }),
103
114
  React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
104
- React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale })))),
115
+ React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit })))),
105
116
  shapes),
106
117
  preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick }))),
107
118
  React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0] })));
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import type { PreparedPlotTitle } from '../hooks/useSplit/types';
3
+ type Props = {
4
+ title?: PreparedPlotTitle;
5
+ };
6
+ export declare const PlotTitle: (props: Props) => React.JSX.Element | null;
7
+ export {};
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { block } from '../../../../utils/cn';
3
+ const b = block('d3-plot-title');
4
+ export const PlotTitle = (props) => {
5
+ const { title } = props;
6
+ if (!title) {
7
+ return null;
8
+ }
9
+ const { x, y, text, style, height } = title;
10
+ return (React.createElement("text", { className: b(), dx: x, dy: y, dominantBaseline: "middle", textAnchor: "middle", style: Object.assign({ lineHeight: `${height}px` }, style) },
11
+ React.createElement("tspan", null, text)));
12
+ };
@@ -11,7 +11,7 @@
11
11
  alignment-baseline: after-edge;
12
12
  }
13
13
 
14
- .chartkit-d3-axis .tick line {
14
+ .chartkit-d3-axis .tick line, .chartkit-d3-axis .tick path {
15
15
  stroke: var(--g-color-line-generic);
16
16
  }
17
17
 
@@ -79,6 +79,12 @@
79
79
  fill: var(--g-color-text-primary);
80
80
  }
81
81
 
82
+ .chartkit-d3-plot-title {
83
+ font-size: var(--g-text-subheader-3-font-size);
84
+ font-weight: var(--g-text-subheader-font-weight);
85
+ fill: var(--g-color-text-secondary);
86
+ }
87
+
82
88
  .chartkit-d3-tooltip[class] {
83
89
  --g-popup-border-width: 0;
84
90
  pointer-events: none;
@@ -7,3 +7,4 @@ export * from './useSeries/types';
7
7
  export * from './useShapes';
8
8
  export * from './useTooltip';
9
9
  export * from './useTooltip/types';
10
+ export * from './useSplit/types';
@@ -7,3 +7,4 @@ export * from './useSeries/types';
7
7
  export * from './useShapes';
8
8
  export * from './useTooltip';
9
9
  export * from './useTooltip/types';
10
+ export * from './useSplit/types';
@@ -1,7 +1,8 @@
1
1
  import type { ScaleBand, ScaleLinear, ScaleTime } from 'd3';
2
2
  import { ChartKitWidgetAxis, ChartKitWidgetSeries } from '../../../../../types';
3
3
  import type { PreparedAxis } from '../useChartOptions/types';
4
- import { PreparedSeries } from '../useSeries/types';
4
+ import type { PreparedSeries } from '../useSeries/types';
5
+ import type { PreparedSplit } from '../useSplit/types';
5
6
  export type ChartScale = ScaleLinear<number, number> | ScaleBand<string> | ScaleTime<number, number>;
6
7
  type Args = {
7
8
  boundsWidth: number;
@@ -9,6 +10,7 @@ type Args = {
9
10
  series: PreparedSeries[];
10
11
  xAxis: PreparedAxis;
11
12
  yAxis: PreparedAxis[];
13
+ split: PreparedSplit;
12
14
  };
13
15
  type ReturnValue = {
14
16
  xScale?: ChartScale;
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { extent, scaleBand, scaleLinear, scaleUtc } from 'd3';
3
3
  import get from 'lodash/get';
4
4
  import { DEFAULT_AXIS_TYPE } from '../../constants';
5
- import { CHART_SERIES_WITH_VOLUME, getDataCategoryValue, getDefaultMaxXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
5
+ import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
6
6
  const isNumericalArrayData = (data) => {
7
7
  return data.every((d) => typeof d === 'number' || d === null);
8
8
  };
@@ -31,7 +31,7 @@ export function createYScale(axis, series, boundsHeight) {
31
31
  const [domainYMin, domainMax] = extent(domain);
32
32
  const yMinValue = typeof yMin === 'number' ? yMin : domainYMin;
33
33
  let yMaxValue = domainMax;
34
- if (series.some((s) => CHART_SERIES_WITH_VOLUME.includes(s.type))) {
34
+ if (series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type))) {
35
35
  yMaxValue = Math.max(yMaxValue, 0);
36
36
  }
37
37
  return scaleLinear().domain([yMinValue, yMaxValue]).range(range).nice();
@@ -73,8 +73,11 @@ function calculateXAxisPadding(series) {
73
73
  switch (s.type) {
74
74
  case 'bar-y': {
75
75
  // Since labels can be located to the right of the bar, need to add an additional space
76
- const labelsMaxWidth = get(s, 'dataLabels.maxWidth', 0);
77
- result = Math.max(result, labelsMaxWidth);
76
+ const inside = get(s, 'dataLabels.inside');
77
+ if (!inside) {
78
+ const labelsMaxWidth = get(s, 'dataLabels.maxWidth', 0);
79
+ result = Math.max(result, labelsMaxWidth);
80
+ }
78
81
  break;
79
82
  }
80
83
  }
@@ -134,7 +137,7 @@ export function createXScale(axis, series, boundsWidth) {
134
137
  throw new Error('Failed to create xScale');
135
138
  }
136
139
  const createScales = (args) => {
137
- const { boundsWidth, boundsHeight, series, xAxis, yAxis } = args;
140
+ const { boundsWidth, boundsHeight, series, xAxis, yAxis, split } = args;
138
141
  let visibleSeries = getOnlyVisibleSeries(series);
139
142
  // Reassign to all series in case of all series unselected,
140
143
  // otherwise we will get an empty space without grid
@@ -147,7 +150,8 @@ const createScales = (args) => {
147
150
  return seriesAxisIndex === index;
148
151
  });
149
152
  const visibleAxisSeries = getOnlyVisibleSeries(axisSeries);
150
- return createYScale(axis, visibleAxisSeries.length ? visibleAxisSeries : axisSeries, boundsHeight);
153
+ const axisHeight = getAxisHeight({ boundsHeight, split });
154
+ return createYScale(axis, visibleAxisSeries.length ? visibleAxisSeries : axisSeries, axisHeight);
151
155
  }),
152
156
  };
153
157
  };
@@ -155,15 +159,21 @@ const createScales = (args) => {
155
159
  * Uses to create scales for axis related series
156
160
  */
157
161
  export const useAxisScales = (args) => {
158
- const { boundsWidth, boundsHeight, series, xAxis, yAxis } = args;
159
- const scales = React.useMemo(() => {
162
+ const { boundsWidth, boundsHeight, series, xAxis, yAxis, split } = args;
163
+ return React.useMemo(() => {
160
164
  let xScale;
161
165
  let yScale;
162
166
  const hasAxisRelatedSeries = series.some(isAxisRelatedSeries);
163
167
  if (hasAxisRelatedSeries) {
164
- ({ xScale, yScale } = createScales({ boundsWidth, boundsHeight, series, xAxis, yAxis }));
168
+ ({ xScale, yScale } = createScales({
169
+ boundsWidth,
170
+ boundsHeight,
171
+ series,
172
+ xAxis,
173
+ yAxis,
174
+ split,
175
+ }));
165
176
  }
166
177
  return { xScale, yScale };
167
- }, [boundsWidth, boundsHeight, series, xAxis, yAxis]);
168
- return scales;
178
+ }, [boundsWidth, boundsHeight, series, xAxis, yAxis, split]);
169
179
  };
@@ -16,6 +16,17 @@ export function getYAxisWidth(axis) {
16
16
  return result;
17
17
  }
18
18
  export function getWidthOccupiedByYAxis(args) {
19
- const { preparedAxis = [] } = args;
20
- return preparedAxis.reduce((sum, axis) => sum + getYAxisWidth(axis), 0);
19
+ const { preparedAxis } = args;
20
+ let leftAxisWidth = 0;
21
+ let rightAxisWidth = 0;
22
+ preparedAxis === null || preparedAxis === void 0 ? void 0 : preparedAxis.forEach((axis) => {
23
+ const axisWidth = getYAxisWidth(axis);
24
+ if (axis.position === 'right') {
25
+ rightAxisWidth = Math.max(rightAxisWidth, axisWidth);
26
+ }
27
+ else {
28
+ leftAxisWidth = Math.max(leftAxisWidth, axisWidth);
29
+ }
30
+ });
31
+ return leftAxisWidth + rightAxisWidth;
21
32
  }
@@ -28,6 +28,7 @@ export type PreparedAxis = Omit<ChartKitWidgetAxis, 'type' | 'labels'> & {
28
28
  pixelInterval?: number;
29
29
  };
30
30
  position: 'left' | 'right' | 'top' | 'bottom';
31
+ plotIndex: number;
31
32
  };
32
33
  export type PreparedTitle = ChartKitWidgetData['title'] & {
33
34
  height: number;
@@ -1,7 +1,7 @@
1
- import type { ChartKitWidgetAxis, ChartKitWidgetSeries } from '../../../../../types';
1
+ import type { ChartKitWidgetSeries, ChartKitWidgetXAxis } from '../../../../../types';
2
2
  import type { PreparedAxis } from './types';
3
3
  export declare const getPreparedXAxis: ({ xAxis, series, width, }: {
4
- xAxis?: ChartKitWidgetAxis | undefined;
4
+ xAxis?: import("../../../../../types").ChartKitWidgetAxis | undefined;
5
5
  series: ChartKitWidgetSeries[];
6
6
  width: number;
7
7
  }) => PreparedAxis;
@@ -1,6 +1,6 @@
1
1
  import get from 'lodash/get';
2
2
  import { DEFAULT_AXIS_LABEL_FONT_SIZE, axisLabelsDefaults, xAxisTitleDefaults, } from '../../constants';
3
- import { calculateCos, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, getXAxisItems, hasOverlappingLabels, } from '../../utils';
3
+ import { CHART_SERIES_WITH_VOLUME_ON_X_AXIS, calculateCos, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, getXAxisItems, hasOverlappingLabels, } from '../../utils';
4
4
  import { createXScale } from '../useAxisScales';
5
5
  function getLabelSettings({ axis, series, width, autoRotation = true, }) {
6
6
  const scale = createXScale(axis, series, width);
@@ -36,6 +36,17 @@ function getLabelSettings({ axis, series, width, autoRotation = true, }) {
36
36
  const maxHeight = rotation ? calculateCos(rotation) * axis.labels.maxWidth : labelsHeight;
37
37
  return { height: Math.min(maxHeight, labelsHeight), rotation };
38
38
  }
39
+ function getAxisMin(axis, series) {
40
+ const min = axis === null || axis === void 0 ? void 0 : axis.min;
41
+ if (typeof min === 'undefined' &&
42
+ (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME_ON_X_AXIS.includes(s.type)))) {
43
+ return series.reduce((minValue, s) => {
44
+ const minYValue = s.data.reduce((res, d) => Math.min(res, get(d, 'x', 0)), 0);
45
+ return Math.min(minValue, minYValue);
46
+ }, 0);
47
+ }
48
+ return min;
49
+ }
39
50
  export const getPreparedXAxis = ({ xAxis, series, width, }) => {
40
51
  var _a;
41
52
  const titleText = get(xAxis, 'title.text', '');
@@ -71,7 +82,7 @@ export const getPreparedXAxis = ({ xAxis, series, width, }) => {
71
82
  ? getHorisontalSvgTextHeight({ text: titleText, style: titleStyle })
72
83
  : 0,
73
84
  },
74
- min: get(xAxis, 'min'),
85
+ min: getAxisMin(xAxis, series),
75
86
  maxPadding: get(xAxis, 'maxPadding', 0.01),
76
87
  grid: {
77
88
  enabled: get(xAxis, 'grid.enabled', true),
@@ -80,6 +91,7 @@ export const getPreparedXAxis = ({ xAxis, series, width, }) => {
80
91
  pixelInterval: get(xAxis, 'ticks.pixelInterval'),
81
92
  },
82
93
  position: 'bottom',
94
+ plotIndex: 0,
83
95
  };
84
96
  const { height, rotation } = getLabelSettings({
85
97
  axis: preparedXAxis,
@@ -1,6 +1,6 @@
1
- import type { ChartKitWidgetData, ChartKitWidgetSeries } from '../../../../../types';
1
+ import type { ChartKitWidgetSeries, ChartKitWidgetYAxis } from '../../../../../types';
2
2
  import type { PreparedAxis } from './types';
3
3
  export declare const getPreparedYAxis: ({ series, yAxis, }: {
4
4
  series: ChartKitWidgetSeries[];
5
- yAxis: ChartKitWidgetData['yAxis'];
5
+ yAxis: ChartKitWidgetYAxis[] | undefined;
6
6
  }) => PreparedAxis[];
@@ -1,6 +1,6 @@
1
1
  import get from 'lodash/get';
2
2
  import { DEFAULT_AXIS_LABEL_FONT_SIZE, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
3
- import { CHART_SERIES_WITH_VOLUME, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, } from '../../utils';
3
+ import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, } from '../../utils';
4
4
  import { createYScale } from '../useAxisScales';
5
5
  const getAxisLabelMaxWidth = (args) => {
6
6
  const { axis, series } = args;
@@ -25,7 +25,7 @@ const getAxisLabelMaxWidth = (args) => {
25
25
  function getAxisMin(axis, series) {
26
26
  const min = axis === null || axis === void 0 ? void 0 : axis.min;
27
27
  if (typeof min === 'undefined' &&
28
- (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME.includes(s.type)))) {
28
+ (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type)))) {
29
29
  return series.reduce((minValue, s) => {
30
30
  switch (s.type) {
31
31
  case 'waterfall': {
@@ -42,8 +42,16 @@ function getAxisMin(axis, series) {
42
42
  return min;
43
43
  }
44
44
  export const getPreparedYAxis = ({ series, yAxis, }) => {
45
- return (yAxis || [{}]).map((axisItem, index) => {
46
- const axisPosition = index === 0 ? 'left' : 'right';
45
+ const axisByPlot = [];
46
+ const axisItems = yAxis || [{}];
47
+ return axisItems.map((axisItem) => {
48
+ const plotIndex = get(axisItem, 'plotIndex', 0);
49
+ const firstPlotAxis = !axisByPlot[plotIndex];
50
+ if (firstPlotAxis) {
51
+ axisByPlot[plotIndex] = [];
52
+ }
53
+ axisByPlot[plotIndex].push(axisItem);
54
+ const defaultAxisPosition = firstPlotAxis ? 'left' : 'right';
47
55
  const labelsEnabled = get(axisItem, 'labels.enabled', true);
48
56
  const labelsStyle = {
49
57
  fontSize: get(axisItem, 'labels.style.fontSize', DEFAULT_AXIS_LABEL_FONT_SIZE),
@@ -86,12 +94,13 @@ export const getPreparedYAxis = ({ series, yAxis, }) => {
86
94
  min: getAxisMin(axisItem, series),
87
95
  maxPadding: get(axisItem, 'maxPadding', 0.05),
88
96
  grid: {
89
- enabled: get(axisItem, 'grid.enabled', index === 0),
97
+ enabled: get(axisItem, 'grid.enabled', firstPlotAxis),
90
98
  },
91
99
  ticks: {
92
100
  pixelInterval: get(axisItem, 'ticks.pixelInterval'),
93
101
  },
94
- position: axisPosition,
102
+ position: get(axisItem, 'position', defaultAxisPosition),
103
+ plotIndex: get(axisItem, 'plotIndex', 0),
95
104
  };
96
105
  if (labelsEnabled) {
97
106
  preparedAxis.labels.width = getAxisLabelMaxWidth({ axis: preparedAxis, series });
@@ -13,9 +13,10 @@ function prepareDataLabels(series) {
13
13
  style,
14
14
  })
15
15
  : {};
16
+ const inside = series.stacking === 'percent' ? true : get(series, 'dataLabels.inside', false);
16
17
  return {
17
18
  enabled,
18
- inside: get(series, 'dataLabels.inside', false),
19
+ inside,
19
20
  style,
20
21
  maxHeight,
21
22
  maxWidth,
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Dispatch } from 'd3';
3
- import type { PreparedSeries, PreparedSeriesOptions } from '../';
3
+ import type { PreparedSeries, PreparedSeriesOptions, PreparedSplit } from '../';
4
4
  import type { ChartScale } from '../useAxisScales';
5
5
  import type { PreparedAxis } from '../useChartOptions/types';
6
6
  import type { PreparedAreaData } from './area/types';
@@ -24,6 +24,7 @@ type Args = {
24
24
  yAxis: PreparedAxis[];
25
25
  xScale?: ChartScale;
26
26
  yScale?: ChartScale[];
27
+ split: PreparedSplit;
27
28
  };
28
29
  export declare const useShapes: (args: Args) => {
29
30
  shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
@@ -15,7 +15,7 @@ import { prepareTreemapData } from './treemap/prepare-data';
15
15
  import { WaterfallSeriesShapes, prepareWaterfallData } from './waterfall';
16
16
  import './styles.css';
17
17
  export const useShapes = (args) => {
18
- const { boundsWidth, boundsHeight, dispatcher, series, seriesOptions, xAxis, xScale, yAxis, yScale, } = args;
18
+ const { boundsWidth, boundsHeight, dispatcher, series, seriesOptions, xAxis, xScale, yAxis, yScale, split, } = args;
19
19
  const shapesComponents = React.useMemo(() => {
20
20
  const visibleSeries = getOnlyVisibleSeries(series);
21
21
  const groupedSeries = group(visibleSeries, (item) => item.type);
@@ -77,6 +77,7 @@ export const useShapes = (args) => {
77
77
  xScale,
78
78
  yAxis,
79
79
  yScale,
80
+ split,
80
81
  });
81
82
  acc.push(React.createElement(LineSeriesShapes, { key: "line", dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData }));
82
83
  shapesData.push(...preparedData);
@@ -1,11 +1,13 @@
1
- import { ChartScale } from '../../useAxisScales';
2
- import { PreparedAxis } from '../../useChartOptions/types';
3
- import { PreparedLineSeries } from '../../useSeries/types';
4
- import { PreparedLineData } from './types';
1
+ import type { ChartScale } from '../../useAxisScales';
2
+ import type { PreparedAxis } from '../../useChartOptions/types';
3
+ import type { PreparedLineSeries } from '../../useSeries/types';
4
+ import type { PreparedSplit } from '../../useSplit/types';
5
+ import type { PreparedLineData } from './types';
5
6
  export declare const prepareLineData: (args: {
6
7
  series: PreparedLineSeries[];
7
8
  xAxis: PreparedAxis;
8
9
  xScale: ChartScale;
9
10
  yAxis: PreparedAxis[];
10
11
  yScale: ChartScale[];
12
+ split: PreparedSplit;
11
13
  }) => PreparedLineData[];
@@ -27,15 +27,18 @@ function getLabelData(point, series, xMax) {
27
27
  return labelData;
28
28
  }
29
29
  export const prepareLineData = (args) => {
30
- const { series, xAxis, xScale, yScale } = args;
31
- const yAxis = args.yAxis[0];
30
+ const { series, xAxis, yAxis, xScale, yScale, split } = args;
32
31
  const [_xMin, xRangeMax] = xScale.range();
33
32
  const xMax = xRangeMax / (1 - xAxis.maxPadding);
34
33
  return series.reduce((acc, s) => {
34
+ var _a;
35
+ const yAxisIndex = s.yAxis;
36
+ const seriesYAxis = yAxis[yAxisIndex];
37
+ const yAxisTop = ((_a = split.plots[seriesYAxis.plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
35
38
  const seriesYScale = yScale[s.yAxis];
36
39
  const points = s.data.map((d) => ({
37
40
  x: getXValue({ point: d, xAxis, xScale }),
38
- y: getYValue({ point: d, yAxis, yScale: seriesYScale }),
41
+ y: yAxisTop + getYValue({ point: d, yAxis: seriesYAxis, yScale: seriesYScale }),
39
42
  active: true,
40
43
  data: d,
41
44
  series: s,
@@ -0,0 +1,14 @@
1
+ import type { ChartKitWidgetSplit } from '../../../../../types';
2
+ import type { PreparedSplit } from './types';
3
+ type UseSplitArgs = {
4
+ split?: ChartKitWidgetSplit;
5
+ boundsHeight: number;
6
+ chartWidth: number;
7
+ };
8
+ export declare function getPlotHeight(args: {
9
+ split: ChartKitWidgetSplit | undefined;
10
+ boundsHeight: number;
11
+ gap: number;
12
+ }): number;
13
+ export declare const useSplit: (args: UseSplitArgs) => PreparedSplit;
14
+ export {};
@@ -0,0 +1,57 @@
1
+ import get from 'lodash/get';
2
+ import { calculateNumericProperty, getHorisontalSvgTextHeight } from '../../utils';
3
+ const DEFAULT_TITLE_FONT_SIZE = '15px';
4
+ const TITLE_TOP_BOTTOM_PADDING = 8;
5
+ function preparePlotTitle(args) {
6
+ const { title, plotIndex, plotHeight, chartWidth, gap } = args;
7
+ const titleText = (title === null || title === void 0 ? void 0 : title.text) || '';
8
+ const titleStyle = {
9
+ fontSize: get(title, 'style.fontSize', DEFAULT_TITLE_FONT_SIZE),
10
+ fontWeight: get(title, 'style.fontWeight'),
11
+ };
12
+ const titleHeight = titleText
13
+ ? getHorisontalSvgTextHeight({ text: titleText, style: titleStyle }) +
14
+ TITLE_TOP_BOTTOM_PADDING * 2
15
+ : 0;
16
+ const top = plotIndex * (plotHeight + gap);
17
+ return {
18
+ text: titleText,
19
+ x: chartWidth / 2,
20
+ y: top + titleHeight / 2,
21
+ style: titleStyle,
22
+ height: titleHeight,
23
+ };
24
+ }
25
+ export function getPlotHeight(args) {
26
+ const { split, boundsHeight, gap } = args;
27
+ const plots = (split === null || split === void 0 ? void 0 : split.plots) || [];
28
+ if (plots.length > 1) {
29
+ return (boundsHeight - gap * (plots.length - 1)) / plots.length;
30
+ }
31
+ return boundsHeight;
32
+ }
33
+ export const useSplit = (args) => {
34
+ var _a;
35
+ const { split, boundsHeight, chartWidth } = args;
36
+ const splitGap = (_a = calculateNumericProperty({ value: split === null || split === void 0 ? void 0 : split.gap, base: boundsHeight })) !== null && _a !== void 0 ? _a : 0;
37
+ const plotHeight = getPlotHeight({ split: split, boundsHeight, gap: splitGap });
38
+ const plots = (split === null || split === void 0 ? void 0 : split.plots) || [];
39
+ return {
40
+ plots: plots.map((p, index) => {
41
+ const title = preparePlotTitle({
42
+ title: p.title,
43
+ plotIndex: index,
44
+ gap: splitGap,
45
+ plotHeight,
46
+ chartWidth,
47
+ });
48
+ const top = index * (plotHeight + splitGap);
49
+ return {
50
+ top: top + title.height,
51
+ height: plotHeight - title.height,
52
+ title,
53
+ };
54
+ }),
55
+ gap: splitGap,
56
+ };
57
+ };
@@ -0,0 +1,17 @@
1
+ import type { BaseTextStyle } from '../../../../../types';
2
+ export type PreparedSplit = {
3
+ plots: PreparedPlot[];
4
+ gap: number;
5
+ };
6
+ export type PreparedPlot = {
7
+ title: PreparedPlotTitle;
8
+ top: number;
9
+ height: number;
10
+ };
11
+ export type PreparedPlotTitle = {
12
+ x: number;
13
+ y: number;
14
+ text: string;
15
+ style?: Partial<BaseTextStyle>;
16
+ height: number;
17
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -11,7 +11,7 @@ type AxisBottomArgs = {
11
11
  labelsStyle?: BaseTextStyle;
12
12
  labelsMaxWidth?: number;
13
13
  labelsLineHeight: number;
14
- size: number;
14
+ items: [number, number][];
15
15
  rotation: number;
16
16
  };
17
17
  domain: {
@@ -1,20 +1,22 @@
1
- import { select } from 'd3';
1
+ import { path, select } from 'd3';
2
2
  import { getXAxisItems, getXAxisOffset, getXTickPosition } from '../axis';
3
3
  import { calculateCos, calculateSin } from '../math';
4
4
  import { getLabelsSize, setEllipsisForOverflowText } from '../text';
5
5
  function addDomain(selection, options) {
6
6
  const { size, color } = options;
7
- selection
7
+ const domainPath = selection
8
8
  .selectAll('.domain')
9
9
  .data([null])
10
10
  .enter()
11
11
  .insert('path', '.tick')
12
12
  .attr('class', 'domain')
13
- .attr('stroke', color || 'currentColor')
14
13
  .attr('d', `M0,0V0H${size}`);
14
+ if (color) {
15
+ domainPath.style('stroke', color);
16
+ }
15
17
  }
16
18
  export function axisBottom(args) {
17
- const { scale, ticks: { labelFormat, labelsPaddings = 0, labelsMargin = 0, labelsMaxWidth = Infinity, labelsStyle, labelsLineHeight, size: tickSize, count: ticksCount, maxTickCount, rotation, }, domain: { size: domainSize, color: domainColor }, } = args;
19
+ const { scale, ticks: { labelFormat, labelsPaddings = 0, labelsMargin = 0, labelsMaxWidth = Infinity, labelsStyle, labelsLineHeight, items: tickItems, count: ticksCount, maxTickCount, rotation, }, domain: { size: domainSize, color: domainColor }, } = args;
18
20
  const offset = getXAxisOffset();
19
21
  const position = getXTickPosition({ scale, offset });
20
22
  const values = getXAxisItems({ scale, count: ticksCount, maxCount: maxTickCount });
@@ -26,22 +28,28 @@ export function axisBottom(args) {
26
28
  var _a, _b;
27
29
  const x = ((_b = (_a = selection.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect()) === null || _b === void 0 ? void 0 : _b.x) || 0;
28
30
  const right = x + domainSize;
29
- let transform = `translate(0, ${labelHeight + labelsMargin}px)`;
31
+ const top = -tickItems[0][0] || 0;
32
+ let transform = `translate(0, ${labelHeight + labelsMargin - top}px)`;
30
33
  if (rotation) {
31
- const labelsOffsetTop = labelHeight * calculateCos(rotation) + labelsMargin;
34
+ const labelsOffsetTop = labelHeight * calculateCos(rotation) + labelsMargin - top;
32
35
  let labelsOffsetLeft = calculateSin(rotation) * labelHeight;
33
36
  if (Math.abs(rotation) % 360 === 90) {
34
37
  labelsOffsetLeft += ((rotation > 0 ? -1 : 1) * labelHeight) / 2;
35
38
  }
36
39
  transform = `translate(${-labelsOffsetLeft}px, ${labelsOffsetTop}px) rotate(${rotation}deg)`;
37
40
  }
41
+ const tickPath = path();
42
+ tickItems.forEach(([start, end]) => {
43
+ tickPath.moveTo(0, start);
44
+ tickPath.lineTo(0, end);
45
+ });
38
46
  selection
39
47
  .selectAll('.tick')
40
48
  .data(values)
41
49
  .order()
42
50
  .join((el) => {
43
51
  const tick = el.append('g').attr('class', 'tick');
44
- tick.append('line').attr('stroke', 'currentColor').attr('y2', tickSize);
52
+ tick.append('path').attr('d', tickPath.toString()).attr('stroke', 'currentColor');
45
53
  tick.append('text')
46
54
  .text(labelFormat)
47
55
  .attr('fill', 'currentColor')
@@ -56,7 +64,7 @@ export function axisBottom(args) {
56
64
  return tick;
57
65
  })
58
66
  .attr('transform', function (d) {
59
- return `translate(${position(d) + offset},0)`;
67
+ return `translate(${position(d) + offset}, ${top})`;
60
68
  });
61
69
  // Remove tick that has the same x coordinate like domain
62
70
  selection
@@ -1,5 +1,5 @@
1
- import { AxisDomain, AxisScale, ScaleBand } from 'd3';
2
- import { PreparedAxis } from '../hooks';
1
+ import type { AxisDomain, AxisScale, ScaleBand } from 'd3';
2
+ import type { PreparedAxis, PreparedSplit } from '../hooks';
3
3
  export declare function getTicksCount({ axis, range }: {
4
4
  axis: PreparedAxis;
5
5
  range: number;
@@ -20,3 +20,7 @@ export declare function getMaxTickCount({ axis, width }: {
20
20
  axis: PreparedAxis;
21
21
  width: number;
22
22
  }): number;
23
+ export declare function getAxisHeight(args: {
24
+ split: PreparedSplit;
25
+ boundsHeight: number;
26
+ }): number;
@@ -41,3 +41,10 @@ export function getMaxTickCount({ axis, width }) {
41
41
  const minTickWidth = parseInt(axis.labels.style.fontSize) + axis.labels.padding;
42
42
  return Math.floor(width / minTickWidth);
43
43
  }
44
+ export function getAxisHeight(args) {
45
+ const { split, boundsHeight } = args;
46
+ if (split.plots.length > 1) {
47
+ return split.plots[0].height;
48
+ }
49
+ return boundsHeight;
50
+ }
@@ -8,7 +8,8 @@ export * from './axis';
8
8
  export * from './labels';
9
9
  export * from './symbol';
10
10
  export * from './series';
11
- export declare const CHART_SERIES_WITH_VOLUME: ChartKitWidgetSeries['type'][];
11
+ export declare const CHART_SERIES_WITH_VOLUME_ON_Y_AXIS: ChartKitWidgetSeries['type'][];
12
+ export declare const CHART_SERIES_WITH_VOLUME_ON_X_AXIS: ChartKitWidgetSeries['type'][];
12
13
  export type AxisDirection = 'x' | 'y';
13
14
  type UnknownSeries = {
14
15
  type: ChartKitWidgetSeries['type'];
@@ -39,7 +40,7 @@ export declare function isSeriesWithCategoryValues(series: UnknownSeries): serie
39
40
  category: string;
40
41
  }[];
41
42
  };
42
- export declare const getDomainDataXBySeries: (series: UnknownSeries[]) => number[];
43
+ export declare const getDomainDataXBySeries: (series: UnknownSeries[]) => unknown[];
43
44
  export declare function getDefaultMaxXAxisValue(series: UnknownSeries[]): 0 | undefined;
44
45
  export declare const getDomainDataYBySeries: (series: UnknownSeries[]) => unknown[];
45
46
  export declare const getSeriesNames: (series: ChartKitWidgetSeries[]) => string[];
@@ -15,11 +15,12 @@ export * from './labels';
15
15
  export * from './symbol';
16
16
  export * from './series';
17
17
  const CHARTS_WITHOUT_AXIS = ['pie', 'treemap'];
18
- export const CHART_SERIES_WITH_VOLUME = [
18
+ export const CHART_SERIES_WITH_VOLUME_ON_Y_AXIS = [
19
19
  'bar-x',
20
20
  'area',
21
21
  'waterfall',
22
22
  ];
23
+ export const CHART_SERIES_WITH_VOLUME_ON_X_AXIS = ['bar-y'];
23
24
  /**
24
25
  * Checks whether the series should be drawn with axes.
25
26
  *
@@ -38,10 +39,45 @@ export function isSeriesWithNumericalYValues(series) {
38
39
  export function isSeriesWithCategoryValues(series) {
39
40
  return isAxisRelatedSeries(series);
40
41
  }
42
+ function getDomainDataForStackedSeries(seriesList, keyAttr = 'x', valueAttr = 'y') {
43
+ const acc = [];
44
+ const stackedSeries = group(seriesList, getSeriesStackId);
45
+ Array.from(stackedSeries).forEach(([_stackId, seriesStack]) => {
46
+ const values = {};
47
+ seriesStack.forEach((singleSeries) => {
48
+ const data = new Map();
49
+ singleSeries.data.forEach((point) => {
50
+ const key = String(point[keyAttr]);
51
+ let value = 0;
52
+ if (valueAttr in point && typeof point[valueAttr] === 'number') {
53
+ value = point[valueAttr];
54
+ }
55
+ if (data.has(key)) {
56
+ value = Math.max(value, data.get(key));
57
+ }
58
+ data.set(key, value);
59
+ });
60
+ Array.from(data).forEach(([key, value]) => {
61
+ values[key] = (values[key] || 0) + value;
62
+ });
63
+ });
64
+ acc.push(...Object.values(values));
65
+ });
66
+ return acc;
67
+ }
41
68
  export const getDomainDataXBySeries = (series) => {
42
- return series.reduce((acc, s) => {
43
- if (isSeriesWithNumericalXValues(s)) {
44
- acc.push(...s.data.map((d) => d.x));
69
+ const groupedSeries = group(series, (item) => item.type);
70
+ return Array.from(groupedSeries).reduce((acc, [type, seriesList]) => {
71
+ switch (type) {
72
+ case 'bar-y': {
73
+ acc.push(...getDomainDataForStackedSeries(seriesList, 'y', 'x'));
74
+ break;
75
+ }
76
+ default: {
77
+ seriesList.filter(isSeriesWithNumericalXValues).forEach((s) => {
78
+ acc.push(...s.data.map((d) => d.x));
79
+ });
80
+ }
45
81
  }
46
82
  return acc;
47
83
  }, []);
@@ -58,28 +94,7 @@ export const getDomainDataYBySeries = (series) => {
58
94
  switch (type) {
59
95
  case 'area':
60
96
  case 'bar-x': {
61
- const stackedSeries = group(seriesList, getSeriesStackId);
62
- Array.from(stackedSeries).forEach(([_stackId, seriesStack]) => {
63
- const values = {};
64
- seriesStack.forEach((singleSeries) => {
65
- const data = new Map();
66
- singleSeries.data.forEach((point) => {
67
- const key = String(point.x);
68
- let value = 0;
69
- if (point.y && typeof point.y === 'number') {
70
- value = point.y;
71
- }
72
- if (data.has(key)) {
73
- value = Math.max(value, data.get(key));
74
- }
75
- data.set(key, value);
76
- });
77
- Array.from(data).forEach(([key, value]) => {
78
- values[key] = (values[key] || 0) + value;
79
- });
80
- });
81
- acc.push(...Object.values(values));
82
- });
97
+ acc.push(...getDomainDataForStackedSeries(seriesList));
83
98
  break;
84
99
  }
85
100
  case 'waterfall': {
@@ -65,3 +65,13 @@ export type ChartKitWidgetAxis = {
65
65
  * */
66
66
  maxPadding?: number;
67
67
  };
68
+ export type ChartKitWidgetXAxis = ChartKitWidgetAxis;
69
+ export type ChartKitWidgetYAxis = ChartKitWidgetAxis & {
70
+ /** Axis location.
71
+ * Possible values - 'left' and 'right'.
72
+ * */
73
+ position?: 'left' | 'right';
74
+ /** Property for splitting charts. Determines which area the axis is located in.
75
+ * */
76
+ plotIndex?: number;
77
+ };
@@ -45,7 +45,8 @@ export type BarYSeries<T = any> = BaseSeries & {
45
45
  grouping?: boolean;
46
46
  dataLabels?: ChartKitWidgetSeriesOptions['dataLabels'] & {
47
47
  /**
48
- * Whether to align the data label inside or outside the box
48
+ * Whether to align the data label inside or outside the box.
49
+ * For charts with a percentage stack, it is always true.
49
50
  *
50
51
  * @default false
51
52
  * */
@@ -1,7 +1,8 @@
1
- import type { ChartKitWidgetAxis } from './axis';
1
+ import type { ChartKitWidgetXAxis, ChartKitWidgetYAxis } from './axis';
2
2
  import type { ChartKitWidgetChart } from './chart';
3
3
  import type { ChartKitWidgetLegend } from './legend';
4
4
  import type { ChartKitWidgetSeries, ChartKitWidgetSeriesOptions } from './series';
5
+ import type { ChartKitWidgetSplit } from './split';
5
6
  import type { ChartKitWidgetTitle } from './title';
6
7
  import type { ChartKitWidgetTooltip } from './tooltip';
7
8
  export * from './axis';
@@ -15,6 +16,7 @@ export * from './bar-y';
15
16
  export * from './area';
16
17
  export * from './line';
17
18
  export * from './series';
19
+ export * from './split';
18
20
  export * from './title';
19
21
  export * from './tooltip';
20
22
  export * from './halo';
@@ -29,6 +31,9 @@ export type ChartKitWidgetData<T = any> = {
29
31
  };
30
32
  title?: ChartKitWidgetTitle;
31
33
  tooltip?: ChartKitWidgetTooltip<T>;
32
- xAxis?: ChartKitWidgetAxis;
33
- yAxis?: ChartKitWidgetAxis[];
34
+ xAxis?: ChartKitWidgetXAxis;
35
+ yAxis?: ChartKitWidgetYAxis[];
36
+ /** Setting for displaying charts on different plots.
37
+ * It can be used to visualize related information on multiple charts. */
38
+ split?: ChartKitWidgetSplit;
34
39
  };
@@ -9,6 +9,7 @@ export * from './bar-y';
9
9
  export * from './area';
10
10
  export * from './line';
11
11
  export * from './series';
12
+ export * from './split';
12
13
  export * from './title';
13
14
  export * from './tooltip';
14
15
  export * from './halo';
@@ -0,0 +1,13 @@
1
+ import type { BaseTextStyle } from './base';
2
+ export type SplitPlotOptions = {
3
+ title?: {
4
+ text: string;
5
+ style?: Partial<BaseTextStyle>;
6
+ };
7
+ };
8
+ export type ChartKitWidgetSplit = {
9
+ enable: boolean;
10
+ layout?: 'vertical';
11
+ gap?: string | number;
12
+ plots?: SplitPlotOptions[];
13
+ };
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/chartkit",
3
- "version": "5.7.0",
3
+ "version": "5.8.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",