@gravity-ui/chartkit 5.7.0 → 5.9.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 (43) hide show
  1. package/build/plugins/d3/examples/line/LogarithmicAxis.d.ts +2 -0
  2. package/build/plugins/d3/examples/line/LogarithmicAxis.js +38 -0
  3. package/build/plugins/d3/renderer/components/AxisX.d.ts +2 -1
  4. package/build/plugins/d3/renderer/components/AxisX.js +13 -3
  5. package/build/plugins/d3/renderer/components/AxisY.d.ts +4 -3
  6. package/build/plugins/d3/renderer/components/AxisY.js +17 -8
  7. package/build/plugins/d3/renderer/components/Chart.js +14 -3
  8. package/build/plugins/d3/renderer/components/PlotTitle.d.ts +7 -0
  9. package/build/plugins/d3/renderer/components/PlotTitle.js +12 -0
  10. package/build/plugins/d3/renderer/components/styles.css +7 -1
  11. package/build/plugins/d3/renderer/hooks/index.d.ts +1 -0
  12. package/build/plugins/d3/renderer/hooks/index.js +1 -0
  13. package/build/plugins/d3/renderer/hooks/useAxisScales/index.d.ts +3 -1
  14. package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +30 -16
  15. package/build/plugins/d3/renderer/hooks/useChartDimensions/utils.js +13 -2
  16. package/build/plugins/d3/renderer/hooks/useChartOptions/types.d.ts +1 -0
  17. package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.d.ts +2 -2
  18. package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +14 -2
  19. package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.d.ts +2 -2
  20. package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +17 -8
  21. package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-y.js +2 -1
  22. package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +2 -1
  23. package/build/plugins/d3/renderer/hooks/useShapes/index.js +2 -1
  24. package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.d.ts +6 -4
  25. package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.js +6 -3
  26. package/build/plugins/d3/renderer/hooks/useSplit/index.d.ts +14 -0
  27. package/build/plugins/d3/renderer/hooks/useSplit/index.js +57 -0
  28. package/build/plugins/d3/renderer/hooks/useSplit/types.d.ts +17 -0
  29. package/build/plugins/d3/renderer/hooks/useSplit/types.js +1 -0
  30. package/build/plugins/d3/renderer/utils/axis-generators/bottom.d.ts +1 -1
  31. package/build/plugins/d3/renderer/utils/axis-generators/bottom.js +16 -8
  32. package/build/plugins/d3/renderer/utils/axis.d.ts +6 -2
  33. package/build/plugins/d3/renderer/utils/axis.js +7 -0
  34. package/build/plugins/d3/renderer/utils/index.d.ts +3 -2
  35. package/build/plugins/d3/renderer/utils/index.js +41 -26
  36. package/build/plugins/highcharts/renderer/helpers/config/config.js +11 -1
  37. package/build/types/widget-data/axis.d.ts +11 -1
  38. package/build/types/widget-data/bar-y.d.ts +2 -1
  39. package/build/types/widget-data/index.d.ts +8 -3
  40. package/build/types/widget-data/index.js +1 -0
  41. package/build/types/widget-data/split.d.ts +13 -0
  42. package/build/types/widget-data/split.js +1 -0
  43. package/package.json +1 -1
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const LineWithLogarithmicAxis: () => React.JSX.Element;
@@ -0,0 +1,38 @@
1
+ import React from 'react';
2
+ import { Col, Container, Row, Text } from '@gravity-ui/uikit';
3
+ import { randomNormal } from 'd3';
4
+ import { ChartKit } from '../../../../components/ChartKit';
5
+ import { ExampleWrapper } from '../ExampleWrapper';
6
+ export const LineWithLogarithmicAxis = () => {
7
+ const randomY = randomNormal(0, 100);
8
+ const widgetData = {
9
+ series: {
10
+ data: [
11
+ {
12
+ type: 'line',
13
+ name: 'Line series',
14
+ data: new Array(25).fill(null).map((_, index) => ({
15
+ x: index,
16
+ y: Math.abs(randomY()),
17
+ })),
18
+ },
19
+ ],
20
+ },
21
+ };
22
+ const lineWidgetData = Object.assign(Object.assign({}, widgetData), { title: { text: 'line' } });
23
+ const logarithmicWidgetData = Object.assign(Object.assign({}, widgetData), { title: { text: 'logarithmic' }, yAxis: [
24
+ {
25
+ type: 'logarithmic',
26
+ },
27
+ ] });
28
+ return (React.createElement(Container, { spaceRow: 5 },
29
+ React.createElement(Row, { space: 1 },
30
+ React.createElement(Text, { variant: "header-2" }, "logarithmic VS line")),
31
+ React.createElement(Row, { space: 3 },
32
+ React.createElement(Col, { s: 12, m: 6 },
33
+ React.createElement(ExampleWrapper, null,
34
+ React.createElement(ChartKit, { type: "d3", data: lineWidgetData }))),
35
+ React.createElement(Col, { s: 12, m: 6 },
36
+ React.createElement(ExampleWrapper, null,
37
+ React.createElement(ChartKit, { type: "d3", data: logarithmicWidgetData }))))));
38
+ };
@@ -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;
@@ -1,8 +1,8 @@
1
1
  import React from 'react';
2
- import { extent, scaleBand, scaleLinear, scaleUtc } from 'd3';
2
+ import { extent, scaleBand, scaleLinear, scaleLog, 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
  };
@@ -24,17 +24,19 @@ export function createYScale(axis, series, boundsHeight) {
24
24
  const yCategories = get(axis, 'categories');
25
25
  const yTimestamps = get(axis, 'timestamps');
26
26
  switch (yType) {
27
- case 'linear': {
27
+ case 'linear':
28
+ case 'logarithmic': {
28
29
  const domain = getDomainDataYBySeries(series);
29
30
  const range = [boundsHeight, boundsHeight * axis.maxPadding];
30
31
  if (isNumericalArrayData(domain)) {
31
32
  const [domainYMin, domainMax] = extent(domain);
32
33
  const yMinValue = typeof yMin === 'number' ? yMin : domainYMin;
33
34
  let yMaxValue = domainMax;
34
- if (series.some((s) => CHART_SERIES_WITH_VOLUME.includes(s.type))) {
35
+ if (series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type))) {
35
36
  yMaxValue = Math.max(yMaxValue, 0);
36
37
  }
37
- return scaleLinear().domain([yMinValue, yMaxValue]).range(range).nice();
38
+ const scaleFn = yType === 'logarithmic' ? scaleLog : scaleLinear;
39
+ return scaleFn().domain([yMinValue, yMaxValue]).range(range).nice();
38
40
  }
39
41
  break;
40
42
  }
@@ -73,8 +75,11 @@ function calculateXAxisPadding(series) {
73
75
  switch (s.type) {
74
76
  case 'bar-y': {
75
77
  // 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);
78
+ const inside = get(s, 'dataLabels.inside');
79
+ if (!inside) {
80
+ const labelsMaxWidth = get(s, 'dataLabels.maxWidth', 0);
81
+ result = Math.max(result, labelsMaxWidth);
82
+ }
78
83
  break;
79
84
  }
80
85
  }
@@ -91,13 +96,15 @@ export function createXScale(axis, series, boundsWidth) {
91
96
  const xAxisMinPadding = boundsWidth * maxPadding + calculateXAxisPadding(series);
92
97
  const xRange = [0, boundsWidth - xAxisMinPadding];
93
98
  switch (xType) {
94
- case 'linear': {
99
+ case 'linear':
100
+ case 'logarithmic': {
95
101
  const domain = getDomainDataXBySeries(series);
96
102
  if (isNumericalArrayData(domain)) {
97
103
  const [domainXMin, domainXMax] = extent(domain);
98
104
  const xMinValue = typeof xMin === 'number' ? xMin : domainXMin;
99
105
  const xMaxValue = typeof xMax === 'number' ? Math.max(xMax, domainXMax) : domainXMax;
100
- return scaleLinear().domain([xMinValue, xMaxValue]).range(xRange).nice();
106
+ const scaleFn = xType === 'logarithmic' ? scaleLog : scaleLinear;
107
+ return scaleFn().domain([xMinValue, xMaxValue]).range(xRange).nice();
101
108
  }
102
109
  break;
103
110
  }
@@ -134,7 +141,7 @@ export function createXScale(axis, series, boundsWidth) {
134
141
  throw new Error('Failed to create xScale');
135
142
  }
136
143
  const createScales = (args) => {
137
- const { boundsWidth, boundsHeight, series, xAxis, yAxis } = args;
144
+ const { boundsWidth, boundsHeight, series, xAxis, yAxis, split } = args;
138
145
  let visibleSeries = getOnlyVisibleSeries(series);
139
146
  // Reassign to all series in case of all series unselected,
140
147
  // otherwise we will get an empty space without grid
@@ -147,7 +154,8 @@ const createScales = (args) => {
147
154
  return seriesAxisIndex === index;
148
155
  });
149
156
  const visibleAxisSeries = getOnlyVisibleSeries(axisSeries);
150
- return createYScale(axis, visibleAxisSeries.length ? visibleAxisSeries : axisSeries, boundsHeight);
157
+ const axisHeight = getAxisHeight({ boundsHeight, split });
158
+ return createYScale(axis, visibleAxisSeries.length ? visibleAxisSeries : axisSeries, axisHeight);
151
159
  }),
152
160
  };
153
161
  };
@@ -155,15 +163,21 @@ const createScales = (args) => {
155
163
  * Uses to create scales for axis related series
156
164
  */
157
165
  export const useAxisScales = (args) => {
158
- const { boundsWidth, boundsHeight, series, xAxis, yAxis } = args;
159
- const scales = React.useMemo(() => {
166
+ const { boundsWidth, boundsHeight, series, xAxis, yAxis, split } = args;
167
+ return React.useMemo(() => {
160
168
  let xScale;
161
169
  let yScale;
162
170
  const hasAxisRelatedSeries = series.some(isAxisRelatedSeries);
163
171
  if (hasAxisRelatedSeries) {
164
- ({ xScale, yScale } = createScales({ boundsWidth, boundsHeight, series, xAxis, yAxis }));
172
+ ({ xScale, yScale } = createScales({
173
+ boundsWidth,
174
+ boundsHeight,
175
+ series,
176
+ xAxis,
177
+ yAxis,
178
+ split,
179
+ }));
165
180
  }
166
181
  return { xScale, yScale };
167
- }, [boundsWidth, boundsHeight, series, xAxis, yAxis]);
168
- return scales;
182
+ }, [boundsWidth, boundsHeight, series, xAxis, yAxis, split]);
169
183
  };
@@ -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
- import { DEFAULT_AXIS_LABEL_FONT_SIZE, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
3
- import { CHART_SERIES_WITH_VOLUME, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, } from '../../utils';
2
+ import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
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),
@@ -52,7 +60,7 @@ export const getPreparedYAxis = ({ series, yAxis, }) => {
52
60
  const titleStyle = {
53
61
  fontSize: get(axisItem, 'title.style.fontSize', yAxisTitleDefaults.fontSize),
54
62
  };
55
- const axisType = get(axisItem, 'type', 'linear');
63
+ const axisType = get(axisItem, 'type', DEFAULT_AXIS_TYPE);
56
64
  const preparedAxis = {
57
65
  type: axisType,
58
66
  labels: {
@@ -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': {
@@ -379,9 +379,19 @@ function validateCellManipulationConfig(tooltipOptions, property, item) {
379
379
  item[property] = tooltipOptions[property];
380
380
  }
381
381
  }
382
+ function getSeriesTypeFromTooltipContext() {
383
+ var _a, _b;
384
+ if (this.series) {
385
+ return this.series.type;
386
+ }
387
+ if (Array.isArray(this.points)) {
388
+ return (_b = (_a = this.points[0]) === null || _a === void 0 ? void 0 : _a.series) === null || _b === void 0 ? void 0 : _b.type;
389
+ }
390
+ return '';
391
+ }
382
392
  function getTooltip(tooltip, options, comments, holidays) {
383
393
  var _a, _b, _c, _d, _e, _f, _g, _h, _j;
384
- const serieType = (this.series && this.series.type) || tooltip.chart.options.chart.type;
394
+ const serieType = getSeriesTypeFromTooltipContext.call(this) || tooltip.chart.options.chart.type;
385
395
  const chart = tooltip.chart;
386
396
  const xAxis = chart.xAxis[0];
387
397
  const isDatetimeXAxis = xAxis.options.type === 'datetime';
@@ -1,6 +1,6 @@
1
1
  import type { FormatNumberOptions } from '../../plugins/shared';
2
2
  import type { BaseTextStyle } from './base';
3
- export type ChartKitWidgetAxisType = 'category' | 'datetime' | 'linear';
3
+ export type ChartKitWidgetAxisType = 'category' | 'datetime' | 'linear' | 'logarithmic';
4
4
  export type ChartKitWidgetAxisLabels = {
5
5
  /** Enable or disable the axis labels. */
6
6
  enabled?: boolean;
@@ -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.9.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",