@gravity-ui/charts 1.9.0 → 1.10.1

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 +149 -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 +203 -195
  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 +29 -28
  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 +45 -30
  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 +149 -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 +203 -195
  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 +29 -28
  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 +45 -30
  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
@@ -7,7 +7,8 @@ type Props = {
7
7
  height: number;
8
8
  scale: ChartScale;
9
9
  split: PreparedSplit;
10
- plotRef?: React.MutableRefObject<SVGGElement | null>;
10
+ plotBeforeRef?: React.MutableRefObject<SVGGElement | null>;
11
+ plotAfterRef?: React.MutableRefObject<SVGGElement | null>;
11
12
  leftmostLimit?: number;
12
13
  };
13
14
  export declare function getTitlePosition(args: {
@@ -42,151 +42,157 @@ export function getTitlePosition(args) {
42
42
  return { x, y };
43
43
  }
44
44
  export const AxisX = React.memo(function AxisX(props) {
45
- const { axis, width, height: totalHeight, scale, split, plotRef, leftmostLimit } = props;
45
+ const { axis, width, height: totalHeight, scale, split, plotBeforeRef, plotAfterRef, leftmostLimit, } = props;
46
46
  const ref = React.useRef(null);
47
47
  React.useEffect(() => {
48
- if (!ref.current) {
49
- return;
50
- }
51
- const svgElement = select(ref.current);
52
- svgElement.selectAll('*').remove();
53
- const plotDataAttr = 'data-plot-x';
54
- let plotContainer = null;
55
- if (plotRef === null || plotRef === void 0 ? void 0 : plotRef.current) {
56
- plotContainer = select(plotRef.current);
57
- plotContainer.selectAll(`[${plotDataAttr}]`).remove();
58
- }
59
- if (!axis.visible) {
60
- return;
61
- }
62
- let tickItems = [];
63
- if (axis.grid.enabled) {
64
- tickItems = new Array(split.plots.length || 1).fill(null).map((_, index) => {
65
- var _a, _b;
66
- const top = ((_a = split.plots[index]) === null || _a === void 0 ? void 0 : _a.top) || 0;
67
- const height = ((_b = split.plots[index]) === null || _b === void 0 ? void 0 : _b.height) || totalHeight;
68
- return [-top, -(top + height)];
69
- });
70
- }
71
- const axisScale = scale;
72
- const xAxisGenerator = axisBottom({
73
- leftmostLimit,
74
- scale: axisScale,
75
- ticks: {
76
- items: tickItems,
77
- labelFormat: getLabelFormatter({ axis, scale }),
78
- labelsPaddings: axis.labels.padding,
79
- labelsMargin: axis.labels.margin,
80
- labelsStyle: axis.labels.style,
81
- labelsMaxWidth: axis.labels.maxWidth,
82
- labelsLineHeight: axis.labels.lineHeight,
83
- count: getTicksCount({ axis, range: width }),
84
- maxTickCount: getMaxTickCount({ axis, width }),
85
- rotation: axis.labels.rotation,
86
- },
87
- domain: {
88
- size: width,
89
- color: axis.lineColor,
90
- },
91
- });
92
- svgElement.call(xAxisGenerator).attr('class', b());
93
- // add an axis header if necessary
94
- if (axis.title.text) {
95
- const titleRows = getAxisTitleRows({ axis, textMaxWidth: width });
96
- svgElement
97
- .append('text')
98
- .attr('class', b('title'))
99
- .attr('transform', () => {
100
- const { x, y } = getTitlePosition({ axis, width, rowCount: titleRows.length });
101
- return `translate(${x}, ${y})`;
102
- })
103
- .attr('font-size', axis.title.style.fontSize)
104
- .attr('text-anchor', 'middle')
105
- .selectAll('tspan')
106
- .data(titleRows)
107
- .join('tspan')
108
- .attr('x', 0)
109
- .attr('y', (d) => d.y)
110
- .text((d) => d.text)
111
- .each((_d, index, nodes) => {
112
- if (index === axis.title.maxRowCount - 1) {
113
- handleOverflowingText(nodes[index], width);
114
- }
48
+ (async () => {
49
+ if (!ref.current) {
50
+ return;
51
+ }
52
+ const svgElement = select(ref.current);
53
+ svgElement.selectAll('*').remove();
54
+ const plotDataAttr = 'data-plot-x';
55
+ let plotBeforeContainer = null;
56
+ let plotAfterContainer = null;
57
+ if (plotBeforeRef === null || plotBeforeRef === void 0 ? void 0 : plotBeforeRef.current) {
58
+ plotBeforeContainer = select(plotBeforeRef.current);
59
+ plotBeforeContainer.selectAll(`[${plotDataAttr}]`).remove();
60
+ }
61
+ if (plotAfterRef === null || plotAfterRef === void 0 ? void 0 : plotAfterRef.current) {
62
+ plotAfterContainer = select(plotAfterRef.current);
63
+ plotAfterContainer.selectAll(`[${plotDataAttr}]`).remove();
64
+ }
65
+ if (!axis.visible) {
66
+ return;
67
+ }
68
+ let tickItems = [];
69
+ if (axis.grid.enabled) {
70
+ tickItems = new Array(split.plots.length || 1).fill(null).map((_, index) => {
71
+ var _a, _b;
72
+ const top = ((_a = split.plots[index]) === null || _a === void 0 ? void 0 : _a.top) || 0;
73
+ const height = ((_b = split.plots[index]) === null || _b === void 0 ? void 0 : _b.height) || totalHeight;
74
+ return [-top, -(top + height)];
75
+ });
76
+ }
77
+ const axisScale = scale;
78
+ const xAxisGenerator = await axisBottom({
79
+ leftmostLimit,
80
+ scale: axisScale,
81
+ ticks: {
82
+ items: tickItems,
83
+ labelFormat: getLabelFormatter({ axis, scale }),
84
+ labelsPaddings: axis.labels.padding,
85
+ labelsMargin: axis.labels.margin,
86
+ labelsStyle: axis.labels.style,
87
+ labelsMaxWidth: axis.labels.maxWidth,
88
+ labelsLineHeight: axis.labels.lineHeight,
89
+ count: getTicksCount({ axis, range: width }),
90
+ maxTickCount: getMaxTickCount({ axis, width }),
91
+ rotation: axis.labels.rotation,
92
+ },
93
+ domain: {
94
+ size: width,
95
+ color: axis.lineColor,
96
+ },
115
97
  });
116
- }
117
- // add plot bands
118
- if (plotContainer && axis.plotBands.length > 0) {
119
- const plotBandDataAttr = 'plot-x-band';
120
- const plotBandsSelection = plotContainer
121
- .selectAll(`[${plotBandDataAttr}]`)
122
- .data(axis.plotBands)
123
- .join('g')
124
- .attr(plotDataAttr, 1)
125
- .attr(plotBandDataAttr, 1);
126
- plotBandsSelection
127
- .append('rect')
128
- .attr('x', (band) => {
129
- var _a, _b;
130
- const { from, to } = getBandsPosition({ band, axisScale, axis: 'x' });
131
- const halfBandwidth = ((_b = (_a = axisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(axisScale)) !== null && _b !== void 0 ? _b : 0) / 2;
132
- const startPos = halfBandwidth + Math.min(from, to);
133
- return Math.max(0, startPos);
134
- })
135
- .attr('width', (band) => {
136
- const { from, to } = getBandsPosition({ band, axisScale, axis: 'x' });
137
- const startPos = width - Math.min(from, to);
138
- const endPos = Math.min(Math.abs(to - from), startPos);
139
- return Math.min(endPos, width);
140
- })
141
- .attr('y', 0)
142
- .attr('height', totalHeight)
143
- .attr('fill', (band) => band.color)
144
- .attr('opacity', (band) => band.opacity);
145
- plotBandsSelection.each((plotBandData, i, nodes) => {
146
- const plotLineSelection = select(nodes[i]);
147
- if (plotBandData.layerPlacement === 'before') {
148
- plotLineSelection.lower();
149
- }
150
- else {
151
- plotLineSelection.raise();
152
- }
153
- });
154
- }
155
- // add plot lines
156
- if (plotContainer && axis.plotLines.length > 0) {
157
- const plotLineDataAttr = 'plot-x-line';
158
- const plotLinesSelection = plotContainer
159
- .selectAll(`[${plotLineDataAttr}]`)
160
- .data(axis.plotLines)
161
- .join('g')
162
- .attr(plotDataAttr, 1)
163
- .attr(plotLineDataAttr, 1);
164
- const lineGenerator = line();
165
- plotLinesSelection
166
- .append('path')
167
- .attr('d', (plotLine) => {
168
- const plotLineValue = Number(axisScale(plotLine.value));
169
- const points = [
170
- [plotLineValue, 0],
171
- [plotLineValue, totalHeight],
172
- ];
173
- return lineGenerator(points);
174
- })
175
- .attr('stroke', (plotLine) => plotLine.color)
176
- .attr('stroke-width', (plotLine) => plotLine.width)
177
- .attr('stroke-dasharray', (plotLine) => getLineDashArray(plotLine.dashStyle, plotLine.width))
178
- .attr('opacity', (plotLine) => plotLine.opacity);
179
- // set layer placement
180
- plotLinesSelection.each((plotLineData, i, nodes) => {
181
- const plotLineSelection = select(nodes[i]);
182
- if (plotLineData.layerPlacement === 'before') {
183
- plotLineSelection.lower();
184
- }
185
- else {
186
- plotLineSelection.raise();
187
- }
188
- });
189
- }
190
- }, [axis, width, totalHeight, scale, split, plotRef, leftmostLimit]);
98
+ svgElement.call(xAxisGenerator).attr('class', b());
99
+ // add an axis header if necessary
100
+ if (axis.title.text) {
101
+ const titleRows = await getAxisTitleRows({ axis, textMaxWidth: width });
102
+ const titleClassName = b('title');
103
+ svgElement.selectAll(`.${titleClassName}`).remove();
104
+ svgElement
105
+ .append('text')
106
+ .attr('class', titleClassName)
107
+ .attr('transform', () => {
108
+ const { x, y } = getTitlePosition({ axis, width, rowCount: titleRows.length });
109
+ return `translate(${x}, ${y})`;
110
+ })
111
+ .attr('font-size', axis.title.style.fontSize)
112
+ .attr('text-anchor', 'middle')
113
+ .selectAll('tspan')
114
+ .data(titleRows)
115
+ .join('tspan')
116
+ .attr('x', 0)
117
+ .attr('y', (d) => d.y)
118
+ .text((d) => d.text)
119
+ .each((_d, index, nodes) => {
120
+ if (index === axis.title.maxRowCount - 1) {
121
+ handleOverflowingText(nodes[index], width);
122
+ }
123
+ });
124
+ }
125
+ // add plot bands
126
+ if (axis.plotBands.length > 0) {
127
+ const plotBandDataAttr = 'plot-x-band';
128
+ const setPlotBands = (plotContainer, plotBands) => {
129
+ if (!plotContainer || !plotBands.length) {
130
+ return;
131
+ }
132
+ const plotBandsSelection = plotContainer
133
+ .selectAll(`[${plotBandDataAttr}]`)
134
+ .remove()
135
+ .data(plotBands)
136
+ .join('g')
137
+ .attr(plotDataAttr, 1)
138
+ .attr(plotBandDataAttr, 1);
139
+ plotBandsSelection
140
+ .append('rect')
141
+ .attr('x', (band) => {
142
+ var _a, _b;
143
+ const { from, to } = getBandsPosition({ band, axisScale, axis: 'x' });
144
+ const halfBandwidth = ((_b = (_a = axisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(axisScale)) !== null && _b !== void 0 ? _b : 0) / 2;
145
+ const startPos = halfBandwidth + Math.min(from, to);
146
+ return Math.max(0, startPos);
147
+ })
148
+ .attr('width', (band) => {
149
+ const { from, to } = getBandsPosition({ band, axisScale, axis: 'x' });
150
+ const startPos = width - Math.min(from, to);
151
+ const endPos = Math.min(Math.abs(to - from), startPos);
152
+ return Math.min(endPos, width);
153
+ })
154
+ .attr('y', 0)
155
+ .attr('height', totalHeight)
156
+ .attr('fill', (band) => band.color)
157
+ .attr('opacity', (band) => band.opacity);
158
+ };
159
+ setPlotBands(plotBeforeContainer, axis.plotBands.filter((d) => d.layerPlacement === 'before'));
160
+ setPlotBands(plotAfterContainer, axis.plotBands.filter((d) => d.layerPlacement === 'after'));
161
+ }
162
+ // add plot lines
163
+ if (axis.plotLines.length > 0) {
164
+ const plotLineDataAttr = 'plot-x-line';
165
+ const setPlotLines = (plotContainer, plotLines) => {
166
+ if (!plotContainer || !plotLines.length) {
167
+ return;
168
+ }
169
+ const plotLinesSelection = plotContainer
170
+ .selectAll(`[${plotLineDataAttr}]`)
171
+ .remove()
172
+ .data(plotLines)
173
+ .join('g')
174
+ .attr(plotDataAttr, 1)
175
+ .attr(plotLineDataAttr, 1);
176
+ const lineGenerator = line();
177
+ plotLinesSelection
178
+ .append('path')
179
+ .attr('d', (plotLine) => {
180
+ const plotLineValue = Number(axisScale(plotLine.value));
181
+ const points = [
182
+ [plotLineValue, 0],
183
+ [plotLineValue, totalHeight],
184
+ ];
185
+ return lineGenerator(points);
186
+ })
187
+ .attr('stroke', (plotLine) => plotLine.color)
188
+ .attr('stroke-width', (plotLine) => plotLine.width)
189
+ .attr('stroke-dasharray', (plotLine) => getLineDashArray(plotLine.dashStyle, plotLine.width))
190
+ .attr('opacity', (plotLine) => plotLine.opacity);
191
+ };
192
+ setPlotLines(plotBeforeContainer, axis.plotLines.filter((d) => d.layerPlacement === 'before'));
193
+ setPlotLines(plotAfterContainer, axis.plotLines.filter((d) => d.layerPlacement === 'after'));
194
+ }
195
+ })();
196
+ }, [axis, width, totalHeight, scale, split, leftmostLimit, plotBeforeRef, plotAfterRef]);
191
197
  return React.createElement("g", { ref: ref });
192
198
  });
@@ -7,7 +7,8 @@ type Props = {
7
7
  width: number;
8
8
  height: number;
9
9
  split: PreparedSplit;
10
- plotRef?: React.MutableRefObject<SVGGElement | null>;
10
+ plotBeforeRef?: React.MutableRefObject<SVGGElement | null>;
11
+ plotAfterRef?: React.MutableRefObject<SVGGElement | null>;
11
12
  bottomLimit?: number;
12
13
  };
13
14
  export declare const AxisY: (props: Props) => React.JSX.Element;
@@ -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
  };