@gravity-ui/charts 1.0.1 → 1.2.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 (101) hide show
  1. package/dist/cjs/components/Axis/AxisX.d.ts +1 -0
  2. package/dist/cjs/components/Axis/AxisX.js +57 -12
  3. package/dist/cjs/components/Axis/AxisY.d.ts +1 -0
  4. package/dist/cjs/components/Axis/AxisY.js +83 -12
  5. package/dist/cjs/components/ChartInner/index.js +3 -3
  6. package/dist/cjs/components/ChartInner/styles.css +2 -0
  7. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +3 -0
  8. package/dist/cjs/components/ChartInner/useChartInnerProps.js +14 -3
  9. package/dist/cjs/components/Legend/index.js +2 -2
  10. package/dist/cjs/components/Title/index.js +1 -1
  11. package/dist/cjs/components/Tooltip/DefaultContent.js +41 -23
  12. package/dist/cjs/hooks/useChartDimensions/index.js +3 -0
  13. package/dist/cjs/hooks/useChartDimensions/utils.js +3 -0
  14. package/dist/cjs/hooks/useChartOptions/types.d.ts +5 -3
  15. package/dist/cjs/hooks/useChartOptions/x-axis.js +8 -0
  16. package/dist/cjs/hooks/useChartOptions/y-axis.js +8 -0
  17. package/dist/cjs/hooks/useSeries/prepare-waterfall.js +40 -28
  18. package/dist/cjs/hooks/useSeries/types.d.ts +4 -3
  19. package/dist/cjs/hooks/useShapes/HtmlLayer.js +2 -1
  20. package/dist/cjs/hooks/useShapes/area/prepare-data.js +1 -0
  21. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +1 -0
  22. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +1 -0
  23. package/dist/cjs/hooks/useShapes/line/prepare-data.js +1 -0
  24. package/dist/cjs/hooks/useShapes/pie/index.js +1 -1
  25. package/dist/cjs/hooks/useShapes/pie/prepare-data.js +1 -0
  26. package/dist/cjs/hooks/useShapes/radar/prepare-data.js +1 -0
  27. package/dist/cjs/hooks/useShapes/treemap/index.js +1 -1
  28. package/dist/cjs/hooks/useShapes/treemap/prepare-data.js +3 -2
  29. package/dist/cjs/hooks/useShapes/waterfall/prepare-data.js +4 -2
  30. package/dist/cjs/i18n/keysets/en.json +3 -1
  31. package/dist/cjs/i18n/keysets/ru.json +3 -1
  32. package/dist/cjs/libs/chart-error/index.d.ts +1 -0
  33. package/dist/cjs/libs/chart-error/index.js +1 -0
  34. package/dist/cjs/types/chart/axis.d.ts +32 -7
  35. package/dist/cjs/types/chart/waterfall.d.ts +9 -0
  36. package/dist/cjs/types/chart-ui.d.ts +1 -0
  37. package/dist/cjs/utils/chart/axis-generators/bottom.d.ts +1 -0
  38. package/dist/cjs/utils/chart/axis-generators/bottom.js +16 -1
  39. package/dist/cjs/utils/chart/axis.d.ts +12 -1
  40. package/dist/cjs/utils/chart/axis.js +35 -0
  41. package/dist/cjs/utils/chart/get-closest-data.js +23 -13
  42. package/dist/cjs/utils/chart/index.d.ts +2 -1
  43. package/dist/cjs/utils/chart/index.js +5 -5
  44. package/dist/cjs/utils/chart/series/waterfall.d.ts +2 -2
  45. package/dist/cjs/utils/chart/series/waterfall.js +1 -7
  46. package/dist/cjs/utils/chart/types.d.ts +1 -0
  47. package/dist/cjs/utils/chart/types.js +1 -0
  48. package/dist/cjs/utils/chart-ui/pie-center-text.d.ts +1 -0
  49. package/dist/cjs/utils/chart-ui/pie-center-text.js +3 -1
  50. package/dist/cjs/validation/index.js +144 -0
  51. package/dist/esm/components/Axis/AxisX.d.ts +1 -0
  52. package/dist/esm/components/Axis/AxisX.js +57 -12
  53. package/dist/esm/components/Axis/AxisY.d.ts +1 -0
  54. package/dist/esm/components/Axis/AxisY.js +83 -12
  55. package/dist/esm/components/ChartInner/index.js +3 -3
  56. package/dist/esm/components/ChartInner/styles.css +2 -0
  57. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +3 -0
  58. package/dist/esm/components/ChartInner/useChartInnerProps.js +14 -3
  59. package/dist/esm/components/Legend/index.js +2 -2
  60. package/dist/esm/components/Title/index.js +1 -1
  61. package/dist/esm/components/Tooltip/DefaultContent.js +41 -23
  62. package/dist/esm/hooks/useChartDimensions/index.js +3 -0
  63. package/dist/esm/hooks/useChartDimensions/utils.js +3 -0
  64. package/dist/esm/hooks/useChartOptions/types.d.ts +5 -3
  65. package/dist/esm/hooks/useChartOptions/x-axis.js +8 -0
  66. package/dist/esm/hooks/useChartOptions/y-axis.js +8 -0
  67. package/dist/esm/hooks/useSeries/prepare-waterfall.js +40 -28
  68. package/dist/esm/hooks/useSeries/types.d.ts +4 -3
  69. package/dist/esm/hooks/useShapes/HtmlLayer.js +2 -1
  70. package/dist/esm/hooks/useShapes/area/prepare-data.js +1 -0
  71. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +1 -0
  72. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +1 -0
  73. package/dist/esm/hooks/useShapes/line/prepare-data.js +1 -0
  74. package/dist/esm/hooks/useShapes/pie/index.js +1 -1
  75. package/dist/esm/hooks/useShapes/pie/prepare-data.js +1 -0
  76. package/dist/esm/hooks/useShapes/radar/prepare-data.js +1 -0
  77. package/dist/esm/hooks/useShapes/treemap/index.js +1 -1
  78. package/dist/esm/hooks/useShapes/treemap/prepare-data.js +3 -2
  79. package/dist/esm/hooks/useShapes/waterfall/prepare-data.js +4 -2
  80. package/dist/esm/i18n/keysets/en.json +3 -1
  81. package/dist/esm/i18n/keysets/ru.json +3 -1
  82. package/dist/esm/libs/chart-error/index.d.ts +1 -0
  83. package/dist/esm/libs/chart-error/index.js +1 -0
  84. package/dist/esm/types/chart/axis.d.ts +32 -7
  85. package/dist/esm/types/chart/waterfall.d.ts +9 -0
  86. package/dist/esm/types/chart-ui.d.ts +1 -0
  87. package/dist/esm/utils/chart/axis-generators/bottom.d.ts +1 -0
  88. package/dist/esm/utils/chart/axis-generators/bottom.js +16 -1
  89. package/dist/esm/utils/chart/axis.d.ts +12 -1
  90. package/dist/esm/utils/chart/axis.js +35 -0
  91. package/dist/esm/utils/chart/get-closest-data.js +23 -13
  92. package/dist/esm/utils/chart/index.d.ts +2 -1
  93. package/dist/esm/utils/chart/index.js +5 -5
  94. package/dist/esm/utils/chart/series/waterfall.d.ts +2 -2
  95. package/dist/esm/utils/chart/series/waterfall.js +1 -7
  96. package/dist/esm/utils/chart/types.d.ts +1 -0
  97. package/dist/esm/utils/chart/types.js +1 -0
  98. package/dist/esm/utils/chart-ui/pie-center-text.d.ts +1 -0
  99. package/dist/esm/utils/chart-ui/pie-center-text.js +3 -1
  100. package/dist/esm/validation/index.js +144 -0
  101. package/package.json +1 -1
@@ -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'];
@@ -17,6 +17,7 @@ export const HtmlLayer = (props) => {
17
17
  return null;
18
18
  }
19
19
  return (React.createElement(Portal, { container: htmlLayout }, items.map((item, index) => {
20
- return (React.createElement("div", { key: index, dangerouslySetInnerHTML: { __html: item.content }, style: { position: 'absolute', left: item.x, top: item.y } }));
20
+ const style = Object.assign(Object.assign({}, item.style), { position: 'absolute', left: item.x, top: item.y });
21
+ return (React.createElement("div", { key: index, dangerouslySetInnerHTML: { __html: item.content }, style: style }));
21
22
  })));
22
23
  };
@@ -111,6 +111,7 @@ export const prepareAreaData = (args) => {
111
111
  y: l.y,
112
112
  content: l.text,
113
113
  size: { width: labelSize.maxWidth, height: labelSize.maxHeight },
114
+ style,
114
115
  };
115
116
  });
116
117
  htmlElements.push(...htmlLabels);
@@ -139,6 +139,7 @@ export const prepareBarXData = (args) => {
139
139
  y: label.y,
140
140
  content: label.text,
141
141
  size: label.size,
142
+ style: label.style,
142
143
  });
143
144
  }
144
145
  else {
@@ -71,6 +71,7 @@ function setLabel(prepared) {
71
71
  y: y - height / 2,
72
72
  content,
73
73
  size: { width, height },
74
+ style: dataLabels.style,
74
75
  });
75
76
  }
76
77
  else {
@@ -35,6 +35,7 @@ function getHtmlLabel(point, series, xMax) {
35
35
  y: Math.max(0, point.y - series.dataLabels.padding - size.maxHeight),
36
36
  content,
37
37
  size: { width: size.maxWidth, height: size.maxHeight },
38
+ style: series.dataLabels.style,
38
39
  };
39
40
  }
40
41
  export const prepareLineData = (args) => {
@@ -75,7 +75,7 @@ export function PieSeriesShapes(args) {
75
75
  .selectAll('text')
76
76
  .data((pieData) => pieData.labels)
77
77
  .join('text')
78
- .text((d) => d.text)
78
+ .html((d) => d.text)
79
79
  .attr('class', b('label'))
80
80
  .attr('x', (d) => d.x)
81
81
  .attr('y', (d) => d.y)
@@ -184,6 +184,7 @@ export function preparePieData(args) {
184
184
  y: Math.max(0, data.center[1] + label.y),
185
185
  content: label.text,
186
186
  size: label.size,
187
+ style: label.style,
187
188
  });
188
189
  }
189
190
  else {
@@ -146,6 +146,7 @@ export function prepareRadarData(args) {
146
146
  y: label.y,
147
147
  content: label.text,
148
148
  size: label.size,
149
+ style: label.style,
149
150
  }));
150
151
  }
151
152
  }
@@ -38,7 +38,7 @@ export const TreemapSeriesShape = (props) => {
38
38
  .selectAll('tspan')
39
39
  .data(labelData)
40
40
  .join('text')
41
- .text((d) => d.text)
41
+ .html((d) => d.text)
42
42
  .attr('class', b('label'))
43
43
  .attr('x', (d) => d.x)
44
44
  .attr('y', (d) => d.y)
@@ -94,10 +94,11 @@ export function prepareTreemapData(args) {
94
94
  let labelData = [];
95
95
  const htmlElements = [];
96
96
  if ((_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.enabled) {
97
- const { html } = series.dataLabels;
97
+ const { html, style: dataLabelsStyle } = series.dataLabels;
98
98
  const labels = getLabels({ data: leaves, options: series.dataLabels });
99
99
  if (html) {
100
- htmlElements.push(...labels);
100
+ const htmlItems = labels.map((l) => (Object.assign({ style: dataLabelsStyle }, l)));
101
+ htmlElements.push(...htmlItems);
101
102
  }
102
103
  else {
103
104
  labelData = labels;
@@ -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,
@@ -11,6 +11,8 @@
11
11
  "label_invalid-series-property": "It seems you are trying to use inappropriate value for \"{{key}}\", or defined it incorrectly. Available values: [{{values}}].",
12
12
  "label_invalid-treemap-redundant-value": "It seems you are trying to set \"value\" for container node. Check node with this properties: { id: \"{{id}}\", name: \"{{name}}\" }",
13
13
  "label_invalid-treemap-missing-value": "It seems you are trying to use node without \"value\". Check node with this properties: { id: \"{{id}}\", name: \"{{name}}\" }",
14
- "label_invalid-y-axis-index": "It seems you are trying to use inappropriate index for Y axis: \"{{index}}\""
14
+ "label_invalid-y-axis-index": "It seems you are trying to use inappropriate index for Y axis: \"{{index}}\"",
15
+ "label_invalid-axis-plot-band-option": "It seems you are trying to use inappropriate type for \"{{axis}}\" axis plot band option: \"{{option}}\"",
16
+ "label_axis-plot-band-options-not-equal": "It seems you are trying to use different type for \"{{axis}}\" axis plot band options"
15
17
  }
16
18
  }
@@ -11,6 +11,8 @@
11
11
  "label_invalid-series-property": "Похоже, что вы пытаетесь использовать недопустимое значение для \"{{key}}\", или указали его неверно. Доступные значения: [{{values}}].",
12
12
  "label_invalid-treemap-redundant-value": "Похоже, что вы пытаетесь установить значение \"value\" для узла, используемого в качестве контейнера. Проверьте узел с этими свойствами: { id: \"{{id}}\", name: \"{{name}}\" }",
13
13
  "label_invalid-treemap-missing-value": "Похоже, что вы пытаетесь использовать узел без значения \"value\". Проверьте узел с этими свойствами: { id: \"{{id}}\", name: \"{{name}}\" }",
14
- "label_invalid-y-axis-index": "Похоже, что вы пытаетесь использовать некорректный индекс для оси Y: \"{{index}}\""
14
+ "label_invalid-y-axis-index": "Похоже, что вы пытаетесь использовать некорректный индекс для оси Y: \"{{index}}\"",
15
+ "label_invalid-axis-plot-band-option": "Похоже, что вы пытаетесь использовать некорректный тип для параметра полосы: \"{{option}}\" для оси \"{{axis}}\"",
16
+ "label_axis-plot-band-options-not-equal": "Похоже, что вы пытаетесь использовать разные типы для для параметра полосы для оси \"{{axis}}\""
15
17
  }
16
18
  }
@@ -7,6 +7,7 @@ export declare const CHART_ERROR_CODE: {
7
7
  NO_DATA: string;
8
8
  INVALID_DATA: string;
9
9
  UNKNOWN: string;
10
+ INVALID_OPTION_TYPE: string;
10
11
  };
11
12
  export declare class ChartError extends Error {
12
13
  readonly code: number | string;
@@ -2,6 +2,7 @@ export const CHART_ERROR_CODE = {
2
2
  NO_DATA: 'ERR.CK.NO_DATA',
3
3
  INVALID_DATA: 'ERR.CK.INVALID_DATA',
4
4
  UNKNOWN: 'ERR.CK.UNKNOWN_ERROR',
5
+ INVALID_OPTION_TYPE: 'ERR.CK.INVALID_OPTION_TYPE',
5
6
  };
6
7
  export class ChartError extends Error {
7
8
  constructor({ originalError, message, code = CHART_ERROR_CODE.UNKNOWN } = {}) {
@@ -75,10 +75,27 @@ 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
+ /** An array of colored bands stretching across the plot area marking an interval on the axis. */
79
+ plotBands?: AxisPlotBand[];
80
+ /** Whether axis, including axis title, line, ticks and labels, should be visible. */
81
+ visible?: boolean;
78
82
  }
79
83
  export interface ChartXAxis extends ChartAxis {
80
84
  }
81
- export interface AxisPlotLine {
85
+ export type PlotLayerPlacement = 'before' | 'after';
86
+ export interface AxisPlot {
87
+ /** Place the line behind or above the chart. */
88
+ layerPlacement?: PlotLayerPlacement;
89
+ /** The color of the plot line (hex, rgba). */
90
+ color?: string;
91
+ /**
92
+ * Individual opacity for the line.
93
+ *
94
+ * @default 1
95
+ * */
96
+ opacity?: number;
97
+ }
98
+ export interface AxisPlotLine extends AxisPlot {
82
99
  /** The position of the line in axis units. */
83
100
  value?: number;
84
101
  /** The color of the plot line (hex, rgba). */
@@ -90,14 +107,22 @@ export interface AxisPlotLine {
90
107
  width?: number;
91
108
  /** Option for line stroke style. */
92
109
  dashStyle?: `${DashStyle}`;
110
+ }
111
+ export interface AxisPlotBand extends AxisPlot {
93
112
  /**
94
- * Individual opacity for the line.
113
+ * The start position of the plot band in axis units.
95
114
  *
96
- * @default 1
97
- * */
98
- opacity?: number;
99
- /** Place the line behind or above the chart. */
100
- layerPlacement?: 'before' | 'after';
115
+ * Can be a number, a string (e.g., a category), or a timestamp if representing a date.
116
+ * When representing a date, the value **must be a timestamp** (number of milliseconds since Unix epoch).
117
+ */
118
+ from: number | string;
119
+ /**
120
+ * The end position of the plot band in axis units.
121
+ *
122
+ * Can be a number, a string (e.g., a category), or a timestamp if representing a date.
123
+ * When representing a date, the value **must be a timestamp** (number of milliseconds since Unix epoch).
124
+ */
125
+ to: number | string;
101
126
  }
102
127
  export interface ChartYAxis extends ChartAxis {
103
128
  /** Axis location.
@@ -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
  }
@@ -22,6 +22,7 @@ export interface HtmlItem {
22
22
  width: number;
23
23
  height: number;
24
24
  };
25
+ style?: BaseTextStyle;
25
26
  }
26
27
  export interface ShapeDataWithHtmlItems {
27
28
  htmlElements: HtmlItem[];
@@ -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];
@@ -1,6 +1,7 @@
1
1
  import type { AxisDomain, AxisScale, ScaleBand } from 'd3';
2
- import type { PreparedAxis, PreparedSplit } from '../../hooks';
2
+ import type { PreparedAxis, PreparedAxisPlotBand, PreparedSplit } from '../../hooks';
3
3
  import type { TextRow } from './text';
4
+ import type { AxisDirection } from './types';
4
5
  export declare function getTicksCount({ axis, range }: {
5
6
  axis: PreparedAxis;
6
7
  range: number;
@@ -29,3 +30,13 @@ export declare function getAxisTitleRows(args: {
29
30
  axis: PreparedAxis;
30
31
  textMaxWidth: number;
31
32
  }): TextRow[];
33
+ interface GetBandsPositionArgs {
34
+ band: PreparedAxisPlotBand;
35
+ axisScale: AxisScale<AxisDomain>;
36
+ axis: AxisDirection;
37
+ }
38
+ export declare function getBandsPosition(args: GetBandsPositionArgs): {
39
+ from: number;
40
+ to: number;
41
+ };
42
+ export {};
@@ -1,3 +1,4 @@
1
+ import clamp from 'lodash/clamp';
1
2
  import { wrapText } from './text';
2
3
  export function getTicksCount({ axis, range }) {
3
4
  let ticksCount;
@@ -69,3 +70,37 @@ export function getAxisTitleRows(args) {
69
70
  return acc;
70
71
  }, []);
71
72
  }
73
+ export function getBandsPosition(args) {
74
+ var _a, _b, _c;
75
+ const { band, axisScale } = args;
76
+ const scalePosTo = axisScale(band.to);
77
+ const scalePosFrom = axisScale(band.from);
78
+ const isX = args.axis === 'x';
79
+ if (scalePosTo !== undefined && scalePosFrom !== undefined) {
80
+ return {
81
+ from: Math.max(scalePosFrom, 0),
82
+ to: Math.max(scalePosTo, 0),
83
+ };
84
+ }
85
+ if (typeof band.from !== 'number' || typeof band.to !== 'number') {
86
+ throw new Error('Filed to create plot band');
87
+ }
88
+ const category = axisScale.domain();
89
+ const bandwidth = (_b = (_a = axisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(axisScale)) !== null && _b !== void 0 ? _b : 1;
90
+ const halfBandwidth = bandwidth / 2;
91
+ const calcPosition = (value) => {
92
+ var _a, _b;
93
+ if (value >= category.length) {
94
+ return ((_a = axisScale(category[category.length - 1])) !== null && _a !== void 0 ? _a : 0) + halfBandwidth * (isX ? 1 : -1);
95
+ }
96
+ return (((_b = axisScale(category[clamp(Math.floor(value), 0, category.length - 1)])) !== null && _b !== void 0 ? _b : 0) +
97
+ bandwidth * (value - Math.floor(Math.abs(value))) * (isX ? 1 : -1));
98
+ };
99
+ const to = calcPosition(band.to);
100
+ const from = calcPosition(band.from);
101
+ const maxPos = ((_c = axisScale(category[isX ? category.length - 1 : 0])) !== null && _c !== void 0 ? _c : 0) + halfBandwidth;
102
+ return {
103
+ from: clamp(from, -halfBandwidth, maxPos),
104
+ to: clamp(to, -halfBandwidth, maxPos),
105
+ };
106
+ }
@@ -1,30 +1,40 @@
1
1
  import { Delaunay, bisector, sort } from 'd3';
2
2
  import get from 'lodash/get';
3
3
  import groupBy from 'lodash/groupBy';
4
- function getClosestPointsByXValue(x, y, points) {
4
+ function getClosestYIndex(items, y) {
5
5
  var _a, _b, _c, _d;
6
- const sorted = sort(points, (p) => p.x);
7
- const closestXIndex = bisector((p) => p.x).center(sorted, x);
8
- if (closestXIndex === -1) {
9
- return [];
10
- }
11
- const closestX = sorted[closestXIndex].x;
12
- const closestPoints = sort(points.filter((p) => p.x === closestX), (p) => p.y0);
13
6
  let closestYIndex = -1;
14
- if (y < ((_a = closestPoints[0]) === null || _a === void 0 ? void 0 : _a.y0)) {
7
+ if (y < ((_a = items[0]) === null || _a === void 0 ? void 0 : _a.y0)) {
15
8
  closestYIndex = 0;
16
9
  }
17
- else if (y > ((_b = closestPoints[closestPoints.length - 1]) === null || _b === void 0 ? void 0 : _b.y1)) {
18
- closestYIndex = closestPoints.length - 1;
10
+ else if (y > ((_b = items[items.length - 1]) === null || _b === void 0 ? void 0 : _b.y1)) {
11
+ closestYIndex = items.length - 1;
19
12
  }
20
13
  else {
21
- closestYIndex = closestPoints.findIndex((p) => y > p.y0 && y < p.y1);
14
+ closestYIndex = items.findIndex((p) => y > p.y0 && y < p.y1);
22
15
  if (closestYIndex === -1) {
23
- const sortedY = sort(closestPoints.map((p, index) => ({ index, y: p.y1 + (p.y0 - p.y1) / 2 })), (p) => p.y);
16
+ const sortedY = sort(items.map((p, index) => ({ index, y: p.y1 + (p.y0 - p.y1) / 2 })), (p) => p.y);
24
17
  const sortedYIndex = bisector((p) => p.y).center(sortedY, y);
25
18
  closestYIndex = (_d = (_c = sortedY[sortedYIndex]) === null || _c === void 0 ? void 0 : _c.index) !== null && _d !== void 0 ? _d : -1;
26
19
  }
27
20
  }
21
+ return closestYIndex;
22
+ }
23
+ function getClosestPointsByXValue(x, y, points) {
24
+ const sorted = sort(points, (p) => p.x);
25
+ const closestXIndex = bisector((p) => p.x).center(sorted, x);
26
+ if (closestXIndex === -1) {
27
+ return [];
28
+ }
29
+ const closestX = sorted[closestXIndex].x;
30
+ const filtered = points.filter((p) => p.x === closestX);
31
+ const groupedBySeries = Object.values(groupBy(filtered, (p) => get(p.series, 'id'))).map((items) => {
32
+ const sortedByY = sort(items, (p) => p.y0);
33
+ const index = getClosestYIndex(sortedByY, y);
34
+ return sortedByY[index === -1 ? 0 : index];
35
+ });
36
+ const closestPoints = sort(groupedBySeries, (p) => p.y0);
37
+ const closestYIndex = getClosestYIndex(closestPoints, y);
28
38
  return closestPoints.map((p, i) => ({
29
39
  data: p.data,
30
40
  series: p.series,
@@ -1,6 +1,7 @@
1
1
  import type { AxisDomain } from 'd3';
2
2
  import type { PreparedAxis } from '../../hooks';
3
3
  import type { BaseTextStyle, ChartSeries, ChartSeriesData } from '../../types';
4
+ import type { AxisDirection } from './types';
4
5
  export * from './math';
5
6
  export * from './text';
6
7
  export * from './time';
@@ -12,7 +13,6 @@ export * from './series';
12
13
  export * from './color';
13
14
  export declare const CHART_SERIES_WITH_VOLUME_ON_Y_AXIS: ChartSeries['type'][];
14
15
  export declare const CHART_SERIES_WITH_VOLUME_ON_X_AXIS: ChartSeries['type'][];
15
- export type AxisDirection = 'x' | 'y';
16
16
  type UnknownSeries = {
17
17
  type: ChartSeries['type'];
18
18
  data: unknown;
@@ -76,3 +76,4 @@ export declare const getDataCategoryValue: (args: {
76
76
  data: ChartSeriesData;
77
77
  }) => string;
78
78
  export declare function getClosestPointsRange(axis: PreparedAxis, points: AxisDomain[]): number | undefined;
79
+ export { AxisDirection };
@@ -2,6 +2,7 @@ import { dateTime } from '@gravity-ui/date-utils';
2
2
  import { group, select } from 'd3';
3
3
  import get from 'lodash/get';
4
4
  import isNil from 'lodash/isNil';
5
+ import sortBy from 'lodash/sortBy';
5
6
  import { DEFAULT_AXIS_LABEL_FONT_SIZE } from '../../constants';
6
7
  import { getSeriesStackId } from '../../hooks/useSeries/utils';
7
8
  import { formatNumber, getNumberUnitRate } from '../../libs/format-number';
@@ -100,11 +101,10 @@ export const getDomainDataYBySeries = (series) => {
100
101
  }
101
102
  case 'waterfall': {
102
103
  let yValue = 0;
103
- seriesList.forEach((s) => {
104
- s.data.forEach((d) => {
105
- yValue += Number(d.y) || 0;
106
- acc.push(yValue);
107
- });
104
+ const points = seriesList.map((s) => s.data).flat();
105
+ sortBy(points, (p) => p.index).forEach((d) => {
106
+ yValue += Number(d.y) || 0;
107
+ acc.push(yValue);
108
108
  });
109
109
  break;
110
110
  }
@@ -1,4 +1,4 @@
1
- import type { PreparedWaterfallSeries } from '../../../hooks';
1
+ import type { PreparedWaterfallSeries, PreparedWaterfallSeriesData } from '../../../hooks';
2
2
  import type { WaterfallSeriesData } from '../../../types';
3
3
  export declare function getWaterfallPointColor(point: WaterfallSeriesData, series: PreparedWaterfallSeries): string;
4
- export declare function getWaterfallPointSubtotal(point: WaterfallSeriesData, series: PreparedWaterfallSeries): number | null;
4
+ export declare function getWaterfallPointSubtotal(point: PreparedWaterfallSeriesData, series: PreparedWaterfallSeries): number | null;
@@ -2,13 +2,7 @@ export function getWaterfallPointColor(point, series) {
2
2
  if (point.color) {
3
3
  return point.color;
4
4
  }
5
- if (point.total) {
6
- return series.color;
7
- }
8
- if (Number(point.y) > 0) {
9
- return series.positiveColor;
10
- }
11
- return series.negativeColor;
5
+ return series.color;
12
6
  }
13
7
  export function getWaterfallPointSubtotal(point, series) {
14
8
  const pointIndex = series.data.indexOf(point);
@@ -0,0 +1 @@
1
+ export type AxisDirection = 'x' | 'y';
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,6 @@
1
1
  export declare function pieCenterText(text: string, options?: {
2
2
  padding?: number;
3
+ color?: string;
3
4
  }): ((args: {
4
5
  series: {
5
6
  innerRadius: number;
@@ -7,6 +7,7 @@ export function pieCenterText(text, options) {
7
7
  return undefined;
8
8
  }
9
9
  const padding = get(options, 'padding', 12);
10
+ const color = get(options, 'color', 'currentColor');
10
11
  return function (args) {
11
12
  let fontSize = MAX_FONT_SIZE;
12
13
  const textSize = getLabelsSize({ labels: [text], style: { fontSize: `${fontSize}px` } });
@@ -17,7 +18,8 @@ export function pieCenterText(text, options) {
17
18
  .text(text)
18
19
  .attr('text-anchor', 'middle')
19
20
  .attr('alignment-baseline', 'middle')
20
- .style('font-size', `${fontSize}px`);
21
+ .style('font-size', `${fontSize}px`)
22
+ .style('fill', color);
21
23
  return container.node();
22
24
  };
23
25
  }