@gravity-ui/charts 0.12.0 → 1.1.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 (51) hide show
  1. package/dist/cjs/components/Axis/AxisX.d.ts +1 -0
  2. package/dist/cjs/components/Axis/AxisX.js +16 -8
  3. package/dist/cjs/components/Axis/AxisY.d.ts +1 -0
  4. package/dist/cjs/components/Axis/AxisY.js +38 -10
  5. package/dist/cjs/components/ChartInner/index.js +3 -3
  6. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +3 -0
  7. package/dist/cjs/components/ChartInner/useChartInnerProps.js +14 -3
  8. package/dist/cjs/components/Tooltip/DefaultContent.js +41 -23
  9. package/dist/cjs/hooks/useChartDimensions/index.js +3 -0
  10. package/dist/cjs/hooks/useChartDimensions/utils.js +3 -0
  11. package/dist/cjs/hooks/useChartOptions/x-axis.js +1 -0
  12. package/dist/cjs/hooks/useChartOptions/y-axis.js +1 -0
  13. package/dist/cjs/hooks/useSeries/prepare-waterfall.js +40 -28
  14. package/dist/cjs/hooks/useSeries/types.d.ts +4 -3
  15. package/dist/cjs/hooks/useShapes/waterfall/prepare-data.js +4 -2
  16. package/dist/cjs/types/chart/axis.d.ts +2 -0
  17. package/dist/cjs/types/chart/waterfall.d.ts +9 -0
  18. package/dist/cjs/utils/chart/axis-generators/bottom.d.ts +1 -0
  19. package/dist/cjs/utils/chart/axis-generators/bottom.js +16 -1
  20. package/dist/cjs/utils/chart/get-closest-data.js +23 -13
  21. package/dist/cjs/utils/chart/index.js +5 -5
  22. package/dist/cjs/utils/chart/series/waterfall.d.ts +2 -2
  23. package/dist/cjs/utils/chart/series/waterfall.js +1 -7
  24. package/dist/cjs/utils/chart-ui/pie-center-text.d.ts +1 -0
  25. package/dist/cjs/utils/chart-ui/pie-center-text.js +3 -1
  26. package/dist/esm/components/Axis/AxisX.d.ts +1 -0
  27. package/dist/esm/components/Axis/AxisX.js +16 -8
  28. package/dist/esm/components/Axis/AxisY.d.ts +1 -0
  29. package/dist/esm/components/Axis/AxisY.js +38 -10
  30. package/dist/esm/components/ChartInner/index.js +3 -3
  31. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +3 -0
  32. package/dist/esm/components/ChartInner/useChartInnerProps.js +14 -3
  33. package/dist/esm/components/Tooltip/DefaultContent.js +41 -23
  34. package/dist/esm/hooks/useChartDimensions/index.js +3 -0
  35. package/dist/esm/hooks/useChartDimensions/utils.js +3 -0
  36. package/dist/esm/hooks/useChartOptions/x-axis.js +1 -0
  37. package/dist/esm/hooks/useChartOptions/y-axis.js +1 -0
  38. package/dist/esm/hooks/useSeries/prepare-waterfall.js +40 -28
  39. package/dist/esm/hooks/useSeries/types.d.ts +4 -3
  40. package/dist/esm/hooks/useShapes/waterfall/prepare-data.js +4 -2
  41. package/dist/esm/types/chart/axis.d.ts +2 -0
  42. package/dist/esm/types/chart/waterfall.d.ts +9 -0
  43. package/dist/esm/utils/chart/axis-generators/bottom.d.ts +1 -0
  44. package/dist/esm/utils/chart/axis-generators/bottom.js +16 -1
  45. package/dist/esm/utils/chart/get-closest-data.js +23 -13
  46. package/dist/esm/utils/chart/index.js +5 -5
  47. package/dist/esm/utils/chart/series/waterfall.d.ts +2 -2
  48. package/dist/esm/utils/chart/series/waterfall.js +1 -7
  49. package/dist/esm/utils/chart-ui/pie-center-text.d.ts +1 -0
  50. package/dist/esm/utils/chart-ui/pie-center-text.js +3 -1
  51. package/package.json +1 -1
@@ -8,6 +8,7 @@ type Props = {
8
8
  scale: ChartScale;
9
9
  split: PreparedSplit;
10
10
  plotRef?: React.MutableRefObject<SVGGElement | null>;
11
+ leftmostLimit?: number;
11
12
  };
12
13
  export declare function getTitlePosition(args: {
13
14
  axis: PreparedAxis;
@@ -42,12 +42,23 @@ export function getTitlePosition(args) {
42
42
  return { x, y };
43
43
  }
44
44
  export const AxisX = React.memo(function AxisX(props) {
45
- const { axis, width, height: totalHeight, scale, split, plotRef } = props;
45
+ const { axis, width, height: totalHeight, scale, split, plotRef, leftmostLimit } = props;
46
46
  const ref = React.useRef(null);
47
47
  React.useEffect(() => {
48
48
  if (!ref.current) {
49
49
  return;
50
50
  }
51
+ const svgElement = select(ref.current);
52
+ svgElement.selectAll('*').remove();
53
+ const plotClassName = b('plot-x');
54
+ let plotContainer = null;
55
+ if (plotRef === null || plotRef === void 0 ? void 0 : plotRef.current) {
56
+ plotContainer = select(plotRef.current);
57
+ plotContainer.selectAll(`.${plotClassName}`).remove();
58
+ }
59
+ if (!axis.visible) {
60
+ return;
61
+ }
51
62
  let tickItems = [];
52
63
  if (axis.grid.enabled) {
53
64
  tickItems = new Array(split.plots.length || 1).fill(null).map((_, index) => {
@@ -59,6 +70,7 @@ export const AxisX = React.memo(function AxisX(props) {
59
70
  }
60
71
  const axisScale = scale;
61
72
  const xAxisGenerator = axisBottom({
73
+ leftmostLimit,
62
74
  scale: axisScale,
63
75
  ticks: {
64
76
  items: tickItems,
@@ -77,8 +89,6 @@ export const AxisX = React.memo(function AxisX(props) {
77
89
  color: axis.lineColor,
78
90
  },
79
91
  });
80
- const svgElement = select(ref.current);
81
- svgElement.selectAll('*').remove();
82
92
  svgElement.call(xAxisGenerator).attr('class', b());
83
93
  // add an axis header if necessary
84
94
  if (axis.title.text) {
@@ -105,15 +115,13 @@ export const AxisX = React.memo(function AxisX(props) {
105
115
  });
106
116
  }
107
117
  // add plot lines
108
- if (plotRef && axis.plotLines.length > 0) {
118
+ if (plotContainer && axis.plotLines.length > 0) {
109
119
  const plotLineClassName = b('plotLine');
110
- const plotLineContainer = select(plotRef.current);
111
- plotLineContainer.selectAll(`.${plotLineClassName}-x`).remove();
112
- const plotLinesSelection = plotLineContainer
120
+ const plotLinesSelection = plotContainer
113
121
  .selectAll(`.${plotLineClassName}-x`)
114
122
  .data(axis.plotLines)
115
123
  .join('g')
116
- .attr('class', `${plotLineClassName}-x`);
124
+ .attr('class', `${plotClassName} ${plotLineClassName}-x`);
117
125
  const lineGenerator = line();
118
126
  plotLinesSelection
119
127
  .append('path')
@@ -8,6 +8,7 @@ type Props = {
8
8
  height: number;
9
9
  split: PreparedSplit;
10
10
  plotRef?: React.MutableRefObject<SVGGElement | null>;
11
+ lowerLimit?: number;
11
12
  };
12
13
  export declare const AxisY: (props: Props) => React.JSX.Element;
13
14
  export {};
@@ -4,8 +4,11 @@ import { block, calculateCos, calculateSin, formatAxisTickLabel, getAxisHeight,
4
4
  import './styles.css';
5
5
  const b = block('axis');
6
6
  function transformLabel(args) {
7
- const { node, axis } = args;
7
+ const { node, axis, isTopOffsetOverload = false } = args;
8
8
  let topOffset = axis.labels.lineHeight / 2;
9
+ if (isTopOffsetOverload) {
10
+ topOffset = 0;
11
+ }
9
12
  let leftOffset = axis.labels.margin;
10
13
  if (axis.position === 'left') {
11
14
  leftOffset = leftOffset * -1;
@@ -82,7 +85,7 @@ function getTitlePosition(args) {
82
85
  return { x, y };
83
86
  }
84
87
  export const AxisY = (props) => {
85
- const { axes, width, height: totalHeight, scale, split, plotRef } = props;
88
+ const { axes: allAxes, width, height: totalHeight, scale, split, plotRef, lowerLimit = 0, } = props;
86
89
  const height = getAxisHeight({ split, boundsHeight: totalHeight });
87
90
  const ref = React.useRef(null);
88
91
  const lineGenerator = line();
@@ -90,8 +93,15 @@ export const AxisY = (props) => {
90
93
  if (!ref.current) {
91
94
  return;
92
95
  }
96
+ const axes = allAxes.filter((a) => a.visible);
93
97
  const svgElement = select(ref.current);
94
98
  svgElement.selectAll('*').remove();
99
+ let plotContainer = null;
100
+ const plotClassName = b('plot-y');
101
+ if (plotRef === null || plotRef === void 0 ? void 0 : plotRef.current) {
102
+ plotContainer = select(plotRef.current);
103
+ plotContainer.selectAll(`.${plotClassName}`).remove();
104
+ }
95
105
  const getAxisPosition = (axis) => {
96
106
  var _a;
97
107
  const top = ((_a = split.plots[axis.plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
@@ -127,8 +137,8 @@ export const AxisY = (props) => {
127
137
  });
128
138
  yAxisGenerator(axisItem);
129
139
  if (d.labels.enabled) {
130
- const tickTexts = axisItem
131
- .selectAll('.tick text')
140
+ const labels = axisItem.selectAll('.tick text');
141
+ const tickTexts = labels
132
142
  // The offset must be applied before the labels are rotated.
133
143
  // Therefore, we reset the values and make an offset in transform attribute.
134
144
  // FIXME: give up axisLeft(d3) and switch to our own generation method
@@ -138,6 +148,26 @@ export const AxisY = (props) => {
138
148
  .style('transform', function () {
139
149
  return transformLabel({ node: this, axis: d });
140
150
  });
151
+ labels.each(function (_d, i) {
152
+ if (i === 0) {
153
+ const currentElement = this;
154
+ const currentElementPosition = currentElement.getBoundingClientRect();
155
+ const text = select(currentElement);
156
+ if (currentElementPosition.bottom > lowerLimit) {
157
+ const transform = transformLabel({
158
+ node: this,
159
+ axis: d,
160
+ isTopOffsetOverload: true,
161
+ });
162
+ text.style('transform', transform);
163
+ if (d.labels.rotation) {
164
+ text.attr('text-anchor', () => {
165
+ return d.labels.rotation < 0 ? 'start' : 'end';
166
+ });
167
+ }
168
+ }
169
+ }
170
+ });
141
171
  const textMaxWidth = !d.labels.rotation || Math.abs(d.labels.rotation) % 360 !== 90
142
172
  ? d.labels.maxWidth
143
173
  : (height - d.labels.padding * (tickTexts.size() - 1)) / tickTexts.size();
@@ -160,15 +190,13 @@ export const AxisY = (props) => {
160
190
  })
161
191
  .remove();
162
192
  }
163
- if (plotRef && d.plotLines.length > 0) {
193
+ if (plotContainer && d.plotLines.length > 0) {
164
194
  const plotLineClassName = b('plotLine');
165
- const plotLineContainer = select(plotRef.current);
166
- plotLineContainer.selectAll(`.${plotLineClassName}`).remove();
167
- const plotLinesSelection = plotLineContainer
195
+ const plotLinesSelection = plotContainer
168
196
  .selectAll(`.${plotLineClassName}`)
169
197
  .data(plotLines)
170
198
  .join('g')
171
- .attr('class', plotLineClassName)
199
+ .attr('class', `${plotClassName} ${plotLineClassName}`)
172
200
  .style('transform', (plotLine) => plotLine.transform);
173
201
  plotLinesSelection
174
202
  .append('path')
@@ -240,6 +268,6 @@ export const AxisY = (props) => {
240
268
  handleOverflowingText(nodes[index], height);
241
269
  }
242
270
  });
243
- }, [axes, width, height, scale, split]);
271
+ }, [allAxes, width, height, scale, split]);
244
272
  return React.createElement("g", { ref: ref, className: b('container') });
245
273
  };
@@ -17,7 +17,7 @@ export const ChartInner = (props) => {
17
17
  const htmlLayerRef = React.useRef(null);
18
18
  const plotRef = React.useRef(null);
19
19
  const dispatcher = React.useMemo(() => getDispatcher(), []);
20
- const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher, htmlLayout: htmlLayerRef.current }));
20
+ const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, svgBottomPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher, htmlLayout: htmlLayerRef.current, svgContainer: svgRef.current }));
21
21
  const { tooltipPinned, togglePinTooltip, unpinTooltip } = useChartInnerState({
22
22
  dispatcher,
23
23
  tooltip,
@@ -67,9 +67,9 @@ export const ChartInner = (props) => {
67
67
  })),
68
68
  React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[boundsOffsetLeft, boundsOffsetTop].join(',')})`, ref: plotRef },
69
69
  xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length) && (React.createElement(React.Fragment, null,
70
- React.createElement(AxisY, { axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotRef: plotRef }),
70
+ React.createElement(AxisY, { lowerLimit: svgBottomPos, axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotRef: plotRef }),
71
71
  React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
72
- React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotRef: plotRef })))),
72
+ React.createElement(AxisX, { leftmostLimit: svgXPos, axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotRef: plotRef })))),
73
73
  shapes),
74
74
  preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip }))),
75
75
  React.createElement("div", { className: b('html-layer'), ref: htmlLayerRef, style: {
@@ -4,8 +4,11 @@ import type { ChartInnerProps } from './types';
4
4
  type Props = ChartInnerProps & {
5
5
  dispatcher: Dispatch<object>;
6
6
  htmlLayout: HTMLElement | null;
7
+ svgContainer: SVGGElement | null;
7
8
  };
8
9
  export declare function useChartInnerProps(props: Props): {
10
+ svgBottomPos: number | undefined;
11
+ svgXPos: number | undefined;
9
12
  boundsHeight: number;
10
13
  boundsOffsetLeft: number;
11
14
  boundsOffsetTop: number;
@@ -4,7 +4,8 @@ import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
4
4
  import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
5
5
  import { getPreparedYAxis } from '../../hooks/useChartOptions/y-axis';
6
6
  export function useChartInnerProps(props) {
7
- const { width, height, data, dispatcher, htmlLayout } = props;
7
+ var _a;
8
+ const { width, height, data, dispatcher, htmlLayout, svgContainer } = props;
8
9
  const prevWidth = usePrevious(width);
9
10
  const prevHeight = usePrevious(height);
10
11
  const { chart, title, tooltip } = useChartOptions({ data });
@@ -54,9 +55,19 @@ export function useChartInnerProps(props) {
54
55
  htmlLayout,
55
56
  });
56
57
  const boundsOffsetTop = chart.margin.top;
57
- // We only need to consider the width of the first left axis
58
- const boundsOffsetLeft = chart.margin.left + getYAxisWidth(yAxis[0]);
58
+ // We need to calculate the width of each axis because the first axis can be hidden
59
+ const boundsOffsetLeft = chart.margin.left +
60
+ yAxis.reduce((acc, axis) => {
61
+ const width = getYAxisWidth(axis);
62
+ if (acc < width) {
63
+ acc = width;
64
+ }
65
+ return acc;
66
+ }, 0);
67
+ const { x, bottom } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
59
68
  return {
69
+ svgBottomPos: bottom,
70
+ svgXPos: x,
60
71
  boundsHeight,
61
72
  boundsOffsetLeft,
62
73
  boundsOffsetTop,
@@ -1,7 +1,5 @@
1
1
  import React from 'react';
2
- import { dateTime } from '@gravity-ui/date-utils';
3
2
  import get from 'lodash/get';
4
- import { formatNumber } from '../../libs';
5
3
  import { block, getDataCategoryValue, getWaterfallPointSubtotal } from '../../utils';
6
4
  import { getFormattedValue } from '../../utils/chart/format';
7
5
  const b = block('tooltip');
@@ -12,23 +10,14 @@ const getRowData = (fieldName, data, axis) => {
12
10
  const categories = get(axis, 'categories', []);
13
11
  return getDataCategoryValue({ axisDirection: fieldName, categories, data });
14
12
  }
15
- case 'datetime': {
16
- const value = get(data, fieldName);
17
- if (!value) {
18
- return undefined;
19
- }
20
- return dateTime({ input: value }).format(DEFAULT_DATE_FORMAT);
21
- }
22
- case 'linear':
23
13
  default: {
24
- const value = get(data, fieldName);
25
- return formatNumber(value);
14
+ return get(data, fieldName);
26
15
  }
27
16
  }
28
17
  };
29
18
  const getXRowData = (data, xAxis) => getRowData('x', data, xAxis);
30
19
  const getYRowData = (data, yAxis) => getRowData('y', data, yAxis);
31
- const getMeasureValue = (data, xAxis, yAxis) => {
20
+ const getMeasureValue = ({ data, xAxis, yAxis, valueFormat, }) => {
32
21
  var _a, _b, _c, _d;
33
22
  if (data.every((item) => ['pie', 'treemap', 'waterfall', 'sankey'].includes(item.series.type))) {
34
23
  return null;
@@ -37,12 +26,38 @@ const getMeasureValue = (data, xAxis, yAxis) => {
37
26
  return (_b = (_a = data[0].category) === null || _a === void 0 ? void 0 : _a.key) !== null && _b !== void 0 ? _b : null;
38
27
  }
39
28
  if (data.some((item) => item.series.type === 'bar-y')) {
40
- return getYRowData((_c = data[0]) === null || _c === void 0 ? void 0 : _c.data, yAxis);
29
+ const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: yAxis });
30
+ return getFormattedValue({
31
+ value: getYRowData((_c = data[0]) === null || _c === void 0 ? void 0 : _c.data, yAxis),
32
+ format,
33
+ });
41
34
  }
42
- return getXRowData((_d = data[0]) === null || _d === void 0 ? void 0 : _d.data, xAxis);
35
+ const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: xAxis });
36
+ return getFormattedValue({
37
+ value: getXRowData((_d = data[0]) === null || _d === void 0 ? void 0 : _d.data, xAxis),
38
+ format,
39
+ });
43
40
  };
41
+ function getDefaultValueFormat({ axis }) {
42
+ switch (axis === null || axis === void 0 ? void 0 : axis.type) {
43
+ case 'linear':
44
+ case 'logarithmic': {
45
+ return {
46
+ type: 'number',
47
+ };
48
+ }
49
+ case 'datetime': {
50
+ return {
51
+ type: 'date',
52
+ format: DEFAULT_DATE_FORMAT,
53
+ };
54
+ }
55
+ default:
56
+ return undefined;
57
+ }
58
+ }
44
59
  export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
45
- const measureValue = getMeasureValue(hovered, xAxis, yAxis);
60
+ const measureValue = getMeasureValue({ data: hovered, xAxis, yAxis });
46
61
  return (React.createElement(React.Fragment, null,
47
62
  measureValue && React.createElement("div", null, measureValue),
48
63
  hovered.map((seriesItem, i) => {
@@ -55,9 +70,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
55
70
  case 'line':
56
71
  case 'area':
57
72
  case 'bar-x': {
73
+ const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: yAxis });
58
74
  const formattedValue = getFormattedValue({
59
75
  value: getYRowData(data, yAxis),
60
- format: valueFormat,
76
+ format,
61
77
  });
62
78
  const value = (React.createElement(React.Fragment, null,
63
79
  series.name,
@@ -70,13 +86,14 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
70
86
  case 'waterfall': {
71
87
  const isTotal = get(data, 'total', false);
72
88
  const subTotalValue = getWaterfallPointSubtotal(data, series);
89
+ const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: yAxis });
73
90
  const subTotal = getFormattedValue({
74
91
  value: subTotalValue,
75
- format: valueFormat,
92
+ format,
76
93
  });
77
94
  const formattedValue = getFormattedValue({
78
95
  value: getYRowData(data, yAxis),
79
- format: valueFormat,
96
+ format,
80
97
  });
81
98
  return (React.createElement("div", { key: `${id}_${get(data, 'x')}` },
82
99
  !isTotal && (React.createElement(React.Fragment, null,
@@ -93,9 +110,10 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
93
110
  subTotal)));
94
111
  }
95
112
  case 'bar-y': {
113
+ const format = valueFormat !== null && valueFormat !== void 0 ? valueFormat : getDefaultValueFormat({ axis: xAxis });
96
114
  const formattedValue = getFormattedValue({
97
115
  value: getXRowData(data, xAxis),
98
- format: valueFormat,
116
+ format,
99
117
  });
100
118
  const value = (React.createElement(React.Fragment, null,
101
119
  series.name,
@@ -110,7 +128,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
110
128
  const seriesData = data;
111
129
  const formattedValue = getFormattedValue({
112
130
  value: seriesData.value,
113
- format: valueFormat,
131
+ format: valueFormat !== null && valueFormat !== void 0 ? valueFormat : { type: 'number' },
114
132
  });
115
133
  return (React.createElement("div", { key: id, className: b('content-row') },
116
134
  React.createElement("div", { className: b('color'), style: { backgroundColor: color } }),
@@ -124,7 +142,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
124
142
  const value = (_a = source.links.find((d) => d.name === (target === null || target === void 0 ? void 0 : target.name))) === null || _a === void 0 ? void 0 : _a.value;
125
143
  const formattedValue = getFormattedValue({
126
144
  value,
127
- format: valueFormat,
145
+ format: valueFormat !== null && valueFormat !== void 0 ? valueFormat : { type: 'number' },
128
146
  });
129
147
  return (React.createElement("div", { key: id, className: b('content-row') },
130
148
  React.createElement("div", { className: b('color'), style: { backgroundColor: source.color } }),
@@ -142,7 +160,7 @@ export const DefaultContent = ({ hovered, xAxis, yAxis, valueFormat }) => {
142
160
  const seriesData = data;
143
161
  const formattedValue = getFormattedValue({
144
162
  value: seriesData.value,
145
- format: valueFormat,
163
+ format: valueFormat !== null && valueFormat !== void 0 ? valueFormat : { type: 'number' },
146
164
  });
147
165
  const value = (React.createElement(React.Fragment, null,
148
166
  React.createElement("span", null,
@@ -8,6 +8,9 @@ const getBottomOffset = (args) => {
8
8
  if (preparedLegend.enabled) {
9
9
  result += preparedLegend.height + preparedLegend.margin;
10
10
  }
11
+ if (!preparedXAxis.visible) {
12
+ return result;
13
+ }
11
14
  if (hasAxisRelatedSeries) {
12
15
  if (preparedXAxis.title.text) {
13
16
  result += preparedXAxis.title.height + preparedXAxis.title.margin;
@@ -6,6 +6,9 @@ export const getBoundsWidth = (args) => {
6
6
  getWidthOccupiedByYAxis({ preparedAxis: preparedYAxis }));
7
7
  };
8
8
  export function getYAxisWidth(axis) {
9
+ if (!(axis === null || axis === void 0 ? void 0 : axis.visible)) {
10
+ return 0;
11
+ }
9
12
  let result = 0;
10
13
  if (axis === null || axis === void 0 ? void 0 : axis.title.text) {
11
14
  result += axis.title.height + axis.title.margin;
@@ -110,6 +110,7 @@ export const getPreparedXAxis = ({ xAxis, series, width, }) => {
110
110
  opacity: get(d, 'opacity', 1),
111
111
  layerPlacement: get(d, 'layerPlacement', 'before'),
112
112
  })),
113
+ visible: get(xAxis, 'visible', true),
113
114
  };
114
115
  const { height, rotation } = getLabelSettings({
115
116
  axis: preparedXAxis,
@@ -116,6 +116,7 @@ export const getPreparedYAxis = ({ series, yAxis, height, }) => {
116
116
  opacity: get(d, 'opacity', 1),
117
117
  layerPlacement: get(d, 'layerPlacement', 'before'),
118
118
  })),
119
+ visible: get(axisItem, 'visible', true),
119
120
  };
120
121
  if (labelsEnabled) {
121
122
  preparedAxis.labels.width = getAxisLabelMaxWidth({ axis: preparedAxis, series });
@@ -4,35 +4,47 @@ import { getUniqId } from '../../utils';
4
4
  import { DEFAULT_DATALABELS_PADDING, DEFAULT_DATALABELS_STYLE } from './constants';
5
5
  import { prepareLegendSymbol } from './utils';
6
6
  export function prepareWaterfallSeries(args) {
7
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o;
7
8
  const { colorScale, series: seriesList, legend } = args;
8
9
  const [, negativeColor, positiveColor] = DEFAULT_PALETTE;
9
- return seriesList.map((series) => {
10
- var _a, _b, _c, _d;
11
- const name = series.name || '';
12
- const color = series.color || colorScale(name);
13
- const prepared = {
14
- type: series.type,
15
- color,
16
- positiveColor: positiveColor,
17
- negativeColor: negativeColor,
18
- name,
19
- id: getUniqId(),
20
- visible: get(series, 'visible', true),
21
- legend: {
22
- enabled: get(series, 'legend.enabled', legend.enabled),
23
- symbol: prepareLegendSymbol(series),
24
- },
25
- data: series.data,
26
- dataLabels: {
27
- enabled: ((_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.enabled) || false,
28
- style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_b = series.dataLabels) === null || _b === void 0 ? void 0 : _b.style),
29
- allowOverlap: ((_c = series.dataLabels) === null || _c === void 0 ? void 0 : _c.allowOverlap) || false,
30
- padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
31
- html: get(series, 'dataLabels.html', false),
32
- format: (_d = series.dataLabels) === null || _d === void 0 ? void 0 : _d.format,
33
- },
34
- cursor: get(series, 'cursor', null),
35
- };
36
- return prepared;
10
+ const series = seriesList[0];
11
+ const common = {
12
+ id: '',
13
+ color: seriesList[0].color || colorScale(seriesList[0].name),
14
+ type: series.type,
15
+ name: series.name,
16
+ visible: get(series, 'visible', true),
17
+ legend: {
18
+ enabled: get(series, 'legend.enabled', legend.enabled),
19
+ symbol: prepareLegendSymbol(series),
20
+ },
21
+ dataLabels: {
22
+ enabled: ((_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.enabled) || true,
23
+ style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_b = series.dataLabels) === null || _b === void 0 ? void 0 : _b.style),
24
+ allowOverlap: ((_c = series.dataLabels) === null || _c === void 0 ? void 0 : _c.allowOverlap) || false,
25
+ padding: get(series, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
26
+ html: get(series, 'dataLabels.html', false),
27
+ format: (_d = series.dataLabels) === null || _d === void 0 ? void 0 : _d.format,
28
+ },
29
+ cursor: get(series, 'cursor', null),
30
+ data: [],
31
+ };
32
+ const positive = Object.assign(Object.assign({}, common), { name: (_g = (_f = (_e = series.legend) === null || _e === void 0 ? void 0 : _e.itemText) === null || _f === void 0 ? void 0 : _f.positive) !== null && _g !== void 0 ? _g : `${series.name} ↑`, id: getUniqId(), color: series.positiveColor || positiveColor, data: [] });
33
+ const negative = Object.assign(Object.assign({}, common), { name: (_k = (_j = (_h = series.legend) === null || _h === void 0 ? void 0 : _h.itemText) === null || _j === void 0 ? void 0 : _j.negative) !== null && _k !== void 0 ? _k : `${series.name} ↓`, id: getUniqId(), color: series.negativeColor || negativeColor, data: [] });
34
+ const totals = Object.assign(Object.assign({}, common), { name: (_o = (_m = (_l = series.legend) === null || _l === void 0 ? void 0 : _l.itemText) === null || _m === void 0 ? void 0 : _m.totals) !== null && _o !== void 0 ? _o : series.name, id: getUniqId(), data: [] });
35
+ series.data.forEach((d, index) => {
36
+ var _a;
37
+ const value = (_a = d === null || d === void 0 ? void 0 : d.y) !== null && _a !== void 0 ? _a : 0;
38
+ const dataItem = Object.assign(Object.assign({}, d), { index });
39
+ if (d === null || d === void 0 ? void 0 : d.total) {
40
+ totals.data.push(dataItem);
41
+ }
42
+ else if (value > 0) {
43
+ positive.data.push(dataItem);
44
+ }
45
+ else if (value < 0) {
46
+ negative.data.push(dataItem);
47
+ }
37
48
  }, []);
49
+ return [positive, negative, totals];
38
50
  }
@@ -243,9 +243,12 @@ export type PreparedTreemapSeries = {
243
243
  };
244
244
  layoutAlgorithm: `${LayoutAlgorithm}`;
245
245
  } & BasePreparedSeries & Omit<TreemapSeries, keyof BasePreparedSeries>;
246
+ export type PreparedWaterfallSeriesData = WaterfallSeriesData & {
247
+ index: number;
248
+ };
246
249
  export type PreparedWaterfallSeries = {
247
250
  type: WaterfallSeries['type'];
248
- data: WaterfallSeriesData[];
251
+ data: PreparedWaterfallSeriesData[];
249
252
  dataLabels: {
250
253
  enabled: boolean;
251
254
  style: BaseTextStyle;
@@ -254,8 +257,6 @@ export type PreparedWaterfallSeries = {
254
257
  html: boolean;
255
258
  format?: ValueFormat;
256
259
  };
257
- positiveColor: string;
258
- negativeColor: string;
259
260
  } & BasePreparedSeries;
260
261
  export type PreparedSankeySeries = {
261
262
  type: SankeySeries['type'];
@@ -5,10 +5,12 @@ import { getFormattedValue } from '../../../utils/chart/format';
5
5
  import { MIN_BAR_GAP, MIN_BAR_WIDTH } from '../constants';
6
6
  import { getXValue, getYValue } from '../utils';
7
7
  function getLabelData(d, plotHeight) {
8
+ var _a, _b;
8
9
  if (!d.series.dataLabels.enabled) {
9
10
  return undefined;
10
11
  }
11
- const text = getFormattedValue(Object.assign({ value: d.data.label || d.subTotal }, d.series.dataLabels));
12
+ const labelValue = (_b = (_a = d.data.label) !== null && _a !== void 0 ? _a : d.data.y) !== null && _b !== void 0 ? _b : d.subTotal;
13
+ const text = getFormattedValue(Object.assign({ value: labelValue }, d.series.dataLabels));
12
14
  const style = d.series.dataLabels.style;
13
15
  const { maxHeight: height, maxWidth: width } = getLabelsSize({ labels: [text], style });
14
16
  let y;
@@ -60,7 +62,7 @@ export const prepareWaterfallData = (args) => {
60
62
  acc.push(...s.data.map((d) => ({ data: d, series: s })));
61
63
  return acc;
62
64
  }, []);
63
- const data = sortBy(flattenData, (d) => d.data.x);
65
+ const data = sortBy(flattenData, (d) => d.data.index);
64
66
  const bandWidth = getBandWidth({
65
67
  series,
66
68
  xAxis,
@@ -75,6 +75,8 @@ export interface ChartAxis {
75
75
  maxPadding?: number;
76
76
  /** An array of lines stretching across the plot area, marking a specific value */
77
77
  plotLines?: AxisPlotLine[];
78
+ /** Whether axis, including axis title, line, ticks and labels, should be visible. */
79
+ visible?: boolean;
78
80
  }
79
81
  export interface ChartXAxis extends ChartAxis {
80
82
  }
@@ -36,5 +36,14 @@ export interface WaterfallSeries<T = MeaningfulAny> extends BaseSeries {
36
36
  /** Individual series legend options. Has higher priority than legend options in widget data. */
37
37
  legend?: ChartLegend & {
38
38
  symbol?: RectLegendSymbolOptions;
39
+ /** The legend item text for positive, negative values and totals. */
40
+ itemText?: {
41
+ /** The legend item text for positive values. */
42
+ positive?: string;
43
+ /** The legend item text for negative values. */
44
+ negative?: string;
45
+ /** The legend item text for totals. */
46
+ totals?: string;
47
+ };
39
48
  };
40
49
  }
@@ -19,6 +19,7 @@ type AxisBottomArgs = {
19
19
  size: number;
20
20
  color?: string;
21
21
  };
22
+ leftmostLimit?: number;
22
23
  };
23
24
  export declare function axisBottom(args: AxisBottomArgs): (selection: Selection<SVGGElement, unknown, null, undefined>) => void;
24
25
  export {};
@@ -16,7 +16,7 @@ function addDomain(selection, options) {
16
16
  }
17
17
  }
18
18
  export function axisBottom(args) {
19
- const { scale, ticks: { labelFormat = (value) => String(value), labelsPaddings = 0, labelsMargin = 0, labelsMaxWidth = Infinity, labelsStyle, labelsLineHeight, items: tickItems, count: ticksCount, maxTickCount, rotation = 0, tickColor, }, domain, } = args;
19
+ const { leftmostLimit = 0, scale, ticks: { labelFormat = (value) => String(value), labelsPaddings = 0, labelsMargin = 0, labelsMaxWidth = Infinity, labelsStyle, labelsLineHeight, items: tickItems, count: ticksCount, maxTickCount, rotation = 0, tickColor, }, domain, } = args;
20
20
  const offset = getXAxisOffset();
21
21
  const position = getXTickPosition({ scale, offset });
22
22
  const values = getXAxisItems({ scale, count: ticksCount, maxCount: maxTickCount });
@@ -103,6 +103,21 @@ export function axisBottom(args) {
103
103
  .remove();
104
104
  // add an ellipsis to the labels that go beyond the boundaries of the chart
105
105
  labels.each(function (_d, i, nodes) {
106
+ if (i === 0) {
107
+ const currentElement = this;
108
+ const text = select(currentElement);
109
+ const currentElementPosition = currentElement.getBoundingClientRect();
110
+ const nextElement = nodes[i + 1];
111
+ const nextElementPosition = nextElement === null || nextElement === void 0 ? void 0 : nextElement.getBoundingClientRect();
112
+ if (currentElementPosition.left < leftmostLimit) {
113
+ const remainSpace = nextElementPosition.left -
114
+ currentElementPosition.right +
115
+ x -
116
+ labelsMargin;
117
+ text.attr('text-anchor', 'start');
118
+ setEllipsisForOverflowText(text, remainSpace);
119
+ }
120
+ }
106
121
  if (i === nodes.length - 1) {
107
122
  const currentElement = this;
108
123
  const prevElement = nodes[i - 1];