@gravity-ui/chartkit 5.5.0 → 5.7.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 (61) hide show
  1. package/build/i18n/keysets/en.json +2 -1
  2. package/build/i18n/keysets/ru.json +2 -4
  3. package/build/plugins/d3/examples/area/NegativeValues.d.ts +2 -0
  4. package/build/plugins/d3/examples/area/NegativeValues.js +24 -0
  5. package/build/plugins/d3/examples/area/TwoYAxis.d.ts +2 -0
  6. package/build/plugins/d3/examples/area/TwoYAxis.js +58 -0
  7. package/build/plugins/d3/examples/bar-x/NegativeValues.d.ts +2 -0
  8. package/build/plugins/d3/examples/bar-x/NegativeValues.js +41 -0
  9. package/build/plugins/d3/examples/bar-x/TwoYAxis.d.ts +2 -0
  10. package/build/plugins/d3/examples/bar-x/TwoYAxis.js +58 -0
  11. package/build/plugins/d3/examples/bar-y/NegativeValues.d.ts +2 -0
  12. package/build/plugins/d3/examples/bar-y/NegativeValues.js +40 -0
  13. package/build/plugins/d3/examples/line/TwoYAxis.d.ts +2 -0
  14. package/build/plugins/d3/examples/line/TwoYAxis.js +58 -0
  15. package/build/plugins/d3/examples/mars-weather.d.ts +13 -0
  16. package/build/plugins/d3/examples/mars-weather.js +1203 -0
  17. package/build/plugins/d3/examples/scatter/TwoYAxis.d.ts +2 -0
  18. package/build/plugins/d3/examples/scatter/TwoYAxis.js +58 -0
  19. package/build/plugins/d3/renderer/components/AxisY.d.ts +1 -1
  20. package/build/plugins/d3/renderer/components/AxisY.js +112 -79
  21. package/build/plugins/d3/renderer/components/Chart.js +4 -3
  22. package/build/plugins/d3/renderer/hooks/useAxisScales/index.d.ts +1 -1
  23. package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +19 -6
  24. package/build/plugins/d3/renderer/hooks/useChartDimensions/utils.d.ts +1 -0
  25. package/build/plugins/d3/renderer/hooks/useChartDimensions/utils.js +11 -10
  26. package/build/plugins/d3/renderer/hooks/useChartOptions/types.d.ts +1 -0
  27. package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +1 -0
  28. package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +61 -54
  29. package/build/plugins/d3/renderer/hooks/useSeries/prepare-area.d.ts +1 -1
  30. package/build/plugins/d3/renderer/hooks/useSeries/prepare-area.js +1 -0
  31. package/build/plugins/d3/renderer/hooks/useSeries/prepare-bar-x.js +1 -0
  32. package/build/plugins/d3/renderer/hooks/useSeries/prepare-legend.js +3 -3
  33. package/build/plugins/d3/renderer/hooks/useSeries/prepare-line.d.ts +1 -1
  34. package/build/plugins/d3/renderer/hooks/useSeries/prepare-line.js +1 -0
  35. package/build/plugins/d3/renderer/hooks/useSeries/prepare-scatter.js +1 -0
  36. package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +4 -0
  37. package/build/plugins/d3/renderer/hooks/useShapes/area/prepare-data.d.ts +2 -1
  38. package/build/plugins/d3/renderer/hooks/useShapes/area/prepare-data.js +21 -10
  39. package/build/plugins/d3/renderer/hooks/useShapes/bar-x/prepare-data.d.ts +2 -1
  40. package/build/plugins/d3/renderer/hooks/useShapes/bar-x/prepare-data.js +8 -6
  41. package/build/plugins/d3/renderer/hooks/useShapes/bar-y/prepare-data.d.ts +1 -1
  42. package/build/plugins/d3/renderer/hooks/useShapes/bar-y/prepare-data.js +6 -4
  43. package/build/plugins/d3/renderer/hooks/useShapes/index.d.ts +1 -1
  44. package/build/plugins/d3/renderer/hooks/useShapes/index.js +3 -1
  45. package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.d.ts +1 -1
  46. package/build/plugins/d3/renderer/hooks/useShapes/line/prepare-data.js +2 -1
  47. package/build/plugins/d3/renderer/hooks/useShapes/scatter/prepare-data.d.ts +2 -2
  48. package/build/plugins/d3/renderer/hooks/useShapes/scatter/prepare-data.js +5 -2
  49. package/build/plugins/d3/renderer/hooks/useShapes/waterfall/prepare-data.d.ts +1 -1
  50. package/build/plugins/d3/renderer/hooks/useShapes/waterfall/prepare-data.js +1 -1
  51. package/build/plugins/d3/renderer/utils/index.d.ts +2 -0
  52. package/build/plugins/d3/renderer/utils/index.js +11 -0
  53. package/build/plugins/d3/renderer/utils/text.js +1 -1
  54. package/build/plugins/d3/renderer/validation/index.js +13 -4
  55. package/build/plugins/d3/utils/pie-center-text.js +1 -1
  56. package/build/plugins/highcharts/renderer/helpers/config/config.js +2 -2
  57. package/build/types/widget-data/area.d.ts +2 -0
  58. package/build/types/widget-data/bar-x.d.ts +2 -0
  59. package/build/types/widget-data/line.d.ts +2 -0
  60. package/build/types/widget-data/scatter.d.ts +2 -0
  61. package/package.json +2 -1
@@ -0,0 +1,2 @@
1
+ import React from 'react';
2
+ export declare const TwoYAxis: () => React.JSX.Element;
@@ -0,0 +1,58 @@
1
+ import React from 'react';
2
+ import { dateTime } from '@gravity-ui/date-utils';
3
+ import { ChartKit } from '../../../../components/ChartKit';
4
+ import { ExampleWrapper } from '../ExampleWrapper';
5
+ import marsWeatherData from '../mars-weather';
6
+ export const TwoYAxis = () => {
7
+ const data = marsWeatherData;
8
+ const minTempData = data.map((d) => ({
9
+ x: dateTime({ input: d.terrestrial_date, format: 'YYYY-MM-DD' }).valueOf(),
10
+ y: d.min_temp,
11
+ }));
12
+ const maxTempData = data.map((d) => ({
13
+ x: dateTime({ input: d.terrestrial_date, format: 'YYYY-MM-DD' }).valueOf(),
14
+ y: d.max_temp,
15
+ }));
16
+ const widgetData = {
17
+ series: {
18
+ data: [
19
+ {
20
+ type: 'scatter',
21
+ data: minTempData,
22
+ name: 'Min Temperature',
23
+ yAxis: 0,
24
+ },
25
+ {
26
+ type: 'scatter',
27
+ data: maxTempData,
28
+ name: 'Max Temperature',
29
+ yAxis: 1,
30
+ },
31
+ ],
32
+ },
33
+ yAxis: [
34
+ {
35
+ title: {
36
+ text: 'Min',
37
+ },
38
+ },
39
+ {
40
+ title: {
41
+ text: 'Max',
42
+ },
43
+ },
44
+ ],
45
+ xAxis: {
46
+ type: 'datetime',
47
+ title: {
48
+ text: 'Terrestrial date',
49
+ },
50
+ ticks: { pixelInterval: 200 },
51
+ },
52
+ title: {
53
+ text: 'Mars weather',
54
+ },
55
+ };
56
+ return (React.createElement(ExampleWrapper, null,
57
+ React.createElement(ChartKit, { type: "d3", data: widgetData })));
58
+ };
@@ -2,9 +2,9 @@ import React from 'react';
2
2
  import type { ChartScale, PreparedAxis } from '../hooks';
3
3
  type Props = {
4
4
  axises: PreparedAxis[];
5
+ scale: ChartScale[];
5
6
  width: number;
6
7
  height: number;
7
- scale: ChartScale;
8
8
  };
9
9
  export declare const AxisY: ({ axises, width, height, scale }: Props) => React.JSX.Element;
10
10
  export {};
@@ -1,11 +1,15 @@
1
1
  import React from 'react';
2
- import { axisLeft, select } from 'd3';
2
+ import { axisLeft, axisRight, line, select } from 'd3';
3
3
  import { block } from '../../../../utils/cn';
4
4
  import { calculateCos, calculateSin, formatAxisTickLabel, getClosestPointsRange, getScaleTicks, getTicksCount, parseTransformStyle, setEllipsisForOverflowText, setEllipsisForOverflowTexts, } from '../utils';
5
5
  const b = block('d3-axis');
6
- function transformLabel(node, axis) {
6
+ function transformLabel(args) {
7
+ const { node, axis } = args;
7
8
  let topOffset = axis.labels.lineHeight / 2;
8
- let leftOffset = -axis.labels.margin;
9
+ let leftOffset = axis.labels.margin;
10
+ if (axis.position === 'left') {
11
+ leftOffset = leftOffset * -1;
12
+ }
9
13
  if (axis.labels.rotation) {
10
14
  if (axis.labels.rotation > 0) {
11
15
  leftOffset -= axis.labels.lineHeight * calculateSin(axis.labels.rotation);
@@ -24,92 +28,121 @@ function transformLabel(node, axis) {
24
28
  }
25
29
  return `translate(${leftOffset}px, ${topOffset}px)`;
26
30
  }
31
+ function getAxisGenerator(args) {
32
+ const { preparedAxis, axisGenerator: generator, width, height, scale } = args;
33
+ const tickSize = preparedAxis.grid.enabled ? width * -1 : 0;
34
+ const step = getClosestPointsRange(preparedAxis, getScaleTicks(scale));
35
+ let axisGenerator = generator
36
+ .tickSize(tickSize)
37
+ .tickPadding(preparedAxis.labels.margin)
38
+ .tickFormat((value) => {
39
+ if (!preparedAxis.labels.enabled) {
40
+ return '';
41
+ }
42
+ return formatAxisTickLabel({
43
+ axis: preparedAxis,
44
+ value,
45
+ step,
46
+ });
47
+ });
48
+ const ticksCount = getTicksCount({ axis: preparedAxis, range: height });
49
+ if (ticksCount) {
50
+ axisGenerator = axisGenerator.ticks(ticksCount);
51
+ }
52
+ return axisGenerator;
53
+ }
27
54
  export const AxisY = ({ axises, width, height, scale }) => {
28
55
  const ref = React.useRef(null);
29
56
  React.useEffect(() => {
30
57
  if (!ref.current) {
31
58
  return;
32
59
  }
33
- const axis = axises[0];
34
60
  const svgElement = select(ref.current);
35
61
  svgElement.selectAll('*').remove();
36
- const tickSize = axis.grid.enabled ? width * -1 : 0;
37
- const step = getClosestPointsRange(axis, getScaleTicks(scale));
38
- let yAxisGenerator = axisLeft(scale)
39
- .tickSize(tickSize)
40
- .tickPadding(axis.labels.margin)
41
- .tickFormat((value) => {
42
- if (!axis.labels.enabled) {
43
- return '';
44
- }
45
- return formatAxisTickLabel({
46
- axis,
47
- value,
48
- step,
62
+ const axisSelection = svgElement
63
+ .selectAll('axis')
64
+ .data(axises)
65
+ .join('g')
66
+ .attr('class', b())
67
+ .style('transform', (_d, index) => (index === 0 ? '' : `translate(${width}px, 0)`));
68
+ axisSelection.each((d, index, node) => {
69
+ const seriesScale = scale[index];
70
+ const axisItem = select(node[index]);
71
+ const yAxisGenerator = getAxisGenerator({
72
+ axisGenerator: index === 0
73
+ ? axisLeft(seriesScale)
74
+ : axisRight(seriesScale),
75
+ preparedAxis: d,
76
+ height,
77
+ width,
78
+ scale: seriesScale,
49
79
  });
80
+ yAxisGenerator(axisItem);
81
+ if (d.labels.enabled) {
82
+ const tickTexts = axisItem
83
+ .selectAll('.tick text')
84
+ // The offset must be applied before the labels are rotated.
85
+ // Therefore, we reset the values and make an offset in transform attribute.
86
+ // FIXME: give up axisLeft(d3) and switch to our own generation method
87
+ .attr('x', null)
88
+ .attr('dy', null)
89
+ .style('font-size', d.labels.style.fontSize)
90
+ .style('transform', function () {
91
+ return transformLabel({ node: this, axis: d });
92
+ });
93
+ const textMaxWidth = !d.labels.rotation || Math.abs(d.labels.rotation) % 360 !== 90
94
+ ? d.labels.maxWidth
95
+ : (height - d.labels.padding * (tickTexts.size() - 1)) / tickTexts.size();
96
+ tickTexts.call(setEllipsisForOverflowTexts, textMaxWidth);
97
+ }
98
+ // remove overlapping ticks
99
+ // Note: this method do not prepared for rotated labels
100
+ if (!d.labels.rotation) {
101
+ let elementY = 0;
102
+ axisItem
103
+ .selectAll('.tick')
104
+ .filter(function (_d, tickIndex) {
105
+ const tickNode = this;
106
+ const r = tickNode.getBoundingClientRect();
107
+ if (r.bottom > elementY && tickIndex !== 0) {
108
+ return true;
109
+ }
110
+ elementY = r.top - d.labels.padding;
111
+ return false;
112
+ })
113
+ .remove();
114
+ }
115
+ return axisItem;
50
116
  });
51
- const ticksCount = getTicksCount({ axis, range: height });
52
- if (ticksCount) {
53
- yAxisGenerator = yAxisGenerator.ticks(ticksCount);
54
- }
55
- svgElement.call(yAxisGenerator).attr('class', b());
56
- svgElement
117
+ axisSelection
57
118
  .select('.domain')
58
- .attr('d', `M0,${height}H0V0`)
59
- .style('stroke', axis.lineColor || '');
60
- if (axis.labels.enabled) {
61
- const tickTexts = svgElement
62
- .selectAll('.tick text')
63
- // The offset must be applied before the labels are rotated.
64
- // Therefore, we reset the values and make an offset in transform attribute.
65
- // FIXME: give up axisLeft(d3) and switch to our own generation method
66
- .attr('x', null)
67
- .attr('dy', null)
68
- .style('font-size', axis.labels.style.fontSize)
69
- .style('transform', function () {
70
- return transformLabel(this, axis);
71
- });
72
- const textMaxWidth = !axis.labels.rotation || Math.abs(axis.labels.rotation) % 360 !== 90
73
- ? axis.labels.maxWidth
74
- : (height - axis.labels.padding * (tickTexts.size() - 1)) / tickTexts.size();
75
- tickTexts.call(setEllipsisForOverflowTexts, textMaxWidth);
76
- }
77
- const transformStyle = svgElement.select('.tick').attr('transform');
78
- const { y } = parseTransformStyle(transformStyle);
79
- if (y === height) {
80
- // Remove stroke from tick that has the same y coordinate like domain
81
- svgElement.select('.tick line').style('stroke', 'none');
82
- }
83
- // remove overlapping ticks
84
- // Note: this method do not prepared for rotated labels
85
- if (!axis.labels.rotation) {
86
- let elementY = 0;
87
- svgElement
88
- .selectAll('.tick')
89
- .filter(function (_d, index) {
90
- const node = this;
91
- const r = node.getBoundingClientRect();
92
- if (r.bottom > elementY && index !== 0) {
93
- return true;
94
- }
95
- elementY = r.top - axis.labels.padding;
96
- return false;
97
- })
98
- .remove();
99
- }
100
- if (axis.title.text) {
101
- const textY = axis.title.margin + axis.labels.margin + axis.labels.width;
102
- svgElement
103
- .append('text')
104
- .attr('class', b('title'))
105
- .attr('text-anchor', 'middle')
106
- .attr('dy', -textY)
107
- .attr('dx', -height / 2)
108
- .attr('font-size', axis.title.style.fontSize)
109
- .attr('transform', 'rotate(-90)')
110
- .text(axis.title.text)
111
- .call(setEllipsisForOverflowText, height);
112
- }
119
+ .attr('d', () => {
120
+ const points = [
121
+ [0, 0],
122
+ [0, height],
123
+ ];
124
+ return line()(points);
125
+ })
126
+ .style('stroke', (d) => d.lineColor || '');
127
+ svgElement.selectAll('.tick').each((_d, index, nodes) => {
128
+ const tickNode = select(nodes[index]);
129
+ if (parseTransformStyle(tickNode.attr('transform')).y === height) {
130
+ // Remove stroke from tick that has the same y coordinate like domain
131
+ tickNode.select('line').style('stroke', 'none');
132
+ }
133
+ });
134
+ axisSelection
135
+ .append('text')
136
+ .attr('class', b('title'))
137
+ .attr('text-anchor', 'middle')
138
+ .attr('dy', (d) => -(d.title.margin + d.labels.margin + d.labels.width))
139
+ .attr('dx', (_d, index) => (index === 0 ? -height / 2 : height / 2))
140
+ .attr('font-size', (d) => d.title.style.fontSize)
141
+ .attr('transform', (_d, index) => (index === 0 ? 'rotate(-90)' : 'rotate(90)'))
142
+ .text((d) => d.title.text)
143
+ .each((_d, index, node) => {
144
+ return setEllipsisForOverflowText(select(node[index]), height);
145
+ });
113
146
  }, [axises, width, height, scale]);
114
- return React.createElement("g", { ref: ref });
147
+ return React.createElement("g", { ref: ref, className: b('container') });
115
148
  };
@@ -4,7 +4,7 @@ import throttle from 'lodash/throttle';
4
4
  import { block } from '../../../../utils/cn';
5
5
  import { getD3Dispatcher } from '../d3-dispatcher';
6
6
  import { useAxisScales, useChartDimensions, useChartOptions, useSeries, useShapes } from '../hooks';
7
- import { getWidthOccupiedByYAxis } from '../hooks/useChartDimensions/utils';
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
10
  import { getClosestPoints } from '../utils/get-closest-data';
@@ -73,7 +73,8 @@ export const Chart = (props) => {
73
73
  };
74
74
  }, [dispatcher, clickHandler]);
75
75
  const boundsOffsetTop = chart.margin.top;
76
- const boundsOffsetLeft = chart.margin.left + getWidthOccupiedByYAxis({ preparedAxis: yAxis });
76
+ // We only need to consider the width of the first left axis
77
+ const boundsOffsetLeft = chart.margin.left + getYAxisWidth(yAxis[0]);
77
78
  const handleMouseMove = (event) => {
78
79
  const [pointerX, pointerY] = pointer(event, svgRef.current);
79
80
  const x = pointerX - boundsOffsetLeft;
@@ -97,7 +98,7 @@ export const Chart = (props) => {
97
98
  React.createElement("svg", { ref: svgRef, className: b(), width: width, height: height, onMouseMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave },
98
99
  title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
99
100
  React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[boundsOffsetLeft, boundsOffsetTop].join(',')})` },
100
- xScale && yScale && (React.createElement(React.Fragment, null,
101
+ xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length) && (React.createElement(React.Fragment, null,
101
102
  React.createElement(AxisY, { axises: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale }),
102
103
  React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
103
104
  React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale })))),
@@ -12,7 +12,7 @@ type Args = {
12
12
  };
13
13
  type ReturnValue = {
14
14
  xScale?: ChartScale;
15
- yScale?: ChartScale;
15
+ yScale?: ChartScale[];
16
16
  };
17
17
  export declare function createYScale(axis: PreparedAxis, series: PreparedSeries[], boundsHeight: number): ScaleBand<string> | ScaleLinear<number, number, never> | ScaleTime<number, number, never>;
18
18
  export declare function createXScale(axis: PreparedAxis | ChartKitWidgetAxis, series: (PreparedSeries | ChartKitWidgetSeries)[], boundsWidth: number): ScaleBand<string> | ScaleLinear<number, number, never> | ScaleTime<number, number, never>;
@@ -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 { getDataCategoryValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
5
+ import { CHART_SERIES_WITH_VOLUME, 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
  };
@@ -28,9 +28,13 @@ export function createYScale(axis, series, boundsHeight) {
28
28
  const domain = getDomainDataYBySeries(series);
29
29
  const range = [boundsHeight, boundsHeight * axis.maxPadding];
30
30
  if (isNumericalArrayData(domain)) {
31
- const [domainYMin, yMax] = extent(domain);
31
+ const [domainYMin, domainMax] = extent(domain);
32
32
  const yMinValue = typeof yMin === 'number' ? yMin : domainYMin;
33
- return scaleLinear().domain([yMinValue, yMax]).range(range).nice();
33
+ let yMaxValue = domainMax;
34
+ if (series.some((s) => CHART_SERIES_WITH_VOLUME.includes(s.type))) {
35
+ yMaxValue = Math.max(yMaxValue, 0);
36
+ }
37
+ return scaleLinear().domain([yMinValue, yMaxValue]).range(range).nice();
34
38
  }
35
39
  break;
36
40
  }
@@ -79,6 +83,7 @@ function calculateXAxisPadding(series) {
79
83
  }
80
84
  export function createXScale(axis, series, boundsWidth) {
81
85
  const xMin = get(axis, 'min');
86
+ const xMax = getDefaultMaxXAxisValue(series);
82
87
  const xType = get(axis, 'type', DEFAULT_AXIS_TYPE);
83
88
  const xCategories = get(axis, 'categories');
84
89
  const xTimestamps = get(axis, 'timestamps');
@@ -89,9 +94,10 @@ export function createXScale(axis, series, boundsWidth) {
89
94
  case 'linear': {
90
95
  const domain = getDomainDataXBySeries(series);
91
96
  if (isNumericalArrayData(domain)) {
92
- const [domainXMin, xMax] = extent(domain);
97
+ const [domainXMin, domainXMax] = extent(domain);
93
98
  const xMinValue = typeof xMin === 'number' ? xMin : domainXMin;
94
- return scaleLinear().domain([xMinValue, xMax]).range(xRange).nice();
99
+ const xMaxValue = typeof xMax === 'number' ? Math.max(xMax, domainXMax) : domainXMax;
100
+ return scaleLinear().domain([xMinValue, xMaxValue]).range(xRange).nice();
95
101
  }
96
102
  break;
97
103
  }
@@ -135,7 +141,14 @@ const createScales = (args) => {
135
141
  visibleSeries = visibleSeries.length === 0 ? series : visibleSeries;
136
142
  return {
137
143
  xScale: createXScale(xAxis, visibleSeries, boundsWidth),
138
- yScale: createYScale(yAxis[0], visibleSeries, boundsHeight),
144
+ yScale: yAxis.map((axis, index) => {
145
+ const axisSeries = series.filter((s) => {
146
+ const seriesAxisIndex = get(s, 'yAxis', 0);
147
+ return seriesAxisIndex === index;
148
+ });
149
+ const visibleAxisSeries = getOnlyVisibleSeries(axisSeries);
150
+ return createYScale(axis, visibleAxisSeries.length ? visibleAxisSeries : axisSeries, boundsHeight);
151
+ }),
139
152
  };
140
153
  };
141
154
  /**
@@ -4,6 +4,7 @@ export declare const getBoundsWidth: (args: {
4
4
  chartMargin: PreparedChart['margin'];
5
5
  preparedYAxis: PreparedAxis[];
6
6
  }) => number;
7
+ export declare function getYAxisWidth(axis: PreparedAxis | undefined): number;
7
8
  export declare function getWidthOccupiedByYAxis(args: {
8
9
  preparedAxis: PreparedAxis[];
9
10
  }): number;
@@ -5,16 +5,17 @@ export const getBoundsWidth = (args) => {
5
5
  chartMargin.left -
6
6
  getWidthOccupiedByYAxis({ preparedAxis: preparedYAxis }));
7
7
  };
8
- export function getWidthOccupiedByYAxis(args) {
9
- const { preparedAxis } = args;
8
+ export function getYAxisWidth(axis) {
10
9
  let result = 0;
11
- preparedAxis.forEach((axis) => {
12
- if (axis.title.text) {
13
- result += axis.title.height + axis.title.margin;
14
- }
15
- if (axis.labels.enabled) {
16
- result += axis.labels.margin + axis.labels.width;
17
- }
18
- });
10
+ if (axis === null || axis === void 0 ? void 0 : axis.title.text) {
11
+ result += axis.title.height + axis.title.margin;
12
+ }
13
+ if (axis === null || axis === void 0 ? void 0 : axis.labels.enabled) {
14
+ result += axis.labels.margin + axis.labels.width;
15
+ }
19
16
  return result;
20
17
  }
18
+ export function getWidthOccupiedByYAxis(args) {
19
+ const { preparedAxis = [] } = args;
20
+ return preparedAxis.reduce((sum, axis) => sum + getYAxisWidth(axis), 0);
21
+ }
@@ -27,6 +27,7 @@ export type PreparedAxis = Omit<ChartKitWidgetAxis, 'type' | 'labels'> & {
27
27
  ticks: {
28
28
  pixelInterval?: number;
29
29
  };
30
+ position: 'left' | 'right' | 'top' | 'bottom';
30
31
  };
31
32
  export type PreparedTitle = ChartKitWidgetData['title'] & {
32
33
  height: number;
@@ -79,6 +79,7 @@ export const getPreparedXAxis = ({ xAxis, series, width, }) => {
79
79
  ticks: {
80
80
  pixelInterval: get(xAxis, 'ticks.pixelInterval'),
81
81
  },
82
+ position: 'bottom',
82
83
  };
83
84
  const { height, rotation } = getLabelSettings({
84
85
  axis: preparedXAxis,
@@ -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 { formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, } from '../../utils';
3
+ import { CHART_SERIES_WITH_VOLUME, 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;
@@ -24,8 +24,8 @@ const getAxisLabelMaxWidth = (args) => {
24
24
  };
25
25
  function getAxisMin(axis, series) {
26
26
  const min = axis === null || axis === void 0 ? void 0 : axis.min;
27
- const seriesWithVolume = ['bar-x', 'area', 'waterfall'];
28
- if (typeof min === 'undefined' && (series === null || series === void 0 ? void 0 : series.some((s) => seriesWithVolume.includes(s.type)))) {
27
+ if (typeof min === 'undefined' &&
28
+ (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME.includes(s.type)))) {
29
29
  return series.reduce((minValue, s) => {
30
30
  switch (s.type) {
31
31
  case 'waterfall': {
@@ -33,7 +33,8 @@ function getAxisMin(axis, series) {
33
33
  return Math.min(minValue, minSubTotal);
34
34
  }
35
35
  default: {
36
- return minValue;
36
+ const minYValue = s.data.reduce((res, d) => Math.min(res, get(d, 'y', 0)), 0);
37
+ return Math.min(minValue, minYValue);
37
38
  }
38
39
  }
39
40
  }, 0);
@@ -41,54 +42,60 @@ function getAxisMin(axis, series) {
41
42
  return min;
42
43
  }
43
44
  export const getPreparedYAxis = ({ series, yAxis, }) => {
44
- // FIXME: add support for n axises
45
- const yAxis1 = yAxis === null || yAxis === void 0 ? void 0 : yAxis[0];
46
- const labelsEnabled = get(yAxis1, 'labels.enabled', true);
47
- const y1LabelsStyle = {
48
- fontSize: get(yAxis1, 'labels.style.fontSize', DEFAULT_AXIS_LABEL_FONT_SIZE),
49
- };
50
- const y1TitleText = get(yAxis1, 'title.text', '');
51
- const y1TitleStyle = {
52
- fontSize: get(yAxis1, 'title.style.fontSize', yAxisTitleDefaults.fontSize),
53
- };
54
- const axisType = get(yAxis1, 'type', 'linear');
55
- const preparedY1Axis = {
56
- type: axisType,
57
- labels: {
58
- enabled: labelsEnabled,
59
- margin: labelsEnabled ? get(yAxis1, 'labels.margin', axisLabelsDefaults.margin) : 0,
60
- padding: labelsEnabled ? get(yAxis1, 'labels.padding', axisLabelsDefaults.padding) : 0,
61
- dateFormat: get(yAxis1, 'labels.dateFormat'),
62
- numberFormat: get(yAxis1, 'labels.numberFormat'),
63
- style: y1LabelsStyle,
64
- rotation: get(yAxis1, 'labels.rotation', 0),
65
- width: 0,
66
- height: 0,
67
- lineHeight: getHorisontalSvgTextHeight({ text: 'TmpLabel', style: y1LabelsStyle }),
68
- maxWidth: get(yAxis1, 'labels.maxWidth', axisLabelsDefaults.maxWidth),
69
- },
70
- lineColor: get(yAxis1, 'lineColor'),
71
- categories: get(yAxis1, 'categories'),
72
- timestamps: get(yAxis1, 'timestamps'),
73
- title: {
74
- text: y1TitleText,
75
- margin: get(yAxis1, 'title.margin', yAxisTitleDefaults.margin),
76
- style: y1TitleStyle,
77
- height: y1TitleText
78
- ? getHorisontalSvgTextHeight({ text: y1TitleText, style: y1TitleStyle })
79
- : 0,
80
- },
81
- min: getAxisMin(yAxis1, series),
82
- maxPadding: get(yAxis1, 'maxPadding', 0.05),
83
- grid: {
84
- enabled: get(yAxis1, 'grid.enabled', true),
85
- },
86
- ticks: {
87
- pixelInterval: get(yAxis1, 'ticks.pixelInterval'),
88
- },
89
- };
90
- if (labelsEnabled) {
91
- preparedY1Axis.labels.width = getAxisLabelMaxWidth({ axis: preparedY1Axis, series });
92
- }
93
- return [preparedY1Axis];
45
+ return (yAxis || [{}]).map((axisItem, index) => {
46
+ const axisPosition = index === 0 ? 'left' : 'right';
47
+ const labelsEnabled = get(axisItem, 'labels.enabled', true);
48
+ const labelsStyle = {
49
+ fontSize: get(axisItem, 'labels.style.fontSize', DEFAULT_AXIS_LABEL_FONT_SIZE),
50
+ };
51
+ const titleText = get(axisItem, 'title.text', '');
52
+ const titleStyle = {
53
+ fontSize: get(axisItem, 'title.style.fontSize', yAxisTitleDefaults.fontSize),
54
+ };
55
+ const axisType = get(axisItem, 'type', 'linear');
56
+ const preparedAxis = {
57
+ type: axisType,
58
+ labels: {
59
+ enabled: labelsEnabled,
60
+ margin: labelsEnabled
61
+ ? get(axisItem, 'labels.margin', axisLabelsDefaults.margin)
62
+ : 0,
63
+ padding: labelsEnabled
64
+ ? get(axisItem, 'labels.padding', axisLabelsDefaults.padding)
65
+ : 0,
66
+ dateFormat: get(axisItem, 'labels.dateFormat'),
67
+ numberFormat: get(axisItem, 'labels.numberFormat'),
68
+ style: labelsStyle,
69
+ rotation: get(axisItem, 'labels.rotation', 0),
70
+ width: 0,
71
+ height: 0,
72
+ lineHeight: getHorisontalSvgTextHeight({ text: 'TmpLabel', style: labelsStyle }),
73
+ maxWidth: get(axisItem, 'labels.maxWidth', axisLabelsDefaults.maxWidth),
74
+ },
75
+ lineColor: get(axisItem, 'lineColor'),
76
+ categories: get(axisItem, 'categories'),
77
+ timestamps: get(axisItem, 'timestamps'),
78
+ title: {
79
+ text: titleText,
80
+ margin: get(axisItem, 'title.margin', yAxisTitleDefaults.margin),
81
+ style: titleStyle,
82
+ height: titleText
83
+ ? getHorisontalSvgTextHeight({ text: titleText, style: titleStyle })
84
+ : 0,
85
+ },
86
+ min: getAxisMin(axisItem, series),
87
+ maxPadding: get(axisItem, 'maxPadding', 0.05),
88
+ grid: {
89
+ enabled: get(axisItem, 'grid.enabled', index === 0),
90
+ },
91
+ ticks: {
92
+ pixelInterval: get(axisItem, 'ticks.pixelInterval'),
93
+ },
94
+ position: axisPosition,
95
+ };
96
+ if (labelsEnabled) {
97
+ preparedAxis.labels.width = getAxisLabelMaxWidth({ axis: preparedAxis, series });
98
+ }
99
+ return preparedAxis;
100
+ });
94
101
  };
@@ -5,9 +5,9 @@ export declare const DEFAULT_LINE_WIDTH = 1;
5
5
  export declare const DEFAULT_MARKER: {
6
6
  enabled: boolean;
7
7
  symbol: "circle" | "diamond" | "square" | "triangle" | "triangle-down";
8
- radius: number;
9
8
  borderColor: string;
10
9
  borderWidth: number;
10
+ radius: number;
11
11
  };
12
12
  type PrepareAreaSeriesArgs = {
13
13
  colorScale: ScaleOrdinal<string, string>;
@@ -55,6 +55,7 @@ export function prepareArea(args) {
55
55
  },
56
56
  marker: prepareMarker(series, seriesOptions),
57
57
  cursor: get(series, 'cursor', null),
58
+ yAxis: get(series, 'yAxis', 0),
58
59
  };
59
60
  return prepared;
60
61
  }, []);
@@ -31,6 +31,7 @@ export function prepareBarXSeries(args) {
31
31
  padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
32
32
  },
33
33
  cursor: get(series, 'cursor', null),
34
+ yAxis: get(series, 'yAxis', 0),
34
35
  };
35
36
  }, []);
36
37
  }
@@ -5,10 +5,10 @@ import merge from 'lodash/merge';
5
5
  import { legendDefaults } from '../../constants';
6
6
  import { getHorisontalSvgTextHeight } from '../../utils';
7
7
  import { getBoundsWidth } from '../useChartDimensions';
8
- import { getWidthOccupiedByYAxis } from '../useChartDimensions/utils';
8
+ import { getYAxisWidth } from '../useChartDimensions/utils';
9
9
  export const getPreparedLegend = (args) => {
10
10
  const { legend, series } = args;
11
- const enabled = typeof (legend === null || legend === void 0 ? void 0 : legend.enabled) === 'boolean' ? legend === null || legend === void 0 ? void 0 : legend.enabled : series.length > 1;
11
+ const enabled = Boolean(typeof (legend === null || legend === void 0 ? void 0 : legend.enabled) === 'boolean' ? legend === null || legend === void 0 ? void 0 : legend.enabled : series.length > 1);
12
12
  const defaultItemStyle = clone(legendDefaults.itemStyle);
13
13
  const itemStyle = get(legend, 'itemStyle');
14
14
  const computedItemStyle = merge(defaultItemStyle, itemStyle);
@@ -90,7 +90,7 @@ export const getLegendComponents = (args) => {
90
90
  preparedLegend.height = legendHeight;
91
91
  const top = chartHeight - chartMargin.bottom - preparedLegend.height;
92
92
  const offset = {
93
- left: chartMargin.left + getWidthOccupiedByYAxis({ preparedAxis: preparedYAxis }),
93
+ left: chartMargin.left + getYAxisWidth(preparedYAxis[0]),
94
94
  top,
95
95
  };
96
96
  return { legendConfig: { offset, pagination }, legendItems: items };