@gravity-ui/charts 1.15.0 → 1.17.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 (141) hide show
  1. package/dist/{esm/components/Axis → cjs/components/AxisX}/AxisX.js +1 -15
  2. package/dist/cjs/components/{Axis → AxisY}/AxisY.d.ts +2 -10
  3. package/dist/cjs/components/AxisY/AxisY.js +173 -0
  4. package/dist/cjs/components/AxisY/prepare-axis-data.d.ts +9 -0
  5. package/dist/cjs/components/AxisY/prepare-axis-data.js +306 -0
  6. package/dist/cjs/components/AxisY/styles.css +15 -0
  7. package/dist/cjs/components/AxisY/types.d.ts +81 -0
  8. package/dist/cjs/components/AxisY/types.js +1 -0
  9. package/dist/cjs/components/AxisY/utils.d.ts +12 -0
  10. package/dist/cjs/components/AxisY/utils.js +71 -0
  11. package/dist/cjs/components/ChartInner/index.js +31 -3
  12. package/dist/cjs/components/ChartInner/useChartInnerProps.js +13 -1
  13. package/dist/cjs/components/Tooltip/ChartTooltipContent.d.ts +4 -2
  14. package/dist/cjs/components/Tooltip/ChartTooltipContent.js +2 -2
  15. package/dist/cjs/components/Tooltip/DefaultTooltipContent/Row.d.ts +2 -1
  16. package/dist/cjs/components/Tooltip/DefaultTooltipContent/Row.js +3 -3
  17. package/dist/cjs/components/Tooltip/DefaultTooltipContent/{RowTotals.d.ts → RowWithAggregation.d.ts} +2 -1
  18. package/dist/cjs/components/Tooltip/DefaultTooltipContent/{RowTotals.js → RowWithAggregation.js} +3 -3
  19. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.d.ts +4 -2
  20. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +174 -114
  21. package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.d.ts +7 -1
  22. package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.js +12 -7
  23. package/dist/cjs/components/Tooltip/index.js +1 -1
  24. package/dist/cjs/components/Tooltip/styles.css +11 -1
  25. package/dist/cjs/constants/date.d.ts +1 -0
  26. package/dist/cjs/constants/date.js +1 -0
  27. package/dist/cjs/constants/index.d.ts +1 -0
  28. package/dist/cjs/constants/index.js +1 -0
  29. package/dist/cjs/hooks/useChartOptions/types.d.ts +7 -1
  30. package/dist/cjs/hooks/useChartOptions/x-axis.js +5 -5
  31. package/dist/cjs/hooks/useChartOptions/y-axis.d.ts +3 -1
  32. package/dist/cjs/hooks/useChartOptions/y-axis.js +20 -20
  33. package/dist/cjs/hooks/useCrosshair/index.js +2 -1
  34. package/dist/cjs/hooks/useSeries/prepare-area.js +1 -0
  35. package/dist/cjs/hooks/useSeries/prepare-bar-x.js +1 -0
  36. package/dist/cjs/hooks/useSeries/prepare-bar-y.d.ts +1 -0
  37. package/dist/cjs/hooks/useSeries/prepare-bar-y.js +1 -0
  38. package/dist/cjs/hooks/useSeries/prepare-line.js +1 -0
  39. package/dist/cjs/hooks/useSeries/prepare-pie.js +1 -0
  40. package/dist/cjs/hooks/useSeries/prepare-radar.js +1 -0
  41. package/dist/cjs/hooks/useSeries/prepare-sankey.js +1 -0
  42. package/dist/cjs/hooks/useSeries/prepare-scatter.js +1 -0
  43. package/dist/cjs/hooks/useSeries/prepare-treemap.js +1 -0
  44. package/dist/cjs/hooks/useSeries/prepare-waterfall.js +1 -0
  45. package/dist/cjs/hooks/useSeries/types.d.ts +2 -1
  46. package/dist/cjs/i18n/keysets/en.json +2 -1
  47. package/dist/cjs/i18n/keysets/ru.json +2 -1
  48. package/dist/cjs/index.d.ts +1 -1
  49. package/dist/cjs/index.js +1 -1
  50. package/dist/cjs/types/chart/axis.d.ts +11 -1
  51. package/dist/cjs/types/chart/base.d.ts +14 -0
  52. package/dist/cjs/types/chart/tooltip.d.ts +5 -1
  53. package/dist/cjs/utils/chart/axis-generators/bottom.js +3 -3
  54. package/dist/cjs/utils/chart/axis.d.ts +14 -8
  55. package/dist/cjs/utils/chart/axis.js +34 -9
  56. package/dist/cjs/utils/chart/format.d.ts +9 -2
  57. package/dist/cjs/utils/chart/format.js +40 -5
  58. package/dist/cjs/utils/chart/index.d.ts +6 -17
  59. package/dist/cjs/utils/chart/index.js +6 -43
  60. package/dist/cjs/utils/chart/text.d.ts +2 -1
  61. package/dist/cjs/utils/chart/text.js +3 -10
  62. package/dist/cjs/utils/misc.d.ts +1 -0
  63. package/dist/cjs/utils/misc.js +6 -0
  64. package/dist/{cjs/components/Axis → esm/components/AxisX}/AxisX.js +1 -15
  65. package/dist/esm/components/{Axis → AxisY}/AxisY.d.ts +2 -10
  66. package/dist/esm/components/AxisY/AxisY.js +173 -0
  67. package/dist/esm/components/AxisY/prepare-axis-data.d.ts +9 -0
  68. package/dist/esm/components/AxisY/prepare-axis-data.js +306 -0
  69. package/dist/esm/components/AxisY/styles.css +15 -0
  70. package/dist/esm/components/AxisY/types.d.ts +81 -0
  71. package/dist/esm/components/AxisY/types.js +1 -0
  72. package/dist/esm/components/AxisY/utils.d.ts +12 -0
  73. package/dist/esm/components/AxisY/utils.js +71 -0
  74. package/dist/esm/components/ChartInner/index.js +31 -3
  75. package/dist/esm/components/ChartInner/useChartInnerProps.js +13 -1
  76. package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +4 -2
  77. package/dist/esm/components/Tooltip/ChartTooltipContent.js +2 -2
  78. package/dist/esm/components/Tooltip/DefaultTooltipContent/Row.d.ts +2 -1
  79. package/dist/esm/components/Tooltip/DefaultTooltipContent/Row.js +3 -3
  80. package/dist/esm/components/Tooltip/DefaultTooltipContent/{RowTotals.d.ts → RowWithAggregation.d.ts} +2 -1
  81. package/dist/esm/components/Tooltip/DefaultTooltipContent/{RowTotals.js → RowWithAggregation.js} +3 -3
  82. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.d.ts +4 -2
  83. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +174 -114
  84. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.d.ts +7 -1
  85. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +12 -7
  86. package/dist/esm/components/Tooltip/index.js +1 -1
  87. package/dist/esm/components/Tooltip/styles.css +11 -1
  88. package/dist/esm/constants/date.d.ts +1 -0
  89. package/dist/esm/constants/date.js +1 -0
  90. package/dist/esm/constants/index.d.ts +1 -0
  91. package/dist/esm/constants/index.js +1 -0
  92. package/dist/esm/hooks/useChartOptions/types.d.ts +7 -1
  93. package/dist/esm/hooks/useChartOptions/x-axis.js +5 -5
  94. package/dist/esm/hooks/useChartOptions/y-axis.d.ts +3 -1
  95. package/dist/esm/hooks/useChartOptions/y-axis.js +20 -20
  96. package/dist/esm/hooks/useCrosshair/index.js +2 -1
  97. package/dist/esm/hooks/useSeries/prepare-area.js +1 -0
  98. package/dist/esm/hooks/useSeries/prepare-bar-x.js +1 -0
  99. package/dist/esm/hooks/useSeries/prepare-bar-y.d.ts +1 -0
  100. package/dist/esm/hooks/useSeries/prepare-bar-y.js +1 -0
  101. package/dist/esm/hooks/useSeries/prepare-line.js +1 -0
  102. package/dist/esm/hooks/useSeries/prepare-pie.js +1 -0
  103. package/dist/esm/hooks/useSeries/prepare-radar.js +1 -0
  104. package/dist/esm/hooks/useSeries/prepare-sankey.js +1 -0
  105. package/dist/esm/hooks/useSeries/prepare-scatter.js +1 -0
  106. package/dist/esm/hooks/useSeries/prepare-treemap.js +1 -0
  107. package/dist/esm/hooks/useSeries/prepare-waterfall.js +1 -0
  108. package/dist/esm/hooks/useSeries/types.d.ts +2 -1
  109. package/dist/esm/i18n/keysets/en.json +2 -1
  110. package/dist/esm/i18n/keysets/ru.json +2 -1
  111. package/dist/esm/index.d.ts +1 -1
  112. package/dist/esm/index.js +1 -1
  113. package/dist/esm/types/chart/axis.d.ts +11 -1
  114. package/dist/esm/types/chart/base.d.ts +14 -0
  115. package/dist/esm/types/chart/tooltip.d.ts +5 -1
  116. package/dist/esm/utils/chart/axis-generators/bottom.js +3 -3
  117. package/dist/esm/utils/chart/axis.d.ts +14 -8
  118. package/dist/esm/utils/chart/axis.js +34 -9
  119. package/dist/esm/utils/chart/format.d.ts +9 -2
  120. package/dist/esm/utils/chart/format.js +40 -5
  121. package/dist/esm/utils/chart/index.d.ts +6 -17
  122. package/dist/esm/utils/chart/index.js +6 -43
  123. package/dist/esm/utils/chart/text.d.ts +2 -1
  124. package/dist/esm/utils/chart/text.js +3 -10
  125. package/dist/esm/utils/misc.d.ts +1 -0
  126. package/dist/esm/utils/misc.js +6 -0
  127. package/package.json +1 -1
  128. package/dist/cjs/components/Axis/AxisY.js +0 -416
  129. package/dist/cjs/components/Axis/index.d.ts +0 -2
  130. package/dist/cjs/components/Axis/index.js +0 -2
  131. package/dist/cjs/components/Tooltip/utils.d.ts +0 -30
  132. package/dist/cjs/components/Tooltip/utils.js +0 -126
  133. package/dist/esm/components/Axis/AxisY.js +0 -416
  134. package/dist/esm/components/Axis/index.d.ts +0 -2
  135. package/dist/esm/components/Axis/index.js +0 -2
  136. package/dist/esm/components/Tooltip/utils.d.ts +0 -30
  137. package/dist/esm/components/Tooltip/utils.js +0 -126
  138. /package/dist/cjs/components/{Axis → AxisX}/AxisX.d.ts +0 -0
  139. /package/dist/cjs/components/{Axis → AxisX}/styles.css +0 -0
  140. /package/dist/esm/components/{Axis → AxisX}/AxisX.d.ts +0 -0
  141. /package/dist/esm/components/{Axis → AxisX}/styles.css +0 -0
@@ -0,0 +1,306 @@
1
+ import { getUniqId } from '@gravity-ui/uikit';
2
+ import { calculateCos, calculateSin, getBandsPosition, getLabelFormatter, getLabelsSize, getTextSizeFn, getTextWithElipsis, wrapText, } from '../../utils';
3
+ import { getTickValues } from './utils';
4
+ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxHeight, }) {
5
+ var _a;
6
+ const originalTextSize = await getTextSize(text);
7
+ // Currently, a preliminary label calculation is used to build the chart - we cannot exceed it here.
8
+ // Therefore, we rely on a pre-calculated number instead of the current maximum label width.
9
+ const labelMaxWidth = axis.labels.width; //axis.labels.maxWidth;
10
+ const size = originalTextSize;
11
+ const content = [];
12
+ // Warp label text only for categories - it will look strange for numbers or dates.
13
+ if (originalTextSize.width > labelMaxWidth && axis.type === 'category') {
14
+ const textRows = await wrapText({
15
+ text,
16
+ style: axis.labels.style,
17
+ width: labelMaxWidth,
18
+ getTextSize,
19
+ });
20
+ let topOffset = top;
21
+ let newLabelWidth = 0;
22
+ let newLabelHeight = 0;
23
+ for (let textRowIndex = 0; textRowIndex < textRows.length; textRowIndex++) {
24
+ const textRow = textRows[textRowIndex];
25
+ let textSize = await getTextSize(textRow.text);
26
+ if (newLabelHeight + textSize.height <= labelMaxHeight) {
27
+ newLabelWidth = Math.max(newLabelWidth, textSize.width);
28
+ newLabelHeight += textSize.height;
29
+ let rowText = textRow.text.trim();
30
+ if (textRowIndex < textRows.length - 1) {
31
+ const nextTextRow = textRows[textRowIndex + 1];
32
+ if (newLabelHeight + (await getTextSize(nextTextRow.text)).height >
33
+ labelMaxHeight) {
34
+ rowText = textRow.text + nextTextRow.text;
35
+ }
36
+ }
37
+ textSize = await getTextSize(rowText);
38
+ if (textSize.width > labelMaxWidth) {
39
+ rowText = await getTextWithElipsis({
40
+ text: rowText,
41
+ getTextWidth: async (str) => (await getTextSize(str)).width,
42
+ maxWidth: labelMaxWidth,
43
+ });
44
+ textSize = await getTextSize(rowText);
45
+ }
46
+ const x = axis.position === 'left'
47
+ ? left - textSize.width - axis.labels.margin
48
+ : left + axis.labels.margin;
49
+ content.push({
50
+ text: rowText,
51
+ x,
52
+ y: topOffset,
53
+ size: textSize,
54
+ });
55
+ topOffset += textSize.height;
56
+ }
57
+ }
58
+ content.forEach((row) => {
59
+ row.y -= newLabelHeight / 2;
60
+ });
61
+ size.width = newLabelWidth;
62
+ size.height = newLabelHeight;
63
+ }
64
+ else {
65
+ const x = axis.position === 'left'
66
+ ? left - size.width - axis.labels.margin
67
+ : left + axis.labels.margin;
68
+ content.push({
69
+ text,
70
+ x,
71
+ y: Math.max(0, top - size.height / 2),
72
+ size,
73
+ });
74
+ }
75
+ const svgLabel = {
76
+ title: content.length > 1 || ((_a = content[0]) === null || _a === void 0 ? void 0 : _a.text) !== text ? text : undefined,
77
+ content: content,
78
+ style: axis.labels.style,
79
+ size: size,
80
+ };
81
+ return svgLabel;
82
+ }
83
+ // eslint-disable-next-line complexity
84
+ export async function prepareAxisData({ axis, split, scale, width, height, }) {
85
+ var _a, _b, _c;
86
+ const axisPlotTopPosition = ((_a = split.plots[axis.plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
87
+ const axisHeight = ((_b = split.plots[axis.plotIndex]) === null || _b === void 0 ? void 0 : _b.height) || height;
88
+ const domainX = axis.position === 'left' ? 0 : width;
89
+ const domain = {
90
+ start: [domainX, axisPlotTopPosition],
91
+ end: [domainX, axisPlotTopPosition + axisHeight],
92
+ lineColor: (_c = axis.lineColor) !== null && _c !== void 0 ? _c : '',
93
+ };
94
+ const ticks = [];
95
+ const getTextSize = getTextSizeFn({ style: axis.labels.style });
96
+ const labelLineHeight = (await getTextSize('Tmp')).height;
97
+ const values = getTickValues({ scale, axis, labelLineHeight });
98
+ const labelMaxHeight = values.length > 1 ? values[0].y - values[1].y - axis.labels.padding * 2 : axisHeight;
99
+ const labelFormatter = getLabelFormatter({ axis, scale });
100
+ for (let i = 0; i < values.length; i++) {
101
+ const tickValue = values[i];
102
+ const y = axisPlotTopPosition + tickValue.y;
103
+ let svgLabel = null;
104
+ let htmlLabel = null;
105
+ if (axis.labels.enabled) {
106
+ if (axis.labels.html) {
107
+ const content = String(tickValue.value);
108
+ const labelSize = await getLabelsSize({
109
+ labels: [content],
110
+ html: true,
111
+ style: axis.labels.style,
112
+ });
113
+ const size = { width: labelSize.maxWidth, height: labelSize.maxHeight };
114
+ const left = domainX;
115
+ const top = y;
116
+ const x = axis.position === 'left'
117
+ ? left - size.width - axis.labels.margin
118
+ : left + axis.labels.margin;
119
+ htmlLabel = {
120
+ content,
121
+ x,
122
+ y: top - size.height / 2,
123
+ size,
124
+ style: axis.labels.style,
125
+ };
126
+ }
127
+ else {
128
+ const text = labelFormatter(tickValue.value);
129
+ svgLabel = await getSvgAxisLabel({
130
+ getTextSize,
131
+ text,
132
+ axis,
133
+ top: y,
134
+ left: domainX,
135
+ labelMaxHeight,
136
+ });
137
+ }
138
+ }
139
+ const tickLine = axis.grid.enabled
140
+ ? {
141
+ points: [
142
+ [0, y],
143
+ [width, y],
144
+ ],
145
+ }
146
+ : null;
147
+ ticks.push({
148
+ line: tickLine,
149
+ svgLabel,
150
+ htmlLabel,
151
+ });
152
+ }
153
+ let labelsWidth = ticks.reduce((acc, item) => { var _a, _b, _c, _d; return Math.max(acc, (_d = (_b = (_a = item.svgLabel) === null || _a === void 0 ? void 0 : _a.size.width) !== null && _b !== void 0 ? _b : (_c = item.htmlLabel) === null || _c === void 0 ? void 0 : _c.size.width) !== null && _d !== void 0 ? _d : 0); }, 0);
154
+ labelsWidth = Math.min(axis.labels.width, labelsWidth);
155
+ let title = null;
156
+ if (axis.title.text) {
157
+ const getTitleTextSize = getTextSizeFn({ style: axis.title.style });
158
+ const rotateAngle = axis.position === 'left' ? -90 : 90;
159
+ const sin = Math.abs(calculateSin(rotateAngle));
160
+ const cos = Math.abs(calculateCos(rotateAngle));
161
+ const titleContent = [];
162
+ const titleMaxWidth = sin * axisHeight;
163
+ if (axis.title.maxRowCount > 1) {
164
+ const titleTextRows = await wrapText({
165
+ text: axis.title.text,
166
+ style: axis.title.style,
167
+ width: titleMaxWidth,
168
+ getTextSize: getTitleTextSize,
169
+ });
170
+ for (let i = 0; i < axis.title.maxRowCount && i < titleTextRows.length; i++) {
171
+ const textRow = titleTextRows[i];
172
+ const textRowContent = textRow.text.trim();
173
+ const textRowSize = await getTitleTextSize(textRowContent);
174
+ titleContent.push({
175
+ text: textRowContent,
176
+ x: 0,
177
+ y: textRow.y,
178
+ size: textRowSize,
179
+ });
180
+ }
181
+ }
182
+ else {
183
+ const text = await getTextWithElipsis({
184
+ text: axis.title.text,
185
+ maxWidth: titleMaxWidth,
186
+ getTextWidth: async (s) => (await getTitleTextSize(s)).width,
187
+ });
188
+ titleContent.push({
189
+ text,
190
+ x: 0,
191
+ y: 0,
192
+ size: await getTitleTextSize(text),
193
+ });
194
+ }
195
+ const originalTextSize = titleContent.reduce((acc, item) => {
196
+ acc.width = Math.max(acc.width, item.size.width);
197
+ acc.height += item.size.height;
198
+ return acc;
199
+ }, { width: 0, height: 0 });
200
+ const rotatedTitleSize = {
201
+ width: sin * originalTextSize.height + cos * originalTextSize.width,
202
+ height: sin * originalTextSize.width + cos * originalTextSize.height,
203
+ };
204
+ const bottom = Math.max(0, calculateSin(rotateAngle) * originalTextSize.width);
205
+ let y = 0;
206
+ switch (axis.title.align) {
207
+ case 'left': {
208
+ y = -bottom + axisHeight;
209
+ break;
210
+ }
211
+ case 'center': {
212
+ y = -bottom + axisHeight / 2 + rotatedTitleSize.height / 2;
213
+ break;
214
+ }
215
+ case 'right': {
216
+ y = -bottom + rotatedTitleSize.height;
217
+ break;
218
+ }
219
+ }
220
+ const left = Math.min(0, calculateCos(rotateAngle) * originalTextSize.width);
221
+ const x = axis.position === 'left'
222
+ ? -left - labelsWidth - axis.labels.margin - axis.title.margin
223
+ : -left + width + labelsWidth + axis.labels.margin + axis.title.margin;
224
+ title = {
225
+ content: titleContent,
226
+ style: axis.title.style,
227
+ size: rotatedTitleSize,
228
+ x: x,
229
+ y: axisPlotTopPosition + y,
230
+ rotate: rotateAngle,
231
+ offset: -(originalTextSize.height / titleContent.length) * (titleContent.length - 1),
232
+ };
233
+ }
234
+ const plotBands = [];
235
+ axis.plotBands.forEach((plotBand) => {
236
+ var _a, _b;
237
+ const axisScale = scale;
238
+ const { from, to } = getBandsPosition({
239
+ band: plotBand,
240
+ axisScale,
241
+ axis: 'y',
242
+ });
243
+ const halfBandwidth = ((_b = (_a = axisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(axisScale)) !== null && _b !== void 0 ? _b : 0) / 2;
244
+ const startPos = halfBandwidth + Math.min(from, to);
245
+ const endPos = Math.min(Math.abs(to - from), axisHeight - Math.min(from, to));
246
+ const top = Math.max(0, startPos);
247
+ plotBands.push({
248
+ layerPlacement: plotBand.layerPlacement,
249
+ x: 0,
250
+ y: axisPlotTopPosition + top,
251
+ width,
252
+ height: Math.min(endPos, axisHeight),
253
+ color: plotBand.color,
254
+ opacity: plotBand.opacity,
255
+ label: plotBand.label.text
256
+ ? {
257
+ text: plotBand.label.text,
258
+ style: plotBand.label.style,
259
+ x: plotBand.label.padding,
260
+ y: plotBand.label.padding,
261
+ }
262
+ : null,
263
+ });
264
+ });
265
+ const plotLines = [];
266
+ for (let i = 0; i < axis.plotLines.length; i++) {
267
+ const plotLine = axis.plotLines[i];
268
+ const axisScale = scale;
269
+ const plotLineValue = Number(axisScale(plotLine.value));
270
+ const points = [
271
+ [0, plotLineValue],
272
+ [width, plotLineValue],
273
+ ];
274
+ let label = null;
275
+ if (plotLine.label.text) {
276
+ const getTitleTextSize = getTextSizeFn({ style: plotLine.label.style });
277
+ const size = await getTitleTextSize(plotLine.label.text);
278
+ label = {
279
+ text: plotLine.label.text,
280
+ style: plotLine.label.style,
281
+ x: plotLine.label.padding,
282
+ y: Math.max(0, plotLineValue - size.height - plotLine.label.padding),
283
+ };
284
+ }
285
+ plotLines.push({
286
+ layerPlacement: plotLine.layerPlacement,
287
+ x: 0,
288
+ y: axisPlotTopPosition,
289
+ width,
290
+ color: plotLine.color,
291
+ opacity: plotLine.opacity,
292
+ label,
293
+ points,
294
+ lineWidth: plotLine.width,
295
+ dashStyle: plotLine.dashStyle,
296
+ });
297
+ }
298
+ return {
299
+ id: getUniqId(),
300
+ title,
301
+ ticks,
302
+ domain,
303
+ plotBands,
304
+ plotLines,
305
+ };
306
+ }
@@ -0,0 +1,15 @@
1
+ .gcharts-y-axis__domain {
2
+ stroke: var(--g-color-line-generic-active);
3
+ }
4
+ .gcharts-y-axis__label {
5
+ fill: var(--g-color-text-secondary);
6
+ stroke: none;
7
+ dominant-baseline: text-after-edge;
8
+ }
9
+ .gcharts-y-axis__tick {
10
+ stroke: var(--g-color-line-generic);
11
+ }
12
+ .gcharts-y-axis__title {
13
+ dominant-baseline: text-after-edge;
14
+ fill: var(--g-color-text-secondary);
15
+ }
@@ -0,0 +1,81 @@
1
+ import type { DashStyle } from 'src/constants';
2
+ import type { BaseTextStyle, HtmlItem, PlotLayerPlacement } from '../../types';
3
+ export type TextRowData = {
4
+ text: string;
5
+ x: number;
6
+ y: number;
7
+ size: {
8
+ width: number;
9
+ height: number;
10
+ };
11
+ };
12
+ export type AxisSvgLabelData = {
13
+ content: TextRowData[];
14
+ title?: string;
15
+ style: BaseTextStyle;
16
+ size: {
17
+ width: number;
18
+ height: number;
19
+ };
20
+ };
21
+ export type AxisTickLine = {
22
+ points: [number, number][];
23
+ };
24
+ export type AxisTickData = {
25
+ line: AxisTickLine | null;
26
+ svgLabel: AxisSvgLabelData | null;
27
+ htmlLabel: HtmlItem | null;
28
+ };
29
+ export type AxisTitleData = {
30
+ content: TextRowData[];
31
+ style: BaseTextStyle;
32
+ size: {
33
+ width: number;
34
+ height: number;
35
+ };
36
+ x: number;
37
+ y: number;
38
+ rotate: number;
39
+ offset: number;
40
+ };
41
+ export type AxisPlotLineLabel = {
42
+ text: string;
43
+ style: BaseTextStyle;
44
+ x: number;
45
+ y: number;
46
+ };
47
+ export type AxisPlotLineData = {
48
+ layerPlacement: PlotLayerPlacement;
49
+ x: number;
50
+ y: number;
51
+ width: number;
52
+ points: [number, number][];
53
+ color: string;
54
+ lineWidth: number;
55
+ opacity: number;
56
+ label: AxisPlotLineLabel | null;
57
+ dashStyle: DashStyle;
58
+ };
59
+ export type AxisPlotBandData = {
60
+ layerPlacement: PlotLayerPlacement;
61
+ x: number;
62
+ y: number;
63
+ width: number;
64
+ height: number;
65
+ color: string;
66
+ opacity: number;
67
+ label: AxisPlotLineLabel | null;
68
+ };
69
+ export type AxisDomainData = {
70
+ start: [number, number];
71
+ end: [number, number];
72
+ lineColor: string;
73
+ };
74
+ export type AxisYData = {
75
+ id: string;
76
+ title: AxisTitleData | null;
77
+ domain: AxisDomainData;
78
+ ticks: AxisTickData[];
79
+ plotLines: AxisPlotLineData[];
80
+ plotBands: AxisPlotBandData[];
81
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,12 @@
1
+ import type { ChartScale, PreparedAxis } from '../../hooks';
2
+ export declare function getTickValues({ scale, axis, labelLineHeight, }: {
3
+ scale: ChartScale;
4
+ axis: PreparedAxis;
5
+ labelLineHeight: number;
6
+ }): {
7
+ y: number;
8
+ value: number | Date;
9
+ }[] | {
10
+ y: number;
11
+ value: string;
12
+ }[];
@@ -0,0 +1,71 @@
1
+ import { getTicksCount, isBandScale } from '../../utils';
2
+ function thinOut(items, delta) {
3
+ const arr = [];
4
+ for (let i = 0; i < items.length; i = i + delta) {
5
+ arr.push(items[i]);
6
+ }
7
+ return arr;
8
+ }
9
+ function getMinSpaceBetween(arr, iterator) {
10
+ return arr.reduce((acc, item, index) => {
11
+ const prev = arr[index - 1];
12
+ if (prev) {
13
+ return Math.min(acc, Math.abs(iterator(prev) - iterator(item)));
14
+ }
15
+ return acc;
16
+ }, Infinity);
17
+ }
18
+ export function getTickValues({ scale, axis, labelLineHeight, }) {
19
+ if ('ticks' in scale && typeof scale.ticks === 'function') {
20
+ const range = scale.range();
21
+ const height = Math.abs(range[0] - range[1]);
22
+ if (!height) {
23
+ return [];
24
+ }
25
+ let ticksCount = getTicksCount({ axis, range: height });
26
+ let result = scale.ticks(ticksCount).map((t) => ({
27
+ y: scale(t),
28
+ value: t,
29
+ }));
30
+ if (result.length <= 1) {
31
+ return result;
32
+ }
33
+ let labelHeight = getMinSpaceBetween(result, (d) => d.y) - axis.labels.padding * 2;
34
+ ticksCount = result.length - 1;
35
+ while (labelHeight < labelLineHeight && result.length > 1) {
36
+ ticksCount = ticksCount ? ticksCount - 1 : result.length - 1;
37
+ result = scale.ticks(ticksCount).map((t) => ({
38
+ y: scale(t),
39
+ value: t,
40
+ }));
41
+ labelHeight = getMinSpaceBetween(result, (d) => d.y) - axis.labels.padding * 2;
42
+ }
43
+ return result;
44
+ }
45
+ if (isBandScale(scale)) {
46
+ const domain = scale.domain();
47
+ const bandWidth = scale.bandwidth();
48
+ const items = domain.map((d) => {
49
+ var _a;
50
+ return ({
51
+ y: ((_a = scale(d)) !== null && _a !== void 0 ? _a : 0) + bandWidth / 2,
52
+ value: d,
53
+ });
54
+ });
55
+ if (items.length <= 1) {
56
+ return items;
57
+ }
58
+ let result = [...items];
59
+ let labelHeight = result[0].y - result[1].y - axis.labels.padding * 2;
60
+ let delta = 2;
61
+ while (labelHeight < labelLineHeight && result.length > 1) {
62
+ result = thinOut(items, delta);
63
+ if (result.length > 1) {
64
+ delta += 1;
65
+ labelHeight = result[0].y - result[1].y - axis.labels.padding * 2;
66
+ }
67
+ }
68
+ return result;
69
+ }
70
+ return [];
71
+ }
@@ -3,7 +3,9 @@ import { ArrowRotateLeft } from '@gravity-ui/icons';
3
3
  import { Button, ButtonIcon, useUniqId } from '@gravity-ui/uikit';
4
4
  import { useCrosshair } from '../../hooks';
5
5
  import { EventType, block, getDispatcher } from '../../utils';
6
- import { AxisX, AxisY } from '../Axis';
6
+ import { AxisX } from '../AxisX/AxisX';
7
+ import { AxisY } from '../AxisY/AxisY';
8
+ import { prepareAxisData } from '../AxisY/prepare-axis-data';
7
9
  import { Legend } from '../Legend';
8
10
  import { PlotTitle } from '../PlotTitle';
9
11
  import { Title } from '../Title';
@@ -23,7 +25,7 @@ export const ChartInner = (props) => {
23
25
  const plotAfterRef = React.useRef(null);
24
26
  const dispatcher = React.useMemo(() => getDispatcher(), []);
25
27
  const clipPathId = useUniqId();
26
- const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, handleZoomReset, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, svgBottomPos, svgTopPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher,
28
+ const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, handleZoomReset, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher,
27
29
  htmlLayout, svgContainer: svgRef.current, plotNode: plotRef.current, clipPathId }));
28
30
  const { tooltipPinned, togglePinTooltip, unpinTooltip } = useChartInnerState({
29
31
  dispatcher,
@@ -80,6 +82,32 @@ export const ChartInner = (props) => {
80
82
  unpinTooltip === null || unpinTooltip === void 0 ? void 0 : unpinTooltip();
81
83
  }
82
84
  }, [prevWidth, width, prevHeight, height, tooltipPinned, unpinTooltip]);
85
+ const [yAxisDataItems, setYAxisDataItems] = React.useState([]);
86
+ const countedRef = React.useRef(0);
87
+ React.useEffect(() => {
88
+ countedRef.current++;
89
+ (async function () {
90
+ const currentRun = countedRef.current;
91
+ const items = [];
92
+ for (let i = 0; i < yAxis.length; i++) {
93
+ const axis = yAxis[i];
94
+ const scale = yScale === null || yScale === void 0 ? void 0 : yScale[i];
95
+ if (scale) {
96
+ const axisData = await prepareAxisData({
97
+ axis,
98
+ scale,
99
+ width: boundsWidth,
100
+ height: boundsHeight,
101
+ split: preparedSplit,
102
+ });
103
+ items.push(axisData);
104
+ }
105
+ }
106
+ if (countedRef.current === currentRun) {
107
+ setYAxisDataItems(items);
108
+ }
109
+ })();
110
+ }, [boundsHeight, boundsWidth, preparedSplit, yAxis, yScale]);
83
111
  return (React.createElement("div", { className: b() },
84
112
  React.createElement("svg", { ref: svgRef, width: width, height: height,
85
113
  // We use onPointerMove here because onMouseMove works incorrectly when the zoom setting is enabled:
@@ -94,7 +122,7 @@ export const ChartInner = (props) => {
94
122
  })),
95
123
  React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[boundsOffsetLeft, boundsOffsetTop].join(',')})`, ref: plotRef },
96
124
  xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length) && (React.createElement(React.Fragment, null,
97
- React.createElement(AxisY, { axes: yAxis, bottomLimit: svgBottomPos, boundsOffsetLeft: boundsOffsetLeft, boundsOffsetTop: boundsOffsetTop, height: boundsHeight, htmlLayout: htmlLayout, plotAfterRef: plotAfterRef, plotBeforeRef: plotBeforeRef, scale: yScale, split: preparedSplit, topLimit: svgTopPos, width: boundsWidth }),
125
+ yAxisDataItems.map((axisData, index) => (React.createElement(AxisY, { key: index, htmlLayout: htmlLayout, plotAfterRef: plotAfterRef, plotBeforeRef: plotBeforeRef, preparedAxisData: axisData }))),
98
126
  xAxis && (React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
99
127
  React.createElement(AxisX, { axis: xAxis, boundsOffsetLeft: boundsOffsetLeft, boundsOffsetTop: boundsOffsetTop, height: boundsHeight, htmlLayout: htmlLayout, leftmostLimit: svgXPos, plotAfterRef: plotAfterRef, plotBeforeRef: plotBeforeRef, scale: xScale, split: preparedSplit, width: boundsWidth }))))),
100
128
  React.createElement("g", { ref: plotBeforeRef }),
@@ -46,16 +46,28 @@ export function useChartInnerProps(props) {
46
46
  seriesOptions: preparedSeriesOptions,
47
47
  }).then((val) => setXAxis(val));
48
48
  }, [data.xAxis, preparedSeriesOptions, width, zoomedSeriesData]);
49
+ const estimatedBoundsHeight = React.useMemo(() => {
50
+ if (xAxis) {
51
+ return (height -
52
+ xAxis.title.height +
53
+ xAxis.title.margin +
54
+ xAxis.labels.margin +
55
+ parseInt(xAxis.labels.style.fontSize, 10));
56
+ }
57
+ return 0;
58
+ }, [height, xAxis]);
49
59
  const [yAxis, setYAxis] = React.useState([]);
50
60
  React.useEffect(() => {
51
61
  setYAxis([]);
52
62
  getPreparedYAxis({
53
63
  height,
64
+ boundsHeight: estimatedBoundsHeight,
65
+ width,
54
66
  seriesData: zoomedSeriesData,
55
67
  seriesOptions: preparedSeriesOptions,
56
68
  yAxis: data.yAxis,
57
69
  }).then((val) => setYAxis(val));
58
- }, [data.yAxis, height, preparedSeriesOptions, zoomedSeriesData]);
70
+ }, [data.yAxis, estimatedBoundsHeight, height, preparedSeriesOptions, width, zoomedSeriesData]);
59
71
  const { preparedSeries, preparedLegend, handleLegendItemClick } = useSeries({
60
72
  colors,
61
73
  legend: data.legend,
@@ -2,11 +2,13 @@ import React from 'react';
2
2
  import type { ChartTooltip, ChartXAxis, ChartYAxis, TooltipDataChunk } from '../../types';
3
3
  export interface ChartTooltipContentProps {
4
4
  hovered?: TooltipDataChunk[];
5
- xAxis?: ChartXAxis | null;
6
- yAxis?: ChartYAxis;
5
+ pinned?: boolean;
7
6
  renderer?: ChartTooltip['renderer'];
8
7
  rowRenderer?: ChartTooltip['rowRenderer'];
9
8
  valueFormat?: ChartTooltip['valueFormat'];
9
+ headerFormat?: ChartTooltip['headerFormat'];
10
10
  totals?: ChartTooltip['totals'];
11
+ xAxis?: ChartXAxis | null;
12
+ yAxis?: ChartYAxis;
11
13
  }
12
14
  export declare const ChartTooltipContent: (props: ChartTooltipContentProps) => React.JSX.Element | null;
@@ -2,10 +2,10 @@ import React from 'react';
2
2
  import isNil from 'lodash/isNil';
3
3
  import { DefaultTooltipContent } from './DefaultTooltipContent';
4
4
  export const ChartTooltipContent = (props) => {
5
- const { hovered, xAxis, yAxis, renderer, rowRenderer, valueFormat, totals } = props;
5
+ const { hovered, xAxis, yAxis, renderer, rowRenderer, valueFormat, headerFormat, totals, pinned, } = props;
6
6
  if (!hovered) {
7
7
  return null;
8
8
  }
9
9
  const customTooltip = renderer === null || renderer === void 0 ? void 0 : renderer({ hovered, xAxis, yAxis });
10
- return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, valueFormat: valueFormat, totals: totals, rowRenderer: rowRenderer })) : (customTooltip);
10
+ return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, pinned: pinned, rowRenderer: rowRenderer, totals: totals, valueFormat: valueFormat, headerFormat: headerFormat, xAxis: xAxis, yAxis: yAxis })) : (customTooltip);
11
11
  };
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
  export declare function Row(props: {
3
3
  label: React.ReactNode;
4
- value: React.ReactNode;
5
4
  active?: boolean;
6
5
  className?: string;
7
6
  color?: string;
8
7
  striped?: boolean;
8
+ style?: React.CSSProperties;
9
+ value?: React.ReactNode;
9
10
  }): React.JSX.Element;
@@ -2,9 +2,9 @@ import React from 'react';
2
2
  import { block } from '../../../utils';
3
3
  const b = block('tooltip');
4
4
  export function Row(props) {
5
- const { label, value, active, color, className, striped } = props;
6
- return (React.createElement("div", { className: b('content-row', { active, striped }, className) },
5
+ const { label, value, active, color, className, striped, style } = props;
6
+ return (React.createElement("div", { className: b('content-row', { active, striped }, className), style: style },
7
7
  color && React.createElement("div", { className: b('content-row-color'), style: { backgroundColor: color } }),
8
8
  label,
9
- React.createElement("span", { className: b('content-row-value') }, value)));
9
+ value && React.createElement("span", { className: b('content-row-value') }, value)));
10
10
  }
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
  import type { ChartTooltipTotalsAggregationValue, ChartTooltipTotalsBuiltInAggregation, ValueFormat } from '../../../types';
3
3
  import type { HoveredValue } from './utils';
4
- export declare function RowTotals(props: {
4
+ export declare function RowWithAggregation(props: {
5
5
  aggregation: ChartTooltipTotalsBuiltInAggregation | (() => ChartTooltipTotalsAggregationValue);
6
6
  values: HoveredValue[];
7
7
  label?: string;
8
+ style?: React.CSSProperties;
8
9
  valueFormat?: ValueFormat;
9
10
  }): React.JSX.Element;
@@ -4,8 +4,8 @@ import { getFormattedValue } from '../../../utils/chart/format';
4
4
  import { Row } from './Row';
5
5
  import { getBuiltInAggregatedValue, getBuiltInAggregationLabel } from './utils';
6
6
  const b = block('tooltip');
7
- export function RowTotals(props) {
8
- const { aggregation, label, valueFormat, values } = props;
7
+ export function RowWithAggregation(props) {
8
+ const { aggregation, label, style, valueFormat, values } = props;
9
9
  let resultLabel = label;
10
10
  if (!resultLabel && typeof aggregation === 'string') {
11
11
  resultLabel = getBuiltInAggregationLabel({ aggregation });
@@ -19,5 +19,5 @@ export function RowTotals(props) {
19
19
  format: valueFormat || { type: 'number' },
20
20
  })
21
21
  : resultValue;
22
- return (React.createElement(Row, { className: b('content-row-totals'), label: resultLabel, value: formattedResultValue }));
22
+ return (React.createElement(Row, { className: b('content-row-totals'), label: resultLabel, style: style, value: formattedResultValue }));
23
23
  }