@gravity-ui/charts 1.21.0 → 1.22.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.
@@ -14,13 +14,14 @@ import { Tooltip } from '../Tooltip';
14
14
  import { useChartInnerHandlers } from './useChartInnerHandlers';
15
15
  import { useChartInnerProps } from './useChartInnerProps';
16
16
  import { useChartInnerState } from './useChartInnerState';
17
- import { useAsyncState } from './utils';
17
+ import { getResetZoomButtonStyle, useAsyncState } from './utils';
18
18
  import './styles.css';
19
19
  const b = block('chart');
20
20
  export const ChartInner = (props) => {
21
21
  var _a, _b, _c, _d;
22
22
  const { width, height, data } = props;
23
23
  const svgRef = React.useRef(null);
24
+ const resetZoomButtonRef = React.useRef(null);
24
25
  const [htmlLayout, setHtmlLayout] = React.useState(null);
25
26
  const plotRef = React.useRef(null);
26
27
  const plotBeforeRef = React.useRef(null);
@@ -39,7 +40,7 @@ export const ChartInner = (props) => {
39
40
  dispatcher,
40
41
  tooltip: preparedTooltip,
41
42
  });
42
- const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, xAxis, xScale, yAxis, yScale, svgXPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
43
+ const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, preparedZoom, prevHeight, prevWidth, shapes, shapesData, svgXPos, title, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
43
44
  dispatcher,
44
45
  htmlLayout, plotNode: plotRef.current, svgContainer: svgRef.current, updateZoomState,
45
46
  zoomState }));
@@ -143,7 +144,10 @@ export const ChartInner = (props) => {
143
144
  React.createElement("div", { className: b('html-layer'), ref: setHtmlLayout, style: {
144
145
  '--g-html-layout-transform': `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
145
146
  } }),
146
- Object.keys(zoomState).length > 0 && (React.createElement(Button, { style: { position: 'absolute', top: 0, right: 0 }, onClick: () => updateZoomState({}) },
147
+ Object.keys(zoomState).length > 0 && preparedZoom && (React.createElement(Button, { onClick: () => updateZoomState({}), ref: resetZoomButtonRef, style: getResetZoomButtonStyle(Object.assign({ boundsHeight,
148
+ boundsOffsetLeft,
149
+ boundsOffsetTop,
150
+ boundsWidth, node: resetZoomButtonRef.current, titleHeight: title === null || title === void 0 ? void 0 : title.height }, preparedZoom.resetButton)) },
147
151
  React.createElement(ButtonIcon, null,
148
152
  React.createElement(ArrowRotateLeft, null)))),
149
153
  React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: preparedTooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0], onOutsideClick: unpinTooltip, tooltipPinned: tooltipPinned })));
@@ -12,8 +12,6 @@ type Props = ChartInnerProps & {
12
12
  zoomState: Partial<ZoomState>;
13
13
  };
14
14
  export declare function useChartInnerProps(props: Props): {
15
- svgBottomPos: number | undefined;
16
- svgTopPos: number | undefined;
17
15
  svgXPos: number | undefined;
18
16
  boundsHeight: number;
19
17
  boundsOffsetLeft: number;
@@ -38,6 +36,22 @@ export declare function useChartInnerProps(props: Props): {
38
36
  preparedLegend: import("../../hooks").PreparedLegend | null;
39
37
  preparedSeries: import("../../hooks").PreparedSeries[];
40
38
  preparedSplit: import("../../hooks").PreparedSplit;
39
+ preparedZoom: Required<{
40
+ type?: ("x" | "y" | "xy") | undefined;
41
+ brush?: Required<{
42
+ style?: Required<{
43
+ fillOpacity?: number | undefined;
44
+ } | undefined>;
45
+ } | undefined>;
46
+ resetButton?: Required<{
47
+ align?: ("bottom-left" | "bottom-right" | "top-left" | "top-right") | undefined;
48
+ offset?: Required<{
49
+ x?: number | undefined;
50
+ y?: number | undefined;
51
+ } | undefined>;
52
+ relativeTo?: ("chart-box" | "plot-box") | undefined;
53
+ } | undefined>;
54
+ }> | null;
41
55
  prevHeight: number | undefined;
42
56
  prevWidth: number | undefined;
43
57
  shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
@@ -138,10 +138,8 @@ export function useChartInnerProps(props) {
138
138
  }
139
139
  return acc;
140
140
  }, 0);
141
- const { bottom, top, x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
141
+ const { x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
142
142
  return {
143
- svgBottomPos: bottom,
144
- svgTopPos: top,
145
143
  svgXPos: x,
146
144
  boundsHeight,
147
145
  boundsOffsetLeft,
@@ -154,6 +152,7 @@ export function useChartInnerProps(props) {
154
152
  preparedLegend,
155
153
  preparedSeries,
156
154
  preparedSplit,
155
+ preparedZoom: chart.zoom,
157
156
  prevHeight,
158
157
  prevWidth,
159
158
  shapes,
@@ -1,4 +1,13 @@
1
+ import React from 'react';
1
2
  import type { PreparedSeries } from '../../hooks';
2
- import type { PreparedAxis } from '../../hooks/useChartOptions/types';
3
+ import type { PreparedAxis, PreparedZoom } from '../../hooks/useChartOptions/types';
3
4
  export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: PreparedSeries[], yAxes?: PreparedAxis[]): boolean;
4
5
  export declare function useAsyncState<T>(value: T, setState: () => Promise<T>): T;
6
+ export declare function getResetZoomButtonStyle(args: {
7
+ boundsHeight: number;
8
+ boundsOffsetLeft: number;
9
+ boundsOffsetTop: number;
10
+ boundsWidth: number;
11
+ node: HTMLElement | null;
12
+ titleHeight?: number;
13
+ } & PreparedZoom['resetButton']): React.CSSProperties;
@@ -47,3 +47,63 @@ export function useAsyncState(value, setState) {
47
47
  }, [setState]);
48
48
  return stateValue;
49
49
  }
50
+ export function getResetZoomButtonStyle(args) {
51
+ const { align, boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, node, offset, relativeTo, titleHeight, } = args;
52
+ const style = {
53
+ position: 'absolute',
54
+ transform: `translate(${offset.x}px, ${offset.y}px)`,
55
+ };
56
+ switch (relativeTo) {
57
+ case 'chart-box': {
58
+ switch (align) {
59
+ case 'bottom-left': {
60
+ style.bottom = 0;
61
+ style.left = 0;
62
+ break;
63
+ }
64
+ case 'bottom-right': {
65
+ style.bottom = 0;
66
+ style.right = 0;
67
+ break;
68
+ }
69
+ case 'top-left': {
70
+ style.top = 0;
71
+ style.left = 0;
72
+ break;
73
+ }
74
+ case 'top-right': {
75
+ style.top = 0;
76
+ style.right = 0;
77
+ break;
78
+ }
79
+ }
80
+ break;
81
+ }
82
+ case 'plot-box': {
83
+ switch (align) {
84
+ case 'bottom-left': {
85
+ style.left = boundsOffsetLeft;
86
+ style.top = boundsHeight - ((node === null || node === void 0 ? void 0 : node.clientHeight) || 0) + (titleHeight || 0);
87
+ break;
88
+ }
89
+ case 'bottom-right': {
90
+ style.left = boundsWidth + boundsOffsetLeft - ((node === null || node === void 0 ? void 0 : node.clientWidth) || 0);
91
+ style.top = boundsHeight - ((node === null || node === void 0 ? void 0 : node.clientHeight) || 0) + (titleHeight || 0);
92
+ break;
93
+ }
94
+ case 'top-left': {
95
+ style.left = boundsOffsetLeft;
96
+ style.top = boundsOffsetTop;
97
+ break;
98
+ }
99
+ case 'top-right': {
100
+ style.left = boundsWidth + boundsOffsetLeft - ((node === null || node === void 0 ? void 0 : node.clientWidth) || 0);
101
+ style.top = boundsOffsetTop;
102
+ break;
103
+ }
104
+ }
105
+ break;
106
+ }
107
+ }
108
+ return style;
109
+ }
@@ -72,7 +72,7 @@ function getZoomType(args) {
72
72
  return undefined;
73
73
  }
74
74
  function getPreparedZoom(args) {
75
- var _a;
75
+ var _a, _b, _c, _d;
76
76
  const { zoom, seriesData } = args;
77
77
  if (!(zoom === null || zoom === void 0 ? void 0 : zoom.enabled)) {
78
78
  return null;
@@ -86,6 +86,11 @@ function getPreparedZoom(args) {
86
86
  brush: {
87
87
  style: Object.assign({ fillOpacity: 1 }, (_a = zoom === null || zoom === void 0 ? void 0 : zoom.brush) === null || _a === void 0 ? void 0 : _a.style),
88
88
  },
89
+ resetButton: {
90
+ align: ((_b = zoom === null || zoom === void 0 ? void 0 : zoom.resetButton) === null || _b === void 0 ? void 0 : _b.align) || 'top-right',
91
+ offset: Object.assign({ x: 0, y: 0 }, (_c = zoom === null || zoom === void 0 ? void 0 : zoom.resetButton) === null || _c === void 0 ? void 0 : _c.offset),
92
+ relativeTo: ((_d = zoom === null || zoom === void 0 ? void 0 : zoom.resetButton) === null || _d === void 0 ? void 0 : _d.relativeTo) || 'chart-box',
93
+ },
89
94
  };
90
95
  }
91
96
  export const getPreparedChart = (args) => {
@@ -33,4 +33,33 @@ export interface ChartZoom {
33
33
  fillOpacity?: number;
34
34
  };
35
35
  };
36
+ /**
37
+ * Reset zoom button configuration.
38
+ * The button appears only after the zoom has been applied.
39
+ */
40
+ resetButton?: {
41
+ /**
42
+ * The alignment of the button.
43
+ *
44
+ * @default 'top-right'
45
+ */
46
+ align?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
47
+ /**
48
+ * The offset of the button.
49
+ *
50
+ * @default {x: 0, y: 0}
51
+ */
52
+ offset?: {
53
+ x?: number;
54
+ y?: number;
55
+ };
56
+ /**
57
+ * The box to which the button is positioned relative to.
58
+ * - `chart-box` refers to the entire chart area, including titles and legends.
59
+ * - `plot-box` refers to the area where the series are drawn.
60
+ *
61
+ * @default 'chart-box'
62
+ */
63
+ relativeTo?: 'chart-box' | 'plot-box';
64
+ };
36
65
  }
@@ -1,4 +1,6 @@
1
1
  import { select } from 'd3-selection';
2
+ import { block } from '../cn';
3
+ const b = block('chart');
2
4
  export function handleOverflowingText(tSpan, maxWidth, textWidth) {
3
5
  var _a, _b, _c;
4
6
  if (!tSpan) {
@@ -186,29 +188,30 @@ function unescapeHtml(str) {
186
188
  return result.replace(value, key);
187
189
  }, str);
188
190
  }
191
+ function getCssStyle(prop, el = document.body) {
192
+ return window.getComputedStyle(el, null).getPropertyValue(prop);
193
+ }
194
+ let measureCanvas = null;
189
195
  export function getTextSizeFn({ style }) {
190
- const map = {};
191
- const setSymbolSize = async (s) => {
192
- const labels = [s === ' ' ? '&nbsp;' : s];
193
- const size = await getLabelsSize({
194
- labels,
195
- style,
196
- });
197
- map[s] = { width: size.maxWidth, height: size.maxHeight };
198
- };
196
+ var _a;
197
+ const canvas = measureCanvas || (measureCanvas = document.createElement('canvas'));
198
+ const context = canvas.getContext('2d');
199
+ if (!context) {
200
+ throw new Error("Couldn't get canvas context");
201
+ }
202
+ const element = (_a = document.getElementsByClassName(b())[0]) !== null && _a !== void 0 ? _a : document.body;
203
+ const defaultFontFamily = getCssStyle('font-family', element);
204
+ const defaultFontSize = getCssStyle('font-size', element);
205
+ const defaultFontWeight = getCssStyle('font-weight', element);
199
206
  return async (str) => {
200
- let width = 0;
201
- let height = 0;
202
- const symbols = unescapeHtml(str);
203
- for (let i = 0; i < symbols.length; i++) {
204
- const s = symbols[i];
205
- if (!map[s]) {
206
- await setSymbolSize(s);
207
- }
208
- width += map[s].width;
209
- height = Math.max(height, map[s].height);
210
- }
211
- return { width, height };
207
+ var _a, _b;
208
+ await document.fonts.ready;
209
+ context.font = `${(_a = style === null || style === void 0 ? void 0 : style.fontWeight) !== null && _a !== void 0 ? _a : defaultFontWeight} ${(_b = style === null || style === void 0 ? void 0 : style.fontSize) !== null && _b !== void 0 ? _b : defaultFontSize} ${defaultFontFamily}`;
210
+ const textMetric = context.measureText(unescapeHtml(str));
211
+ return {
212
+ width: textMetric.width,
213
+ height: textMetric.fontBoundingBoxDescent + textMetric.fontBoundingBoxAscent,
214
+ };
212
215
  };
213
216
  }
214
217
  // We ignore an inaccuracy of less than a pixel.
@@ -14,13 +14,14 @@ import { Tooltip } from '../Tooltip';
14
14
  import { useChartInnerHandlers } from './useChartInnerHandlers';
15
15
  import { useChartInnerProps } from './useChartInnerProps';
16
16
  import { useChartInnerState } from './useChartInnerState';
17
- import { useAsyncState } from './utils';
17
+ import { getResetZoomButtonStyle, useAsyncState } from './utils';
18
18
  import './styles.css';
19
19
  const b = block('chart');
20
20
  export const ChartInner = (props) => {
21
21
  var _a, _b, _c, _d;
22
22
  const { width, height, data } = props;
23
23
  const svgRef = React.useRef(null);
24
+ const resetZoomButtonRef = React.useRef(null);
24
25
  const [htmlLayout, setHtmlLayout] = React.useState(null);
25
26
  const plotRef = React.useRef(null);
26
27
  const plotBeforeRef = React.useRef(null);
@@ -39,7 +40,7 @@ export const ChartInner = (props) => {
39
40
  dispatcher,
40
41
  tooltip: preparedTooltip,
41
42
  });
42
- const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, prevHeight, prevWidth, shapes, shapesData, title, xAxis, xScale, yAxis, yScale, svgXPos, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
43
+ const { boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedSeries, preparedSplit, preparedLegend, preparedZoom, prevHeight, prevWidth, shapes, shapesData, svgXPos, title, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
43
44
  dispatcher,
44
45
  htmlLayout, plotNode: plotRef.current, svgContainer: svgRef.current, updateZoomState,
45
46
  zoomState }));
@@ -143,7 +144,10 @@ export const ChartInner = (props) => {
143
144
  React.createElement("div", { className: b('html-layer'), ref: setHtmlLayout, style: {
144
145
  '--g-html-layout-transform': `translate(${boundsOffsetLeft}px, ${boundsOffsetTop}px)`,
145
146
  } }),
146
- Object.keys(zoomState).length > 0 && (React.createElement(Button, { style: { position: 'absolute', top: 0, right: 0 }, onClick: () => updateZoomState({}) },
147
+ Object.keys(zoomState).length > 0 && preparedZoom && (React.createElement(Button, { onClick: () => updateZoomState({}), ref: resetZoomButtonRef, style: getResetZoomButtonStyle(Object.assign({ boundsHeight,
148
+ boundsOffsetLeft,
149
+ boundsOffsetTop,
150
+ boundsWidth, node: resetZoomButtonRef.current, titleHeight: title === null || title === void 0 ? void 0 : title.height }, preparedZoom.resetButton)) },
147
151
  React.createElement(ButtonIcon, null,
148
152
  React.createElement(ArrowRotateLeft, null)))),
149
153
  React.createElement(Tooltip, { dispatcher: dispatcher, tooltip: preparedTooltip, svgContainer: svgRef.current, xAxis: xAxis, yAxis: yAxis[0], onOutsideClick: unpinTooltip, tooltipPinned: tooltipPinned })));
@@ -12,8 +12,6 @@ type Props = ChartInnerProps & {
12
12
  zoomState: Partial<ZoomState>;
13
13
  };
14
14
  export declare function useChartInnerProps(props: Props): {
15
- svgBottomPos: number | undefined;
16
- svgTopPos: number | undefined;
17
15
  svgXPos: number | undefined;
18
16
  boundsHeight: number;
19
17
  boundsOffsetLeft: number;
@@ -38,6 +36,22 @@ export declare function useChartInnerProps(props: Props): {
38
36
  preparedLegend: import("../../hooks").PreparedLegend | null;
39
37
  preparedSeries: import("../../hooks").PreparedSeries[];
40
38
  preparedSplit: import("../../hooks").PreparedSplit;
39
+ preparedZoom: Required<{
40
+ type?: ("x" | "y" | "xy") | undefined;
41
+ brush?: Required<{
42
+ style?: Required<{
43
+ fillOpacity?: number | undefined;
44
+ } | undefined>;
45
+ } | undefined>;
46
+ resetButton?: Required<{
47
+ align?: ("bottom-left" | "bottom-right" | "top-left" | "top-right") | undefined;
48
+ offset?: Required<{
49
+ x?: number | undefined;
50
+ y?: number | undefined;
51
+ } | undefined>;
52
+ relativeTo?: ("chart-box" | "plot-box") | undefined;
53
+ } | undefined>;
54
+ }> | null;
41
55
  prevHeight: number | undefined;
42
56
  prevWidth: number | undefined;
43
57
  shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
@@ -138,10 +138,8 @@ export function useChartInnerProps(props) {
138
138
  }
139
139
  return acc;
140
140
  }, 0);
141
- const { bottom, top, x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
141
+ const { x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
142
142
  return {
143
- svgBottomPos: bottom,
144
- svgTopPos: top,
145
143
  svgXPos: x,
146
144
  boundsHeight,
147
145
  boundsOffsetLeft,
@@ -154,6 +152,7 @@ export function useChartInnerProps(props) {
154
152
  preparedLegend,
155
153
  preparedSeries,
156
154
  preparedSplit,
155
+ preparedZoom: chart.zoom,
157
156
  prevHeight,
158
157
  prevWidth,
159
158
  shapes,
@@ -1,4 +1,13 @@
1
+ import React from 'react';
1
2
  import type { PreparedSeries } from '../../hooks';
2
- import type { PreparedAxis } from '../../hooks/useChartOptions/types';
3
+ import type { PreparedAxis, PreparedZoom } from '../../hooks/useChartOptions/types';
3
4
  export declare function hasAtLeastOneSeriesDataPerPlot(seriesData: PreparedSeries[], yAxes?: PreparedAxis[]): boolean;
4
5
  export declare function useAsyncState<T>(value: T, setState: () => Promise<T>): T;
6
+ export declare function getResetZoomButtonStyle(args: {
7
+ boundsHeight: number;
8
+ boundsOffsetLeft: number;
9
+ boundsOffsetTop: number;
10
+ boundsWidth: number;
11
+ node: HTMLElement | null;
12
+ titleHeight?: number;
13
+ } & PreparedZoom['resetButton']): React.CSSProperties;
@@ -47,3 +47,63 @@ export function useAsyncState(value, setState) {
47
47
  }, [setState]);
48
48
  return stateValue;
49
49
  }
50
+ export function getResetZoomButtonStyle(args) {
51
+ const { align, boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, node, offset, relativeTo, titleHeight, } = args;
52
+ const style = {
53
+ position: 'absolute',
54
+ transform: `translate(${offset.x}px, ${offset.y}px)`,
55
+ };
56
+ switch (relativeTo) {
57
+ case 'chart-box': {
58
+ switch (align) {
59
+ case 'bottom-left': {
60
+ style.bottom = 0;
61
+ style.left = 0;
62
+ break;
63
+ }
64
+ case 'bottom-right': {
65
+ style.bottom = 0;
66
+ style.right = 0;
67
+ break;
68
+ }
69
+ case 'top-left': {
70
+ style.top = 0;
71
+ style.left = 0;
72
+ break;
73
+ }
74
+ case 'top-right': {
75
+ style.top = 0;
76
+ style.right = 0;
77
+ break;
78
+ }
79
+ }
80
+ break;
81
+ }
82
+ case 'plot-box': {
83
+ switch (align) {
84
+ case 'bottom-left': {
85
+ style.left = boundsOffsetLeft;
86
+ style.top = boundsHeight - ((node === null || node === void 0 ? void 0 : node.clientHeight) || 0) + (titleHeight || 0);
87
+ break;
88
+ }
89
+ case 'bottom-right': {
90
+ style.left = boundsWidth + boundsOffsetLeft - ((node === null || node === void 0 ? void 0 : node.clientWidth) || 0);
91
+ style.top = boundsHeight - ((node === null || node === void 0 ? void 0 : node.clientHeight) || 0) + (titleHeight || 0);
92
+ break;
93
+ }
94
+ case 'top-left': {
95
+ style.left = boundsOffsetLeft;
96
+ style.top = boundsOffsetTop;
97
+ break;
98
+ }
99
+ case 'top-right': {
100
+ style.left = boundsWidth + boundsOffsetLeft - ((node === null || node === void 0 ? void 0 : node.clientWidth) || 0);
101
+ style.top = boundsOffsetTop;
102
+ break;
103
+ }
104
+ }
105
+ break;
106
+ }
107
+ }
108
+ return style;
109
+ }
@@ -72,7 +72,7 @@ function getZoomType(args) {
72
72
  return undefined;
73
73
  }
74
74
  function getPreparedZoom(args) {
75
- var _a;
75
+ var _a, _b, _c, _d;
76
76
  const { zoom, seriesData } = args;
77
77
  if (!(zoom === null || zoom === void 0 ? void 0 : zoom.enabled)) {
78
78
  return null;
@@ -86,6 +86,11 @@ function getPreparedZoom(args) {
86
86
  brush: {
87
87
  style: Object.assign({ fillOpacity: 1 }, (_a = zoom === null || zoom === void 0 ? void 0 : zoom.brush) === null || _a === void 0 ? void 0 : _a.style),
88
88
  },
89
+ resetButton: {
90
+ align: ((_b = zoom === null || zoom === void 0 ? void 0 : zoom.resetButton) === null || _b === void 0 ? void 0 : _b.align) || 'top-right',
91
+ offset: Object.assign({ x: 0, y: 0 }, (_c = zoom === null || zoom === void 0 ? void 0 : zoom.resetButton) === null || _c === void 0 ? void 0 : _c.offset),
92
+ relativeTo: ((_d = zoom === null || zoom === void 0 ? void 0 : zoom.resetButton) === null || _d === void 0 ? void 0 : _d.relativeTo) || 'chart-box',
93
+ },
89
94
  };
90
95
  }
91
96
  export const getPreparedChart = (args) => {
@@ -33,4 +33,33 @@ export interface ChartZoom {
33
33
  fillOpacity?: number;
34
34
  };
35
35
  };
36
+ /**
37
+ * Reset zoom button configuration.
38
+ * The button appears only after the zoom has been applied.
39
+ */
40
+ resetButton?: {
41
+ /**
42
+ * The alignment of the button.
43
+ *
44
+ * @default 'top-right'
45
+ */
46
+ align?: 'bottom-left' | 'bottom-right' | 'top-left' | 'top-right';
47
+ /**
48
+ * The offset of the button.
49
+ *
50
+ * @default {x: 0, y: 0}
51
+ */
52
+ offset?: {
53
+ x?: number;
54
+ y?: number;
55
+ };
56
+ /**
57
+ * The box to which the button is positioned relative to.
58
+ * - `chart-box` refers to the entire chart area, including titles and legends.
59
+ * - `plot-box` refers to the area where the series are drawn.
60
+ *
61
+ * @default 'chart-box'
62
+ */
63
+ relativeTo?: 'chart-box' | 'plot-box';
64
+ };
36
65
  }
@@ -1,4 +1,6 @@
1
1
  import { select } from 'd3-selection';
2
+ import { block } from '../cn';
3
+ const b = block('chart');
2
4
  export function handleOverflowingText(tSpan, maxWidth, textWidth) {
3
5
  var _a, _b, _c;
4
6
  if (!tSpan) {
@@ -186,29 +188,30 @@ function unescapeHtml(str) {
186
188
  return result.replace(value, key);
187
189
  }, str);
188
190
  }
191
+ function getCssStyle(prop, el = document.body) {
192
+ return window.getComputedStyle(el, null).getPropertyValue(prop);
193
+ }
194
+ let measureCanvas = null;
189
195
  export function getTextSizeFn({ style }) {
190
- const map = {};
191
- const setSymbolSize = async (s) => {
192
- const labels = [s === ' ' ? '&nbsp;' : s];
193
- const size = await getLabelsSize({
194
- labels,
195
- style,
196
- });
197
- map[s] = { width: size.maxWidth, height: size.maxHeight };
198
- };
196
+ var _a;
197
+ const canvas = measureCanvas || (measureCanvas = document.createElement('canvas'));
198
+ const context = canvas.getContext('2d');
199
+ if (!context) {
200
+ throw new Error("Couldn't get canvas context");
201
+ }
202
+ const element = (_a = document.getElementsByClassName(b())[0]) !== null && _a !== void 0 ? _a : document.body;
203
+ const defaultFontFamily = getCssStyle('font-family', element);
204
+ const defaultFontSize = getCssStyle('font-size', element);
205
+ const defaultFontWeight = getCssStyle('font-weight', element);
199
206
  return async (str) => {
200
- let width = 0;
201
- let height = 0;
202
- const symbols = unescapeHtml(str);
203
- for (let i = 0; i < symbols.length; i++) {
204
- const s = symbols[i];
205
- if (!map[s]) {
206
- await setSymbolSize(s);
207
- }
208
- width += map[s].width;
209
- height = Math.max(height, map[s].height);
210
- }
211
- return { width, height };
207
+ var _a, _b;
208
+ await document.fonts.ready;
209
+ context.font = `${(_a = style === null || style === void 0 ? void 0 : style.fontWeight) !== null && _a !== void 0 ? _a : defaultFontWeight} ${(_b = style === null || style === void 0 ? void 0 : style.fontSize) !== null && _b !== void 0 ? _b : defaultFontSize} ${defaultFontFamily}`;
210
+ const textMetric = context.measureText(unescapeHtml(str));
211
+ return {
212
+ width: textMetric.width,
213
+ height: textMetric.fontBoundingBoxDescent + textMetric.fontBoundingBoxAscent,
214
+ };
212
215
  };
213
216
  }
214
217
  // We ignore an inaccuracy of less than a pixel.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/charts",
3
- "version": "1.21.0",
3
+ "version": "1.22.0",
4
4
  "description": "React component used to render charts",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",