@gravity-ui/charts 1.9.0 → 1.10.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 (199) hide show
  1. package/dist/cjs/components/Axis/AxisX.d.ts +2 -1
  2. package/dist/cjs/components/Axis/AxisX.js +147 -143
  3. package/dist/cjs/components/Axis/AxisY.d.ts +2 -1
  4. package/dist/cjs/components/Axis/AxisY.js +113 -91
  5. package/dist/cjs/components/ChartInner/index.js +23 -10
  6. package/dist/cjs/components/ChartInner/useChartInnerHandlers.d.ts +1 -1
  7. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +8 -5
  8. package/dist/cjs/components/ChartInner/useChartInnerProps.js +55 -9
  9. package/dist/cjs/components/ChartInner/utils.d.ts +3 -0
  10. package/dist/cjs/components/ChartInner/utils.js +28 -0
  11. package/dist/cjs/components/Legend/index.js +199 -196
  12. package/dist/cjs/components/Tooltip/ChartTooltipContent.d.ts +1 -1
  13. package/dist/cjs/components/Tooltip/DefaultContent.d.ts +1 -1
  14. package/dist/cjs/components/Tooltip/DefaultContent.js +1 -1
  15. package/dist/cjs/components/Tooltip/index.d.ts +1 -1
  16. package/dist/cjs/hooks/hooks-utils/index.d.ts +1 -0
  17. package/dist/cjs/hooks/hooks-utils/index.js +1 -0
  18. package/dist/cjs/hooks/hooks-utils/zoom.d.ts +8 -0
  19. package/dist/cjs/hooks/hooks-utils/zoom.js +81 -0
  20. package/dist/cjs/hooks/useAxisScales/index.d.ts +4 -2
  21. package/dist/cjs/hooks/useAxisScales/index.js +22 -8
  22. package/dist/cjs/hooks/useBrush/index.d.ts +3 -0
  23. package/dist/cjs/hooks/useBrush/index.js +70 -0
  24. package/dist/cjs/hooks/useBrush/styles.css +10 -0
  25. package/dist/cjs/hooks/useBrush/types.d.ts +24 -0
  26. package/dist/cjs/hooks/useBrush/types.js +1 -0
  27. package/dist/cjs/hooks/useChartDimensions/index.d.ts +3 -3
  28. package/dist/cjs/hooks/useChartDimensions/index.js +2 -2
  29. package/dist/cjs/hooks/useChartDimensions/utils.d.ts +2 -2
  30. package/dist/cjs/hooks/useChartOptions/chart.d.ts +2 -1
  31. package/dist/cjs/hooks/useChartOptions/chart.js +80 -1
  32. package/dist/cjs/hooks/useChartOptions/index.js +3 -2
  33. package/dist/cjs/hooks/useChartOptions/types.d.ts +3 -1
  34. package/dist/cjs/hooks/useChartOptions/x-axis.d.ts +3 -3
  35. package/dist/cjs/hooks/useChartOptions/x-axis.js +11 -11
  36. package/dist/cjs/hooks/useChartOptions/y-axis.d.ts +3 -3
  37. package/dist/cjs/hooks/useChartOptions/y-axis.js +22 -18
  38. package/dist/cjs/hooks/useCrosshair/index.d.ts +1 -1
  39. package/dist/cjs/hooks/useCrosshair/index.js +2 -2
  40. package/dist/cjs/hooks/useSeries/index.d.ts +8 -6
  41. package/dist/cjs/hooks/useSeries/index.js +41 -22
  42. package/dist/cjs/hooks/useSeries/prepare-bar-y.d.ts +27 -2
  43. package/dist/cjs/hooks/useSeries/prepare-bar-y.js +5 -5
  44. package/dist/cjs/hooks/useSeries/prepare-legend.d.ts +1 -1
  45. package/dist/cjs/hooks/useSeries/prepare-legend.js +6 -5
  46. package/dist/cjs/hooks/useSeries/prepareSeries.d.ts +1 -1
  47. package/dist/cjs/hooks/useSeries/prepareSeries.js +2 -2
  48. package/dist/cjs/hooks/useShapes/area/index.js +1 -1
  49. package/dist/cjs/hooks/useShapes/area/prepare-data.d.ts +1 -1
  50. package/dist/cjs/hooks/useShapes/area/prepare-data.js +32 -16
  51. package/dist/cjs/hooks/useShapes/area/types.d.ts +1 -0
  52. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.d.ts +1 -1
  53. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +17 -13
  54. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.d.ts +1 -1
  55. package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +6 -6
  56. package/dist/cjs/hooks/useShapes/index.d.ts +1 -1
  57. package/dist/cjs/hooks/useShapes/index.js +40 -31
  58. package/dist/cjs/hooks/useShapes/line/prepare-data.d.ts +1 -1
  59. package/dist/cjs/hooks/useShapes/line/prepare-data.js +14 -11
  60. package/dist/cjs/hooks/useShapes/line/types.d.ts +1 -0
  61. package/dist/cjs/hooks/useShapes/marker.js +2 -2
  62. package/dist/cjs/hooks/useShapes/pie/index.js +3 -3
  63. package/dist/cjs/hooks/useShapes/pie/prepare-data.d.ts +1 -1
  64. package/dist/cjs/hooks/useShapes/pie/prepare-data.js +15 -11
  65. package/dist/cjs/hooks/useShapes/radar/prepare-data.d.ts +1 -1
  66. package/dist/cjs/hooks/useShapes/radar/prepare-data.js +6 -7
  67. package/dist/cjs/hooks/useShapes/radar/types.d.ts +1 -0
  68. package/dist/cjs/hooks/useShapes/scatter/index.js +0 -1
  69. package/dist/cjs/hooks/useShapes/scatter/prepare-data.js +2 -0
  70. package/dist/cjs/hooks/useShapes/scatter/types.d.ts +1 -0
  71. package/dist/cjs/hooks/useShapes/treemap/prepare-data.d.ts +1 -1
  72. package/dist/cjs/hooks/useShapes/treemap/prepare-data.js +19 -16
  73. package/dist/cjs/hooks/useShapes/waterfall/prepare-data.d.ts +1 -1
  74. package/dist/cjs/hooks/useShapes/waterfall/prepare-data.js +8 -7
  75. package/dist/cjs/hooks/useZoom/index.d.ts +18 -0
  76. package/dist/cjs/hooks/useZoom/index.js +54 -0
  77. package/dist/cjs/hooks/useZoom/types.d.ts +19 -0
  78. package/dist/cjs/hooks/useZoom/types.js +1 -0
  79. package/dist/cjs/hooks/useZoom/utils.d.ts +12 -0
  80. package/dist/cjs/hooks/useZoom/utils.js +128 -0
  81. package/dist/cjs/types/chart/chart.d.ts +5 -0
  82. package/dist/cjs/types/chart/pie.d.ts +1 -1
  83. package/dist/cjs/types/chart/tooltip.d.ts +1 -1
  84. package/dist/cjs/types/chart/zoom.d.ts +36 -0
  85. package/dist/cjs/types/chart/zoom.js +1 -0
  86. package/dist/cjs/types/index.d.ts +1 -0
  87. package/dist/cjs/types/index.js +1 -0
  88. package/dist/cjs/types/misc.d.ts +7 -0
  89. package/dist/cjs/utils/chart/axis-generators/bottom.d.ts +1 -1
  90. package/dist/cjs/utils/chart/axis-generators/bottom.js +25 -25
  91. package/dist/cjs/utils/chart/axis.d.ts +1 -1
  92. package/dist/cjs/utils/chart/axis.js +2 -2
  93. package/dist/cjs/utils/chart/get-closest-data.js +1 -1
  94. package/dist/cjs/utils/chart/text.d.ts +7 -7
  95. package/dist/cjs/utils/chart/text.js +37 -29
  96. package/dist/cjs/utils/chart-ui/pie-center-text.d.ts +1 -1
  97. package/dist/cjs/utils/chart-ui/pie-center-text.js +2 -2
  98. package/dist/cjs/validation/index.d.ts +1 -1
  99. package/dist/cjs/validation/index.js +16 -16
  100. package/dist/esm/components/Axis/AxisX.d.ts +2 -1
  101. package/dist/esm/components/Axis/AxisX.js +147 -143
  102. package/dist/esm/components/Axis/AxisY.d.ts +2 -1
  103. package/dist/esm/components/Axis/AxisY.js +113 -91
  104. package/dist/esm/components/ChartInner/index.js +23 -10
  105. package/dist/esm/components/ChartInner/useChartInnerHandlers.d.ts +1 -1
  106. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +8 -5
  107. package/dist/esm/components/ChartInner/useChartInnerProps.js +55 -9
  108. package/dist/esm/components/ChartInner/utils.d.ts +3 -0
  109. package/dist/esm/components/ChartInner/utils.js +28 -0
  110. package/dist/esm/components/Legend/index.js +199 -196
  111. package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +1 -1
  112. package/dist/esm/components/Tooltip/DefaultContent.d.ts +1 -1
  113. package/dist/esm/components/Tooltip/DefaultContent.js +1 -1
  114. package/dist/esm/components/Tooltip/index.d.ts +1 -1
  115. package/dist/esm/hooks/hooks-utils/index.d.ts +1 -0
  116. package/dist/esm/hooks/hooks-utils/index.js +1 -0
  117. package/dist/esm/hooks/hooks-utils/zoom.d.ts +8 -0
  118. package/dist/esm/hooks/hooks-utils/zoom.js +81 -0
  119. package/dist/esm/hooks/useAxisScales/index.d.ts +4 -2
  120. package/dist/esm/hooks/useAxisScales/index.js +22 -8
  121. package/dist/esm/hooks/useBrush/index.d.ts +3 -0
  122. package/dist/esm/hooks/useBrush/index.js +70 -0
  123. package/dist/esm/hooks/useBrush/styles.css +10 -0
  124. package/dist/esm/hooks/useBrush/types.d.ts +24 -0
  125. package/dist/esm/hooks/useBrush/types.js +1 -0
  126. package/dist/esm/hooks/useChartDimensions/index.d.ts +3 -3
  127. package/dist/esm/hooks/useChartDimensions/index.js +2 -2
  128. package/dist/esm/hooks/useChartDimensions/utils.d.ts +2 -2
  129. package/dist/esm/hooks/useChartOptions/chart.d.ts +2 -1
  130. package/dist/esm/hooks/useChartOptions/chart.js +80 -1
  131. package/dist/esm/hooks/useChartOptions/index.js +3 -2
  132. package/dist/esm/hooks/useChartOptions/types.d.ts +3 -1
  133. package/dist/esm/hooks/useChartOptions/x-axis.d.ts +3 -3
  134. package/dist/esm/hooks/useChartOptions/x-axis.js +11 -11
  135. package/dist/esm/hooks/useChartOptions/y-axis.d.ts +3 -3
  136. package/dist/esm/hooks/useChartOptions/y-axis.js +22 -18
  137. package/dist/esm/hooks/useCrosshair/index.d.ts +1 -1
  138. package/dist/esm/hooks/useCrosshair/index.js +2 -2
  139. package/dist/esm/hooks/useSeries/index.d.ts +8 -6
  140. package/dist/esm/hooks/useSeries/index.js +41 -22
  141. package/dist/esm/hooks/useSeries/prepare-bar-y.d.ts +27 -2
  142. package/dist/esm/hooks/useSeries/prepare-bar-y.js +5 -5
  143. package/dist/esm/hooks/useSeries/prepare-legend.d.ts +1 -1
  144. package/dist/esm/hooks/useSeries/prepare-legend.js +6 -5
  145. package/dist/esm/hooks/useSeries/prepareSeries.d.ts +1 -1
  146. package/dist/esm/hooks/useSeries/prepareSeries.js +2 -2
  147. package/dist/esm/hooks/useShapes/area/index.js +1 -1
  148. package/dist/esm/hooks/useShapes/area/prepare-data.d.ts +1 -1
  149. package/dist/esm/hooks/useShapes/area/prepare-data.js +32 -16
  150. package/dist/esm/hooks/useShapes/area/types.d.ts +1 -0
  151. package/dist/esm/hooks/useShapes/bar-x/prepare-data.d.ts +1 -1
  152. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +17 -13
  153. package/dist/esm/hooks/useShapes/bar-y/prepare-data.d.ts +1 -1
  154. package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +6 -6
  155. package/dist/esm/hooks/useShapes/index.d.ts +1 -1
  156. package/dist/esm/hooks/useShapes/index.js +40 -31
  157. package/dist/esm/hooks/useShapes/line/prepare-data.d.ts +1 -1
  158. package/dist/esm/hooks/useShapes/line/prepare-data.js +14 -11
  159. package/dist/esm/hooks/useShapes/line/types.d.ts +1 -0
  160. package/dist/esm/hooks/useShapes/marker.js +2 -2
  161. package/dist/esm/hooks/useShapes/pie/index.js +3 -3
  162. package/dist/esm/hooks/useShapes/pie/prepare-data.d.ts +1 -1
  163. package/dist/esm/hooks/useShapes/pie/prepare-data.js +15 -11
  164. package/dist/esm/hooks/useShapes/radar/prepare-data.d.ts +1 -1
  165. package/dist/esm/hooks/useShapes/radar/prepare-data.js +6 -7
  166. package/dist/esm/hooks/useShapes/radar/types.d.ts +1 -0
  167. package/dist/esm/hooks/useShapes/scatter/index.js +0 -1
  168. package/dist/esm/hooks/useShapes/scatter/prepare-data.js +2 -0
  169. package/dist/esm/hooks/useShapes/scatter/types.d.ts +1 -0
  170. package/dist/esm/hooks/useShapes/treemap/prepare-data.d.ts +1 -1
  171. package/dist/esm/hooks/useShapes/treemap/prepare-data.js +19 -16
  172. package/dist/esm/hooks/useShapes/waterfall/prepare-data.d.ts +1 -1
  173. package/dist/esm/hooks/useShapes/waterfall/prepare-data.js +8 -7
  174. package/dist/esm/hooks/useZoom/index.d.ts +18 -0
  175. package/dist/esm/hooks/useZoom/index.js +54 -0
  176. package/dist/esm/hooks/useZoom/types.d.ts +19 -0
  177. package/dist/esm/hooks/useZoom/types.js +1 -0
  178. package/dist/esm/hooks/useZoom/utils.d.ts +12 -0
  179. package/dist/esm/hooks/useZoom/utils.js +128 -0
  180. package/dist/esm/types/chart/chart.d.ts +5 -0
  181. package/dist/esm/types/chart/pie.d.ts +1 -1
  182. package/dist/esm/types/chart/tooltip.d.ts +1 -1
  183. package/dist/esm/types/chart/zoom.d.ts +36 -0
  184. package/dist/esm/types/chart/zoom.js +1 -0
  185. package/dist/esm/types/index.d.ts +1 -0
  186. package/dist/esm/types/index.js +1 -0
  187. package/dist/esm/types/misc.d.ts +7 -0
  188. package/dist/esm/utils/chart/axis-generators/bottom.d.ts +1 -1
  189. package/dist/esm/utils/chart/axis-generators/bottom.js +25 -25
  190. package/dist/esm/utils/chart/axis.d.ts +1 -1
  191. package/dist/esm/utils/chart/axis.js +2 -2
  192. package/dist/esm/utils/chart/get-closest-data.js +1 -1
  193. package/dist/esm/utils/chart/text.d.ts +7 -7
  194. package/dist/esm/utils/chart/text.js +37 -29
  195. package/dist/esm/utils/chart-ui/pie-center-text.d.ts +1 -1
  196. package/dist/esm/utils/chart-ui/pie-center-text.js +2 -2
  197. package/dist/esm/validation/index.d.ts +1 -1
  198. package/dist/esm/validation/index.js +16 -16
  199. package/package.json +2 -1
@@ -85,7 +85,7 @@ function getTitlePosition(args) {
85
85
  return { x, y };
86
86
  }
87
87
  export const AxisY = (props) => {
88
- const { axes: allAxes, width, height: totalHeight, scale, split, plotRef, bottomLimit = 0, } = props;
88
+ const { axes: allAxes, width, height: totalHeight, scale, split, plotBeforeRef, plotAfterRef, bottomLimit = 0, } = props;
89
89
  const height = getAxisHeight({ split, boundsHeight: totalHeight });
90
90
  const ref = React.useRef(null);
91
91
  const lineGenerator = line();
@@ -96,11 +96,16 @@ export const AxisY = (props) => {
96
96
  const axes = allAxes.filter((a) => a.visible);
97
97
  const svgElement = select(ref.current);
98
98
  svgElement.selectAll('*').remove();
99
- let plotContainer = null;
99
+ let plotBeforeContainer = null;
100
+ let plotAfterContainer = null;
100
101
  const plotDataAttr = 'data-plot-y';
101
- if (plotRef === null || plotRef === void 0 ? void 0 : plotRef.current) {
102
- plotContainer = select(plotRef.current);
103
- plotContainer.selectAll(`[${plotDataAttr}]`).remove();
102
+ if (plotBeforeRef === null || plotBeforeRef === void 0 ? void 0 : plotBeforeRef.current) {
103
+ plotBeforeContainer = select(plotBeforeRef.current);
104
+ plotBeforeContainer.selectAll(`[${plotDataAttr}]`).remove();
105
+ }
106
+ if (plotAfterRef === null || plotAfterRef === void 0 ? void 0 : plotAfterRef.current) {
107
+ plotAfterContainer = select(plotAfterRef.current);
108
+ plotAfterContainer.selectAll(`[${plotDataAttr}]`).remove();
104
109
  }
105
110
  const axisSelection = svgElement
106
111
  .selectAll('axis')
@@ -174,76 +179,74 @@ export const AxisY = (props) => {
174
179
  })
175
180
  .remove();
176
181
  }
177
- if (plotContainer && d.plotBands.length > 0) {
182
+ if (d.plotBands.length > 0) {
178
183
  const plotBandDataAttr = `data-plot-y-band-${index}`;
179
- const plotBandsSelection = plotContainer
180
- .selectAll(`[${plotBandDataAttr}]`)
181
- .data(d.plotBands)
182
- .join('g')
183
- .attr(plotDataAttr, 1)
184
- .attr(plotBandDataAttr, 1)
185
- .style('transform', getAxisPlotsPosition(d, split));
186
- plotBandsSelection
187
- .append('rect')
188
- .attr('x', 0)
189
- .attr('width', width)
190
- .attr('y', (band) => {
191
- var _a, _b;
192
- const { from, to } = getBandsPosition({ band, axisScale, axis: 'y' });
193
- const halfBandwidth = ((_b = (_a = axisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(axisScale)) !== null && _b !== void 0 ? _b : 0) / 2;
194
- const startPos = halfBandwidth + Math.min(from, to);
195
- return Math.max(0, startPos);
196
- })
197
- .attr('height', (band) => {
198
- const { from, to } = getBandsPosition({ band, axisScale, axis: 'y' });
199
- const startPos = height - Math.min(from, to);
200
- const endPos = Math.min(Math.abs(to - from), startPos);
201
- return Math.min(endPos, height);
202
- })
203
- .attr('fill', (band) => band.color)
204
- .attr('opacity', (band) => band.opacity);
205
- plotBandsSelection.each((plotBandData, i, nodes) => {
206
- const plotLineSelection = select(nodes[i]);
207
- if (plotBandData.layerPlacement === 'before') {
208
- plotLineSelection.lower();
184
+ const setPlotBands = (plotContainer, plotBands) => {
185
+ if (!plotContainer || !plotBands.length) {
186
+ return;
209
187
  }
210
- else {
211
- plotLineSelection.raise();
212
- }
213
- });
188
+ const plotBandsSelection = plotContainer
189
+ .selectAll(`[${plotBandDataAttr}]`)
190
+ .remove()
191
+ .data(plotBands)
192
+ .join('g')
193
+ .attr(plotDataAttr, 1)
194
+ .attr(plotBandDataAttr, 1)
195
+ .style('transform', getAxisPlotsPosition(d, split));
196
+ plotBandsSelection
197
+ .append('rect')
198
+ .attr('x', 0)
199
+ .attr('width', width)
200
+ .attr('y', (band) => {
201
+ var _a, _b;
202
+ const { from, to } = getBandsPosition({ band, axisScale, axis: 'y' });
203
+ const halfBandwidth = ((_b = (_a = axisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(axisScale)) !== null && _b !== void 0 ? _b : 0) / 2;
204
+ const startPos = halfBandwidth + Math.min(from, to);
205
+ return Math.max(0, startPos);
206
+ })
207
+ .attr('height', (band) => {
208
+ const { from, to } = getBandsPosition({ band, axisScale, axis: 'y' });
209
+ const startPos = height - Math.min(from, to);
210
+ const endPos = Math.min(Math.abs(to - from), startPos);
211
+ return Math.min(endPos, height);
212
+ })
213
+ .attr('fill', (band) => band.color)
214
+ .attr('opacity', (band) => band.opacity);
215
+ };
216
+ setPlotBands(plotBeforeContainer, d.plotBands.filter((item) => item.layerPlacement === 'before'));
217
+ setPlotBands(plotAfterContainer, d.plotBands.filter((item) => item.layerPlacement === 'after'));
214
218
  }
215
- if (plotContainer && d.plotLines.length > 0) {
219
+ if (d.plotLines.length > 0) {
216
220
  const plotLineDataAttr = `data-plot-y-line-${index}`;
217
- const plotLinesSelection = plotContainer
218
- .selectAll(`[${plotLineDataAttr}]`)
219
- .data(d.plotLines)
220
- .join('g')
221
- .attr(plotDataAttr, 1)
222
- .attr(plotLineDataAttr, 1)
223
- .style('transform', getAxisPlotsPosition(d, split));
224
- plotLinesSelection
225
- .append('path')
226
- .attr('d', (plotLine) => {
227
- const plotLineValue = Number(axisScale(plotLine.value));
228
- const points = [
229
- [0, plotLineValue],
230
- [width, plotLineValue],
231
- ];
232
- return lineGenerator(points);
233
- })
234
- .attr('stroke', (plotLine) => plotLine.color)
235
- .attr('stroke-width', (plotLine) => plotLine.width)
236
- .attr('stroke-dasharray', (plotLine) => getLineDashArray(plotLine.dashStyle, plotLine.width))
237
- .attr('opacity', (plotLine) => plotLine.opacity);
238
- plotLinesSelection.each((plotLineData, i, nodes) => {
239
- const plotLineSelection = select(nodes[i]);
240
- if (plotLineData.layerPlacement === 'before') {
241
- plotLineSelection.lower();
242
- }
243
- else {
244
- plotLineSelection.raise();
221
+ const setPlotLines = (plotContainer, plotLines) => {
222
+ if (!plotContainer || !plotLines.length) {
223
+ return;
245
224
  }
246
- });
225
+ const plotLinesSelection = plotContainer
226
+ .selectAll(`[${plotLineDataAttr}]`)
227
+ .remove()
228
+ .data(plotLines)
229
+ .join('g')
230
+ .attr(plotDataAttr, 1)
231
+ .attr(plotLineDataAttr, 1)
232
+ .style('transform', getAxisPlotsPosition(d, split));
233
+ plotLinesSelection
234
+ .append('path')
235
+ .attr('d', (plotLine) => {
236
+ const plotLineValue = Number(axisScale(plotLine.value));
237
+ const points = [
238
+ [0, plotLineValue],
239
+ [width, plotLineValue],
240
+ ];
241
+ return lineGenerator(points);
242
+ })
243
+ .attr('stroke', (plotLine) => plotLine.color)
244
+ .attr('stroke-width', (plotLine) => plotLine.width)
245
+ .attr('stroke-dasharray', (plotLine) => getLineDashArray(plotLine.dashStyle, plotLine.width))
246
+ .attr('opacity', (plotLine) => plotLine.opacity);
247
+ };
248
+ setPlotLines(plotBeforeContainer, d.plotLines.filter((item) => item.layerPlacement === 'before'));
249
+ setPlotLines(plotAfterContainer, d.plotLines.filter((item) => item.layerPlacement === 'after'));
247
250
  }
248
251
  return axisItem;
249
252
  });
@@ -269,28 +272,47 @@ export const AxisY = (props) => {
269
272
  .attr('class', b('title'))
270
273
  .attr('text-anchor', 'middle')
271
274
  .attr('font-size', (d) => d.title.style.fontSize)
272
- .attr('transform', (d) => {
273
- const titleRows = wrapText({
274
- text: d.title.text,
275
- style: d.title.style,
276
- width: height,
275
+ .call(async (s) => {
276
+ s.each(async function prepareAxisTitle(d) {
277
+ if (!this) {
278
+ return;
279
+ }
280
+ const selection = select(this);
281
+ const titleRows = await wrapText({
282
+ text: d.title.text,
283
+ style: d.title.style,
284
+ width: height,
285
+ });
286
+ const rowCount = Math.min(titleRows.length, d.title.maxRowCount);
287
+ const { x, y } = getTitlePosition({ axis: d, axisHeight: height, rowCount });
288
+ const angle = d.position === 'left' ? -90 : 90;
289
+ selection.attr('transform', `translate(${x}, ${y}) rotate(${angle})`);
290
+ const axisTitleRows = await getAxisTitleRows({ axis: d, textMaxWidth: height });
291
+ selection
292
+ .selectAll('tspan')
293
+ .data(axisTitleRows)
294
+ .join('tspan')
295
+ .attr('x', 0)
296
+ .attr('y', (titleRow) => titleRow.y)
297
+ .text((titleRow) => titleRow.text)
298
+ .each((_d, index, nodes) => {
299
+ if (index === nodes.length - 1) {
300
+ handleOverflowingText(nodes[index], height);
301
+ }
302
+ });
277
303
  });
278
- const rowCount = Math.min(titleRows.length, d.title.maxRowCount);
279
- const { x, y } = getTitlePosition({ axis: d, axisHeight: height, rowCount });
280
- const angle = d.position === 'left' ? -90 : 90;
281
- return `translate(${x}, ${y}) rotate(${angle})`;
282
- })
283
- .selectAll('tspan')
284
- .data((d) => getAxisTitleRows({ axis: d, textMaxWidth: height }))
285
- .join('tspan')
286
- .attr('x', 0)
287
- .attr('y', (d) => d.y)
288
- .text((d) => d.text)
289
- .each((_d, index, nodes) => {
290
- if (index === nodes.length - 1) {
291
- handleOverflowingText(nodes[index], height);
292
- }
304
+ return s;
293
305
  });
294
- }, [allAxes, width, height, scale, split, bottomLimit]);
306
+ }, [
307
+ allAxes,
308
+ width,
309
+ height,
310
+ scale,
311
+ split,
312
+ bottomLimit,
313
+ lineGenerator,
314
+ plotBeforeRef,
315
+ plotAfterRef,
316
+ ]);
295
317
  return React.createElement("g", { ref: ref, className: b('container') });
296
318
  };
@@ -1,4 +1,6 @@
1
1
  import React from 'react';
2
+ import { ArrowRotateLeft } from '@gravity-ui/icons';
3
+ import { Button, ButtonIcon } from '@gravity-ui/uikit';
2
4
  import { useCrosshair } from '../../hooks';
3
5
  import { EventType, block, getDispatcher } from '../../utils';
4
6
  import { AxisX, AxisY } from '../Axis';
@@ -15,10 +17,13 @@ export const ChartInner = (props) => {
15
17
  var _a, _b, _c, _d;
16
18
  const { width, height, data } = props;
17
19
  const svgRef = React.useRef(null);
18
- const htmlLayerRef = React.useRef(null);
20
+ const [htmlLayout, setHtmlLayout] = React.useState(null);
19
21
  const plotRef = React.useRef(null);
22
+ const plotBeforeRef = React.useRef(null);
23
+ const plotAfterRef = React.useRef(null);
20
24
  const dispatcher = React.useMemo(() => getDispatcher(), []);
21
- 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 }));
25
+ const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, handleZoomReset, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, tooltip, xAxis, xScale, yAxis, yScale, svgXPos, svgBottomPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { dispatcher,
26
+ htmlLayout, svgContainer: svgRef.current, plotNode: plotRef.current }));
22
27
  const { tooltipPinned, togglePinTooltip, unpinTooltip } = useChartInnerState({
23
28
  dispatcher,
24
29
  tooltip,
@@ -42,7 +47,7 @@ export const ChartInner = (props) => {
42
47
  const pointerMoveHandler = (_d = (_c = data.chart) === null || _c === void 0 ? void 0 : _c.events) === null || _d === void 0 ? void 0 : _d.pointermove;
43
48
  useCrosshair({
44
49
  split: preparedSplit,
45
- plotElement: plotRef.current,
50
+ plotElement: plotAfterRef.current,
46
51
  boundsOffsetLeft,
47
52
  boundsOffsetTop,
48
53
  width: boundsWidth,
@@ -74,20 +79,28 @@ export const ChartInner = (props) => {
74
79
  }
75
80
  }, [prevWidth, width, prevHeight, height, tooltipPinned, unpinTooltip]);
76
81
  return (React.createElement("div", { className: b() },
77
- React.createElement("svg", { ref: svgRef, width: width, height: height, onMouseMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave, onTouchStart: throttledHandleTouchMove, onTouchMove: throttledHandleTouchMove, onClick: handleChartClick },
82
+ React.createElement("svg", { ref: svgRef, width: width, height: height,
83
+ // We use onPointerMove here because onMouseMove works incorrectly when the zoom setting is enabled:
84
+ // when starting to select an area, the tooltip remains in the position where the selection began
85
+ onPointerMove: throttledHandleMouseMove, onMouseLeave: handleMouseLeave, onTouchStart: throttledHandleTouchMove, onTouchMove: throttledHandleTouchMove, onClick: handleChartClick },
78
86
  title && React.createElement(Title, Object.assign({}, title, { chartWidth: width })),
79
87
  React.createElement("g", { transform: `translate(0, ${boundsOffsetTop})` }, preparedSplit.plots.map((plot, index) => {
80
88
  return React.createElement(PlotTitle, { key: `plot-${index}`, title: plot.title });
81
89
  })),
82
90
  React.createElement("g", { width: boundsWidth, height: boundsHeight, transform: `translate(${[boundsOffsetLeft, boundsOffsetTop].join(',')})`, ref: plotRef },
83
91
  xScale && (yScale === null || yScale === void 0 ? void 0 : yScale.length) && (React.createElement(React.Fragment, null,
84
- React.createElement(AxisY, { bottomLimit: svgBottomPos, axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotRef: plotRef }),
85
- React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
86
- React.createElement(AxisX, { leftmostLimit: svgXPos, axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotRef: plotRef })))),
87
- shapes),
88
- preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip, htmlLayout: htmlLayerRef.current }))),
89
- React.createElement("div", { className: b('html-layer'), ref: htmlLayerRef, style: {
92
+ React.createElement(AxisY, { bottomLimit: svgBottomPos, axes: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale, split: preparedSplit, plotBeforeRef: plotBeforeRef, plotAfterRef: plotAfterRef }),
93
+ xAxis && (React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
94
+ React.createElement(AxisX, { leftmostLimit: svgXPos, axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, split: preparedSplit, plotBeforeRef: plotBeforeRef, plotAfterRef: plotAfterRef }))))),
95
+ React.createElement("g", { ref: plotBeforeRef }),
96
+ shapes,
97
+ React.createElement("g", { ref: plotAfterRef })),
98
+ (preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && legendConfig && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip, htmlLayout: htmlLayout }))),
99
+ React.createElement("div", { className: b('html-layer'), ref: setHtmlLayout, style: {
90
100
  '--g-html-layout-transform': `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
91
101
  } }),
102
+ handleZoomReset && (React.createElement(Button, { style: { position: 'absolute', top: 0, right: 0 }, onClick: handleZoomReset },
103
+ React.createElement(ButtonIcon, null,
104
+ React.createElement(ArrowRotateLeft, null)))),
92
105
  React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: tooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0], onOutsideClick: unpinTooltip, tooltipPinned: tooltipPinned })));
93
106
  };
@@ -14,7 +14,7 @@ type Props = {
14
14
  togglePinTooltip: ChartInnerState['togglePinTooltip'];
15
15
  tooltipPinned: boolean;
16
16
  unpinTooltip: ChartInnerState['unpinTooltip'];
17
- xAxis: PreparedAxis;
17
+ xAxis: PreparedAxis | null;
18
18
  yAxis: PreparedAxis[];
19
19
  tooltipThrottle: number;
20
20
  };
@@ -1,10 +1,12 @@
1
1
  import React from 'react';
2
2
  import type { Dispatch } from 'd3';
3
+ import type { PreparedAxis } from '../../hooks';
3
4
  import type { ChartInnerProps } from './types';
4
5
  type Props = ChartInnerProps & {
5
6
  dispatcher: Dispatch<object>;
6
7
  htmlLayout: HTMLElement | null;
7
8
  svgContainer: SVGGElement | null;
9
+ plotNode: SVGGElement | null;
8
10
  };
9
11
  export declare function useChartInnerProps(props: Props): {
10
12
  svgBottomPos: number | undefined;
@@ -14,6 +16,7 @@ export declare function useChartInnerProps(props: Props): {
14
16
  boundsOffsetTop: number;
15
17
  boundsWidth: number;
16
18
  handleLegendItemClick: import("../../hooks").OnLegendItemClick;
19
+ handleZoomReset: (() => void) | undefined;
17
20
  legendConfig: {
18
21
  offset: {
19
22
  left: number;
@@ -25,9 +28,9 @@ export declare function useChartInnerProps(props: Props): {
25
28
  end: number;
26
29
  }[];
27
30
  } | undefined;
28
- };
29
- legendItems: import("../../hooks").LegendItem[][];
30
- preparedLegend: import("../../hooks").PreparedLegend;
31
+ } | undefined;
32
+ legendItems: never[] | import("../../hooks").LegendItem[][];
33
+ preparedLegend: import("../../hooks").PreparedLegend | null;
31
34
  preparedSeries: import("../../hooks").PreparedSeries[];
32
35
  preparedSplit: import("../../hooks").PreparedSplit;
33
36
  prevHeight: number | undefined;
@@ -41,9 +44,9 @@ export declare function useChartInnerProps(props: Props): {
41
44
  enabled: boolean;
42
45
  throttle: number;
43
46
  };
44
- xAxis: import("../../hooks").PreparedAxis;
47
+ xAxis: PreparedAxis | null;
45
48
  xScale: import("../../hooks").ChartScale | undefined;
46
- yAxis: import("../../hooks").PreparedAxis[];
49
+ yAxis: PreparedAxis[];
47
50
  yScale: import("../../hooks").ChartScale[] | undefined;
48
51
  };
49
52
  export {};
@@ -1,28 +1,44 @@
1
1
  import React from 'react';
2
2
  import { useAxisScales, useChartDimensions, useChartOptions, usePrevious, useSeries, useShapes, useSplit, } from '../../hooks';
3
+ import { getZoomedSeriesData } from '../../hooks/hooks-utils';
3
4
  import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
4
5
  import { getPreparedXAxis } from '../../hooks/useChartOptions/x-axis';
5
6
  import { getPreparedYAxis } from '../../hooks/useChartOptions/y-axis';
7
+ import { useZoom } from '../../hooks/useZoom';
8
+ import { hasAtLeastOneSeriesDataPerPlot } from './utils';
6
9
  export function useChartInnerProps(props) {
7
10
  var _a;
8
- const { width, height, data, dispatcher, htmlLayout, svgContainer } = props;
11
+ const { width, height, data, dispatcher, htmlLayout, svgContainer, plotNode } = props;
9
12
  const prevWidth = usePrevious(width);
10
13
  const prevHeight = usePrevious(height);
14
+ const [zoomState, setZoomState] = React.useState({});
11
15
  const { chart, title, tooltip, colors } = useChartOptions({ data });
12
- const xAxis = React.useMemo(() => getPreparedXAxis({ xAxis: data.xAxis, width, series: data.series.data }), [data, width]);
13
- const yAxis = React.useMemo(() => getPreparedYAxis({
14
- series: data.series.data,
15
- yAxis: data.yAxis,
16
- height,
17
- }), [data, height]);
16
+ const zoomedSeriesData = React.useMemo(() => {
17
+ return getZoomedSeriesData({
18
+ seriesData: data.series.data,
19
+ xAxis: data.xAxis,
20
+ yAxises: data.yAxis,
21
+ zoomState,
22
+ });
23
+ }, [data.series.data, data.xAxis, data.yAxis, zoomState]);
24
+ const [xAxis, setXAxis] = React.useState(null);
25
+ React.useEffect(() => {
26
+ getPreparedXAxis({ xAxis: data.xAxis, width, seriesData: zoomedSeriesData }).then((val) => setXAxis(val));
27
+ }, [data.xAxis, width, zoomedSeriesData]);
28
+ const [yAxis, setYAxis] = React.useState([]);
29
+ React.useEffect(() => {
30
+ getPreparedYAxis({ yAxis: data.yAxis, height, seriesData: zoomedSeriesData }).then((val) => setYAxis(val));
31
+ }, [data.yAxis, height, zoomedSeriesData]);
18
32
  const { legendItems, legendConfig, preparedSeries, preparedSeriesOptions, preparedLegend, handleLegendItemClick, } = useSeries({
19
33
  chartWidth: width,
20
34
  chartHeight: height,
21
35
  chartMargin: chart.margin,
22
- series: data.series,
36
+ colors,
23
37
  legend: data.legend,
38
+ originalSeriesData: data.series.data,
39
+ seriesData: zoomedSeriesData,
40
+ seriesOptions: data.series.options,
24
41
  preparedYAxis: yAxis,
25
- colors,
26
42
  });
27
43
  const { boundsWidth, boundsHeight } = useChartDimensions({
28
44
  width,
@@ -37,6 +53,8 @@ export function useChartInnerProps(props) {
37
53
  const { xScale, yScale } = useAxisScales({
38
54
  boundsWidth,
39
55
  boundsHeight,
56
+ hasZoomX: Boolean(zoomState.x),
57
+ hasZoomY: Boolean(zoomState.y),
40
58
  series: preparedSeries,
41
59
  xAxis,
42
60
  yAxis,
@@ -55,6 +73,30 @@ export function useChartInnerProps(props) {
55
73
  split: preparedSplit,
56
74
  htmlLayout,
57
75
  });
76
+ const handleAttemptToSetZoomState = React.useCallback((nextZoomState) => {
77
+ const nextZoomedSeriesData = getZoomedSeriesData({
78
+ seriesData: zoomedSeriesData,
79
+ xAxis: data.xAxis,
80
+ yAxises: data.yAxis,
81
+ zoomState: nextZoomState,
82
+ });
83
+ const hasData = hasAtLeastOneSeriesDataPerPlot(nextZoomedSeriesData, yAxis);
84
+ if (hasData) {
85
+ setZoomState(nextZoomState);
86
+ }
87
+ }, [data.xAxis, data.yAxis, yAxis, zoomedSeriesData]);
88
+ useZoom({
89
+ node: plotNode,
90
+ onUpdate: handleAttemptToSetZoomState,
91
+ plotContainerHeight: boundsHeight,
92
+ plotContainerWidth: boundsWidth,
93
+ preparedSplit,
94
+ preparedZoom: chart.zoom,
95
+ xAxis,
96
+ xScale,
97
+ yAxis,
98
+ yScale,
99
+ });
58
100
  const boundsOffsetTop = chart.margin.top;
59
101
  // We need to calculate the width of each left axis because the first axis can be hidden
60
102
  const boundsOffsetLeft = chart.margin.left +
@@ -69,6 +111,9 @@ export function useChartInnerProps(props) {
69
111
  return acc;
70
112
  }, 0);
71
113
  const { x, bottom } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
114
+ const handleZoomReset = React.useCallback(() => {
115
+ setZoomState({});
116
+ }, []);
72
117
  return {
73
118
  svgBottomPos: bottom,
74
119
  svgXPos: x,
@@ -77,6 +122,7 @@ export function useChartInnerProps(props) {
77
122
  boundsOffsetTop,
78
123
  boundsWidth,
79
124
  handleLegendItemClick,
125
+ handleZoomReset: Object.keys(zoomState).length > 0 ? handleZoomReset : undefined,
80
126
  legendConfig,
81
127
  legendItems,
82
128
  preparedLegend,
@@ -0,0 +1,3 @@
1
+ import type { PreparedAxis } from '../../hooks/useChartOptions/types';
2
+ import type { ChartSeries } from '../../types';
3
+ export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: ChartSeries[], yAxises?: PreparedAxis[]): boolean;
@@ -0,0 +1,28 @@
1
+ export function hasAtLeastOneSeriesDataPerPlot(seriesData, yAxises = []) {
2
+ const hasDataMap = new Map();
3
+ yAxises.forEach((yAxis) => {
4
+ var _a;
5
+ const plotIndex = (_a = yAxis.plotIndex) !== null && _a !== void 0 ? _a : 0;
6
+ if (!hasDataMap.has(plotIndex)) {
7
+ hasDataMap.set(plotIndex, false);
8
+ }
9
+ });
10
+ if (hasDataMap.size === 0) {
11
+ return false;
12
+ }
13
+ seriesData.forEach((seriesDataChunk) => {
14
+ var _a;
15
+ let yAxisIndex = 0;
16
+ if ('yAxis' in seriesDataChunk && typeof seriesDataChunk.yAxis === 'number') {
17
+ yAxisIndex = seriesDataChunk.yAxis;
18
+ }
19
+ const yAxis = yAxises[yAxisIndex];
20
+ const plotIndex = (_a = yAxis === null || yAxis === void 0 ? void 0 : yAxis.plotIndex) !== null && _a !== void 0 ? _a : 0;
21
+ if (!hasDataMap.get(plotIndex)) {
22
+ if (seriesDataChunk.data.length > 0) {
23
+ hasDataMap.set(plotIndex, true);
24
+ }
25
+ }
26
+ });
27
+ return [...hasDataMap.values()].every((hasData) => hasData);
28
+ }