@gravity-ui/charts 1.37.1 → 1.38.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 (43) hide show
  1. package/dist/cjs/components/ChartInner/index.js +3 -2
  2. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +24 -26
  3. package/dist/cjs/components/ChartInner/useChartInnerProps.js +58 -21
  4. package/dist/cjs/components/Legend/index.d.ts +2 -1
  5. package/dist/cjs/components/Legend/index.js +17 -16
  6. package/dist/cjs/hooks/useAxis/index.d.ts +2 -1
  7. package/dist/cjs/hooks/useAxis/index.js +9 -2
  8. package/dist/cjs/hooks/useChartDimensions/index.d.ts +2 -1
  9. package/dist/cjs/hooks/useChartDimensions/index.js +27 -13
  10. package/dist/cjs/hooks/useChartOptions/tooltip.d.ts +5 -0
  11. package/dist/cjs/hooks/useChartOptions/tooltip.js +2 -2
  12. package/dist/cjs/hooks/useRangeSlider/index.js +2 -1
  13. package/dist/cjs/hooks/useRangeSlider/types.d.ts +2 -1
  14. package/dist/cjs/hooks/useSeries/prepare-legend.d.ts +4 -2
  15. package/dist/cjs/hooks/useSeries/prepare-legend.js +76 -55
  16. package/dist/cjs/hooks/useSeries/types.d.ts +1 -13
  17. package/dist/cjs/index.d.ts +1 -0
  18. package/dist/cjs/index.js +1 -0
  19. package/dist/cjs/types/chart-ui.d.ts +15 -0
  20. package/dist/cjs/utils/chart/index.js +4 -2
  21. package/dist/cjs/utils/chart/text.js +4 -6
  22. package/dist/esm/components/ChartInner/index.js +3 -2
  23. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +17 -19
  24. package/dist/esm/components/ChartInner/useChartInnerProps.js +58 -21
  25. package/dist/esm/components/Legend/index.d.ts +2 -1
  26. package/dist/esm/components/Legend/index.js +17 -16
  27. package/dist/esm/hooks/useAxis/index.d.ts +2 -1
  28. package/dist/esm/hooks/useAxis/index.js +9 -2
  29. package/dist/esm/hooks/useChartDimensions/index.d.ts +2 -1
  30. package/dist/esm/hooks/useChartDimensions/index.js +27 -13
  31. package/dist/esm/hooks/useChartOptions/tooltip.d.ts +5 -0
  32. package/dist/esm/hooks/useChartOptions/tooltip.js +2 -2
  33. package/dist/esm/hooks/useRangeSlider/index.js +2 -1
  34. package/dist/esm/hooks/useRangeSlider/types.d.ts +2 -1
  35. package/dist/esm/hooks/useSeries/prepare-legend.d.ts +4 -2
  36. package/dist/esm/hooks/useSeries/prepare-legend.js +76 -55
  37. package/dist/esm/hooks/useSeries/types.d.ts +1 -13
  38. package/dist/esm/index.d.ts +1 -0
  39. package/dist/esm/index.js +1 -0
  40. package/dist/esm/types/chart-ui.d.ts +15 -0
  41. package/dist/esm/utils/chart/index.js +4 -2
  42. package/dist/esm/utils/chart/text.js +4 -6
  43. package/package.json +1 -1
@@ -1,10 +1,9 @@
1
- import { select } from 'd3';
2
1
  import { groupBy } from 'lodash';
3
2
  import clone from 'lodash/clone';
4
3
  import get from 'lodash/get';
5
4
  import merge from 'lodash/merge';
6
5
  import { CONTINUOUS_LEGEND_SIZE, legendDefaults } from '../../constants';
7
- import { getDefaultColorStops, getDomainForContinuousColorScale, getLabelsSize } from '../../utils';
6
+ import { getDefaultColorStops, getDomainForContinuousColorScale, getLabelsSize, getTextSizeFn, getTextWithElipsis, } from '../../utils';
8
7
  export async function getPreparedLegend(args) {
9
8
  var _a, _b, _c, _d, _e, _f, _g;
10
9
  const { legend, series } = args;
@@ -87,68 +86,81 @@ function getFlattenLegendItems(series, preparedLegend) {
87
86
  return Object.values(grouped).reduce((acc, items) => {
88
87
  const s = items.find((item) => item.legend.enabled);
89
88
  if (s) {
90
- acc.push(Object.assign(Object.assign({}, s), { id: s.legend.groupId, name: s.legend.itemText, height: preparedLegend.lineHeight, symbol: s.legend.symbol }));
89
+ acc.push(Object.assign(Object.assign({}, s), { id: s.legend.groupId, name: s.legend.itemText, text: s.legend.itemText, height: preparedLegend.lineHeight, symbol: s.legend.symbol }));
91
90
  }
92
91
  return acc;
93
92
  }, []);
94
93
  }
95
- function getGroupedLegendItems(args) {
94
+ async function getGroupedLegendItems(args) {
96
95
  const { maxLegendWidth, items, preparedLegend } = args;
97
96
  const result = [[]];
98
- const bodySelection = select(document.body);
99
97
  let textWidthsInLine = [0];
100
98
  let lineIndex = 0;
101
- items.forEach((item) => {
102
- const itemSelection = preparedLegend.html
103
- ? bodySelection
104
- .append('div')
105
- .html(item.name)
106
- .style('position', 'absolute')
107
- .style('display', 'inline-block')
108
- .style('white-space', 'nowrap')
109
- : bodySelection.append('text').text(item.name).style('white-space', 'nowrap');
110
- itemSelection
111
- .style('font-size', preparedLegend.itemStyle.fontSize)
112
- .each(function () {
113
- const resultItem = clone(item);
114
- const { height, width: textWidth } = this.getBoundingClientRect();
115
- resultItem.height = height;
116
- if (textWidth >
117
- maxLegendWidth - resultItem.symbol.width - resultItem.symbol.padding) {
99
+ const getLegendItemTextSize = getTextSizeFn({ style: preparedLegend.itemStyle });
100
+ for (let i = 0; i < items.length; i++) {
101
+ const item = items[i];
102
+ const resultItem = clone(item);
103
+ resultItem.text = item.name;
104
+ const maxTextWidth = maxLegendWidth - resultItem.symbol.width - resultItem.symbol.padding;
105
+ let textHeight = 0;
106
+ let textWidth = 0;
107
+ if (preparedLegend.html) {
108
+ const textSize = await getLabelsSize({
109
+ labels: [resultItem.text],
110
+ html: true,
111
+ style: preparedLegend.itemStyle,
112
+ });
113
+ textHeight = textSize.maxHeight;
114
+ textWidth = textSize.maxWidth;
115
+ }
116
+ else {
117
+ const textSize = await getLegendItemTextSize(resultItem.text);
118
+ textHeight = textSize.height;
119
+ textWidth = textSize.width;
120
+ }
121
+ resultItem.height = textHeight;
122
+ if (textWidth > maxTextWidth) {
123
+ if (preparedLegend.html) {
118
124
  resultItem.overflowed = true;
119
- resultItem.textWidth =
120
- maxLegendWidth - resultItem.symbol.width - resultItem.symbol.padding;
125
+ resultItem.textWidth = maxTextWidth;
121
126
  }
122
127
  else {
123
- resultItem.textWidth = textWidth;
124
- }
125
- textWidthsInLine.push(textWidth);
126
- const textsWidth = textWidthsInLine.reduce((acc, width) => acc + width, 0);
127
- if (!result[lineIndex]) {
128
- result[lineIndex] = [];
128
+ resultItem.text = await getTextWithElipsis({
129
+ text: resultItem.text,
130
+ getTextWidth: async (s) => (await getLegendItemTextSize(s)).width,
131
+ maxWidth: maxTextWidth,
132
+ });
133
+ resultItem.textWidth = (await getLegendItemTextSize(resultItem.text)).width;
129
134
  }
130
- result[lineIndex].push(resultItem);
131
- const symbolsWidth = result[lineIndex].reduce((acc, { symbol }) => {
132
- return acc + symbol.width + symbol.padding;
133
- }, 0);
134
- const distancesWidth = (result[lineIndex].length - 1) * preparedLegend.itemDistance;
135
- const isOverflowedAsOnlyItemInLine = resultItem.overflowed && result[lineIndex].length === 1;
136
- const isCurrentLineOverMaxWidth = maxLegendWidth < textsWidth + symbolsWidth + distancesWidth;
137
- if (isOverflowedAsOnlyItemInLine) {
138
- lineIndex += 1;
139
- textWidthsInLine = [];
140
- }
141
- else if (isCurrentLineOverMaxWidth) {
142
- result[lineIndex].pop();
143
- lineIndex += 1;
144
- textWidthsInLine = [textWidth];
145
- const nextLineIndex = lineIndex;
146
- result[nextLineIndex] = [];
147
- result[nextLineIndex].push(resultItem);
148
- }
149
- })
150
- .remove();
151
- });
135
+ }
136
+ else {
137
+ resultItem.textWidth = textWidth;
138
+ }
139
+ textWidthsInLine.push(textWidth);
140
+ const textsWidth = textWidthsInLine.reduce((acc, width) => acc + width, 0);
141
+ if (!result[lineIndex]) {
142
+ result[lineIndex] = [];
143
+ }
144
+ result[lineIndex].push(resultItem);
145
+ const symbolsWidth = result[lineIndex].reduce((acc, { symbol }) => {
146
+ return acc + symbol.width + symbol.padding;
147
+ }, 0);
148
+ const distancesWidth = (result[lineIndex].length - 1) * preparedLegend.itemDistance;
149
+ const isOverflowedAsOnlyItemInLine = resultItem.overflowed && result[lineIndex].length === 1;
150
+ const isCurrentLineOverMaxWidth = maxLegendWidth < textsWidth + symbolsWidth + distancesWidth;
151
+ if (isOverflowedAsOnlyItemInLine) {
152
+ lineIndex += 1;
153
+ textWidthsInLine = [];
154
+ }
155
+ else if (isCurrentLineOverMaxWidth) {
156
+ result[lineIndex].pop();
157
+ lineIndex += 1;
158
+ textWidthsInLine = [textWidth];
159
+ const nextLineIndex = lineIndex;
160
+ result[nextLineIndex] = [];
161
+ result[nextLineIndex].push(resultItem);
162
+ }
163
+ }
152
164
  return result;
153
165
  }
154
166
  function getPagination(args) {
@@ -227,7 +239,7 @@ function getMaxLegendHeight(args) {
227
239
  }
228
240
  return (chartHeight - chartMargin.top - chartMargin.bottom - preparedLegend.margin) / 2;
229
241
  }
230
- export function getLegendComponents(args) {
242
+ export async function getLegendComponents(args) {
231
243
  const { chartWidth, chartHeight, chartMargin, series, preparedLegend } = args;
232
244
  const isVerticalPosition = preparedLegend.position === 'right' || preparedLegend.position === 'left';
233
245
  const maxLegendWidth = getMaxLegendWidth({
@@ -243,7 +255,7 @@ export function getLegendComponents(args) {
243
255
  isVerticalPosition,
244
256
  });
245
257
  const flattenLegendItems = getFlattenLegendItems(series, preparedLegend);
246
- const items = getGroupedLegendItems({
258
+ const items = await getGroupedLegendItems({
247
259
  maxLegendWidth,
248
260
  items: flattenLegendItems,
249
261
  preparedLegend,
@@ -278,5 +290,14 @@ export function getLegendComponents(args) {
278
290
  legendWidth: preparedLegend.width,
279
291
  legendHeight: preparedLegend.height,
280
292
  });
281
- return { legendConfig: { offset, pagination, maxWidth: maxLegendWidth }, legendItems: items };
293
+ return {
294
+ legendConfig: {
295
+ offset,
296
+ pagination,
297
+ maxWidth: maxLegendWidth,
298
+ height: preparedLegend.height,
299
+ width: preparedLegend.width,
300
+ },
301
+ legendItems: items,
302
+ };
282
303
  }
@@ -44,25 +44,13 @@ export type LegendItem = {
44
44
  color: string;
45
45
  height: number;
46
46
  name: string;
47
+ text: string;
47
48
  symbol: PreparedLegendSymbol;
48
49
  textWidth: number;
49
50
  dashStyle?: DashStyle;
50
51
  overflowed?: boolean;
51
52
  visible?: boolean;
52
53
  };
53
- export type LegendConfig = {
54
- offset: {
55
- left: number;
56
- top: number;
57
- };
58
- maxWidth: number;
59
- pagination?: {
60
- pages: {
61
- start: number;
62
- end: number;
63
- }[];
64
- };
65
- };
66
54
  export type PreparedHaloOptions = {
67
55
  enabled: boolean;
68
56
  opacity: number;
@@ -1,3 +1,4 @@
1
1
  export { CustomShapeRenderer, getFormattedValue } from './utils';
2
+ export { getDefaultTooltipHeaderFormat } from './hooks/useChartOptions/tooltip';
2
3
  export * from './components';
3
4
  export * from './types';
package/dist/cjs/index.js CHANGED
@@ -1,3 +1,4 @@
1
1
  export { CustomShapeRenderer, getFormattedValue } from './utils';
2
+ export { getDefaultTooltipHeaderFormat } from './hooks/useChartOptions/tooltip';
2
3
  export * from './components';
3
4
  export * from './types';
@@ -27,3 +27,18 @@ export interface HtmlItem {
27
27
  export interface ShapeDataWithHtmlItems {
28
28
  htmlElements: HtmlItem[];
29
29
  }
30
+ export type LegendConfig = {
31
+ offset: {
32
+ left: number;
33
+ top: number;
34
+ };
35
+ pagination?: {
36
+ pages: {
37
+ start: number;
38
+ end: number;
39
+ }[];
40
+ };
41
+ maxWidth: number;
42
+ height: number;
43
+ width: number;
44
+ };
@@ -107,8 +107,10 @@ export const getDomainDataYBySeries = (series) => {
107
107
  let yValue = 0;
108
108
  const points = seriesList.map((s) => s.data).flat();
109
109
  sortBy(points, (p) => p.index).forEach((d) => {
110
- yValue += Number(d.y) || 0;
111
- acc.push(yValue);
110
+ if (!d.total) {
111
+ yValue += Number(d.y) || 0;
112
+ acc.push(yValue);
113
+ }
112
114
  });
113
115
  break;
114
116
  }
@@ -176,9 +176,6 @@ function unescapeHtml(str) {
176
176
  return result.replace(value, key);
177
177
  }, str);
178
178
  }
179
- function getCssStyle(prop, el = document.body) {
180
- return window.getComputedStyle(el, null).getPropertyValue(prop);
181
- }
182
179
  let measureCanvas = null;
183
180
  export function getTextSizeFn({ style }) {
184
181
  var _a;
@@ -188,9 +185,10 @@ export function getTextSizeFn({ style }) {
188
185
  throw new Error("Couldn't get canvas context");
189
186
  }
190
187
  const element = (_a = document.getElementsByClassName(b())[0]) !== null && _a !== void 0 ? _a : document.body;
191
- const defaultFontFamily = getCssStyle('font-family', element);
192
- const defaultFontSize = getCssStyle('font-size', element);
193
- const defaultFontWeight = getCssStyle('font-weight', element);
188
+ const computedStyle = window.getComputedStyle(element, null);
189
+ const defaultFontFamily = computedStyle.getPropertyValue('font-family');
190
+ const defaultFontSize = computedStyle.getPropertyValue('font-size');
191
+ const defaultFontWeight = computedStyle.getPropertyValue('font-weight');
194
192
  return async (str) => {
195
193
  var _a, _b;
196
194
  await document.fonts.ready;
@@ -66,7 +66,8 @@ export const ChartInner = (props) => {
66
66
  const { allPreparedSeries, boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedLegend, preparedSeries, preparedSeriesOptions, preparedSplit, prevHeight, prevWidth, shapes, shapesData, shapesReady, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
67
67
  dispatcher,
68
68
  htmlLayout, plotNode: plotRef.current, preparedChart,
69
- rangeSliderState, svgContainer: svgRef.current, updateZoomState,
69
+ rangeSliderState,
70
+ updateZoomState,
70
71
  zoomState }));
71
72
  const debouncedBoundsWidth = useDebouncedValue({
72
73
  value: boundsWidth,
@@ -248,7 +249,7 @@ export const ChartInner = (props) => {
248
249
  React.createElement("g", { ref: plotBeforeRef }),
249
250
  shapes,
250
251
  React.createElement("g", { ref: plotAfterRef })),
251
- ((_e = xAxis === null || xAxis === void 0 ? void 0 : xAxis.rangeSlider) === null || _e === void 0 ? void 0 : _e.enabled) && (React.createElement(RangeSlider, { boundsOffsetLeft: debouncedOffsetLeft, boundsWidth: debouncedBoundsWidth, height: height, htmlLayout: htmlLayout, onUpdate: updateRangeSliderState, preparedChart: preparedChart, preparedLegend: preparedLegend, preparedSeries: debouncedAllPreparedSeries, preparedSeriesOptions: preparedSeriesOptions, preparedRangeSlider: xAxis.rangeSlider, rangeSliderState: rangeSliderState, ref: rangeSliderRef, width: width, xAxis: data.xAxis, yAxis: data.yAxis })),
252
+ ((_e = xAxis === null || xAxis === void 0 ? void 0 : xAxis.rangeSlider) === null || _e === void 0 ? void 0 : _e.enabled) && (React.createElement(RangeSlider, { boundsOffsetLeft: debouncedOffsetLeft, boundsWidth: debouncedBoundsWidth, height: height, htmlLayout: htmlLayout, onUpdate: updateRangeSliderState, preparedChart: preparedChart, preparedLegend: preparedLegend, preparedSeries: debouncedAllPreparedSeries, preparedSeriesOptions: preparedSeriesOptions, preparedRangeSlider: xAxis.rangeSlider, rangeSliderState: rangeSliderState, ref: rangeSliderRef, width: width, xAxis: data.xAxis, yAxis: data.yAxis, legendConfig: legendConfig })),
252
253
  (preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && legendConfig && (React.createElement(Legend, { chartSeries: preparedSeries, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick, onUpdate: unpinTooltip, htmlLayout: htmlLayout }))));
253
254
  return (React.createElement("div", { className: b() },
254
255
  React.createElement("svg", { ref: svgRef, width: width, height: height,
@@ -1,6 +1,7 @@
1
1
  import React from 'react';
2
2
  import type { Dispatch } from 'd3';
3
- import type { PreparedChart, PreparedLegend, RangeSliderState, ZoomState } from '../../hooks';
3
+ import type { LegendItem, PreparedChart, PreparedLegend, PreparedSeries, RangeSliderState, ZoomState } from '../../hooks';
4
+ import type { LegendConfig } from '../../types';
4
5
  import type { ChartInnerProps } from './types';
5
6
  type Props = ChartInnerProps & {
6
7
  clipPathId: string;
@@ -8,35 +9,33 @@ type Props = ChartInnerProps & {
8
9
  htmlLayout: HTMLElement | null;
9
10
  plotNode: SVGGElement | null;
10
11
  preparedChart: PreparedChart;
11
- svgContainer: SVGGElement | null;
12
12
  updateZoomState: (nextZoomState: Partial<ZoomState>) => void;
13
13
  zoomState: Partial<ZoomState>;
14
14
  rangeSliderState?: RangeSliderState;
15
15
  };
16
+ type LegendState = {
17
+ legendConfig?: LegendConfig;
18
+ legendItems: LegendItem[][];
19
+ };
20
+ export declare function useLegend({ preparedLegend, preparedChart, preparedSeries, width, height, }: {
21
+ preparedLegend: PreparedLegend | null;
22
+ preparedChart: PreparedChart;
23
+ preparedSeries: PreparedSeries[];
24
+ width: number;
25
+ height: number;
26
+ }): LegendState;
16
27
  export declare function useChartInnerProps(props: Props): {
17
- allPreparedSeries: import("../../hooks").PreparedSeries[];
28
+ allPreparedSeries: PreparedSeries[];
18
29
  boundsHeight: number;
19
30
  boundsOffsetLeft: number;
20
31
  boundsOffsetTop: number;
21
32
  boundsWidth: number;
22
33
  handleLegendItemClick: import("../../hooks").OnLegendItemClick;
23
34
  isOutsideBounds: (x: number, y: number) => boolean;
24
- legendConfig: {
25
- offset: {
26
- left: number;
27
- top: number;
28
- };
29
- pagination: {
30
- pages: {
31
- start: number;
32
- end: number;
33
- }[];
34
- } | undefined;
35
- maxWidth: number;
36
- } | undefined;
37
- legendItems: never[] | import("../../hooks").LegendItem[][];
35
+ legendConfig: LegendConfig | undefined;
36
+ legendItems: LegendItem[][];
38
37
  preparedLegend: PreparedLegend | null;
39
- preparedSeries: import("../../hooks").PreparedSeries[];
38
+ preparedSeries: PreparedSeries[];
40
39
  preparedSeriesOptions: import("../../constants").SeriesOptionsDefaults;
41
40
  preparedSplit: import("../../hooks").PreparedSplit;
42
41
  prevHeight: number | undefined;
@@ -44,7 +43,6 @@ export declare function useChartInnerProps(props: Props): {
44
43
  shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
45
44
  shapesData: import("../../hooks").ShapeData[];
46
45
  shapesReady: boolean;
47
- svgXPos: number | undefined;
48
46
  xAxis: import("../../hooks").PreparedXAxis | null;
49
47
  xScale: import("../../hooks").ChartScale | undefined;
50
48
  yAxis: (Omit<import("../..").ChartAxis, "type" | "labels" | "plotLines" | "plotBands"> & {
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import isEqual from 'lodash/isEqual';
2
3
  import { DEFAULT_PALETTE, SERIES_TYPE } from '../../constants';
3
4
  import { useAxis, useAxisScales, useChartDimensions, useNormalizedOriginalData, usePrevious, useSeries, useShapes, useSplit, useYAxisLabelWidth, useZoom, } from '../../hooks';
4
5
  import { getYAxisWidth } from '../../hooks/useChartDimensions/utils';
@@ -9,17 +10,18 @@ import { hasAtLeastOneSeriesDataPerPlot } from './utils';
9
10
  const CLIP_PATH_BY_SERIES_TYPE = {
10
11
  [SERIES_TYPE.Scatter]: false,
11
12
  };
12
- function getBoundsOffsetTop(args) {
13
- const { chartMarginTop, preparedLegend } = args;
13
+ function getBoundsOffsetTop({ chartMarginTop, preparedLegend, legendConfig, }) {
14
+ var _a;
14
15
  return (chartMarginTop +
15
16
  ((preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && preparedLegend.position === 'top'
16
- ? preparedLegend.height + preparedLegend.margin
17
+ ? ((_a = legendConfig === null || legendConfig === void 0 ? void 0 : legendConfig.height) !== null && _a !== void 0 ? _a : 0) + preparedLegend.margin
17
18
  : 0));
18
19
  }
19
20
  function getBoundsOffsetLeft(args) {
20
- const { chartMarginLeft, preparedLegend, yAxis, getYAxisWidth: getAxisWidth } = args;
21
+ var _a;
22
+ const { chartMarginLeft, preparedLegend, yAxis, getYAxisWidth: getAxisWidth, legendConfig, } = args;
21
23
  const legendOffset = (preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && preparedLegend.position === 'left'
22
- ? preparedLegend.width + preparedLegend.margin
24
+ ? ((_a = legendConfig === null || legendConfig === void 0 ? void 0 : legendConfig.width) !== null && _a !== void 0 ? _a : 0) + preparedLegend.margin
23
25
  : 0;
24
26
  const leftAxisWidth = yAxis.reduce((acc, axis) => {
25
27
  if (axis.position !== 'left') {
@@ -33,9 +35,47 @@ function getBoundsOffsetLeft(args) {
33
35
  }, 0);
34
36
  return chartMarginLeft + legendOffset + leftAxisWidth;
35
37
  }
38
+ export function useLegend({ preparedLegend, preparedChart, preparedSeries, width, height, }) {
39
+ const [legendState, setLegend] = React.useState({
40
+ legendConfig: undefined,
41
+ legendItems: [],
42
+ });
43
+ const legendStateRunRef = React.useRef(0);
44
+ const prevLegendStateValue = React.useRef(legendState);
45
+ const legendStateReady = React.useRef(false);
46
+ React.useEffect(() => {
47
+ legendStateRunRef.current++;
48
+ legendStateReady.current = false;
49
+ (async function () {
50
+ const currentRun = legendStateRunRef.current;
51
+ if (!preparedLegend) {
52
+ return;
53
+ }
54
+ const newStateValue = await getLegendComponents({
55
+ chartWidth: width,
56
+ chartHeight: height,
57
+ chartMargin: preparedChart.margin,
58
+ series: preparedSeries,
59
+ preparedLegend,
60
+ });
61
+ if (legendStateRunRef.current === currentRun) {
62
+ if (!isEqual(prevLegendStateValue.current, newStateValue)) {
63
+ setLegend(newStateValue);
64
+ prevLegendStateValue.current = newStateValue;
65
+ }
66
+ legendStateReady.current = true;
67
+ }
68
+ })();
69
+ }, [height, preparedChart.margin, preparedLegend, preparedSeries, width]);
70
+ return legendStateReady.current
71
+ ? legendState
72
+ : {
73
+ legendConfig: undefined,
74
+ legendItems: [],
75
+ };
76
+ }
36
77
  export function useChartInnerProps(props) {
37
- var _a;
38
- const { clipPathId, data, dispatcher, height, htmlLayout, plotNode, preparedChart, rangeSliderState, svgContainer, width, updateZoomState, zoomState, } = props;
78
+ const { clipPathId, data, dispatcher, height, htmlLayout, plotNode, preparedChart, rangeSliderState, width, updateZoomState, zoomState, } = props;
39
79
  const prevWidth = usePrevious(width);
40
80
  const prevHeight = usePrevious(height);
41
81
  const colors = React.useMemo(() => {
@@ -76,21 +116,17 @@ export function useChartInnerProps(props) {
76
116
  zoomState: effectiveZoomState,
77
117
  });
78
118
  }, [allPreparedSeries, normalizedXAxis, normalizedYAxis, effectiveZoomState]);
79
- const { legendConfig, legendItems } = React.useMemo(() => {
80
- if (!preparedLegend) {
81
- return { legendConfig: undefined, legendItems: [] };
82
- }
83
- return getLegendComponents({
84
- chartWidth: width,
85
- chartHeight: height,
86
- chartMargin: preparedChart.margin,
87
- series: preparedSeries,
88
- preparedLegend,
89
- });
90
- }, [width, height, preparedChart.margin, preparedSeries, preparedLegend]);
119
+ const { legendConfig, legendItems } = useLegend({
120
+ width,
121
+ height,
122
+ preparedChart,
123
+ preparedSeries,
124
+ preparedLegend,
125
+ });
91
126
  const { xAxis, yAxis, setAxes } = useAxis({
92
127
  height,
93
128
  preparedChart,
129
+ legendConfig,
94
130
  preparedLegend,
95
131
  preparedSeries,
96
132
  preparedSeriesOptions,
@@ -106,6 +142,7 @@ export function useChartInnerProps(props) {
106
142
  preparedYAxis: yAxis,
107
143
  preparedXAxis: xAxis,
108
144
  width,
145
+ legendConfig,
109
146
  });
110
147
  const preparedSplit = useSplit({ split: data.split, boundsHeight, chartWidth: width });
111
148
  const { xScale, yScale } = useAxisScales({
@@ -166,6 +203,7 @@ export function useChartInnerProps(props) {
166
203
  const boundsOffsetTop = getBoundsOffsetTop({
167
204
  chartMarginTop: preparedChart.margin.top,
168
205
  preparedLegend,
206
+ legendConfig,
169
207
  });
170
208
  // We need to calculate the width of each left axis because the first axis can be hidden
171
209
  const boundsOffsetLeft = getBoundsOffsetLeft({
@@ -173,8 +211,8 @@ export function useChartInnerProps(props) {
173
211
  preparedLegend,
174
212
  yAxis,
175
213
  getYAxisWidth,
214
+ legendConfig,
176
215
  });
177
- const { x } = (_a = svgContainer === null || svgContainer === void 0 ? void 0 : svgContainer.getBoundingClientRect()) !== null && _a !== void 0 ? _a : {};
178
216
  return {
179
217
  allPreparedSeries,
180
218
  boundsHeight,
@@ -194,7 +232,6 @@ export function useChartInnerProps(props) {
194
232
  shapes,
195
233
  shapesData,
196
234
  shapesReady,
197
- svgXPos: x,
198
235
  xAxis,
199
236
  xScale,
200
237
  yAxis,
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
- import type { LegendConfig, LegendItem, OnLegendItemClick, PreparedLegend, PreparedSeries } from '../../hooks';
2
+ import type { LegendItem, OnLegendItemClick, PreparedLegend, PreparedSeries } from '../../hooks';
3
+ import type { LegendConfig } from '../../types';
3
4
  import './styles.css';
4
5
  type Props = {
5
6
  chartSeries: PreparedSeries[];
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { scaleLinear, select, symbol } from 'd3';
3
3
  import { CONTINUOUS_LEGEND_SIZE } from '../../constants';
4
4
  import { formatNumber } from '../../libs';
5
- import { block, createGradientRect, getContinuesColorFn, getLabelsSize, getSymbol, getUniqId, handleOverflowingText, } from '../../utils';
5
+ import { block, createGradientRect, getContinuesColorFn, getLabelsSize, getSymbol, getUniqId, } from '../../utils';
6
6
  import { axisBottom } from '../../utils/chart/axis-generators';
7
7
  import { appendLinePathElement } from '../utils';
8
8
  import './styles.css';
@@ -163,7 +163,6 @@ export const Legend = (props) => {
163
163
  : items;
164
164
  const legendLineHeights = [];
165
165
  pageItems.forEach((line) => {
166
- var _a;
167
166
  const legendLine = svgElement.append('g').attr('class', b('line'));
168
167
  const htmlLegendLine = htmlContainer === null || htmlContainer === void 0 ? void 0 : htmlContainer.append('div').style('position', 'absolute');
169
168
  const legendItemTemplate = legendLine
@@ -214,9 +213,8 @@ export const Legend = (props) => {
214
213
  .on('click', function (e, d) {
215
214
  onItemClick({ id: d.id, name: d.name, metaKey: e.metaKey });
216
215
  onUpdate === null || onUpdate === void 0 ? void 0 : onUpdate();
217
- })[legend.html ? 'html' : 'text'](function (d) {
218
- return d.name;
219
- });
216
+ })
217
+ .html((d) => d.text);
220
218
  }
221
219
  else {
222
220
  legendItemTemplate
@@ -231,19 +229,22 @@ export const Legend = (props) => {
231
229
  const mods = { selected: d.visible, unselected: !d.visible };
232
230
  return b('item-text', mods);
233
231
  })
234
- .html(function (d) {
235
- return ('name' in d && d.name);
236
- })
237
- .style('font-size', legend.itemStyle.fontSize)
238
- .each((d, index, nodes) => {
239
- if (d.overflowed) {
240
- handleOverflowingText(nodes[index], d.textWidth);
232
+ .html((d) => d.text)
233
+ .style('font-size', legend.itemStyle.fontSize);
234
+ }
235
+ let contentWidth = 0;
236
+ if (legend.html) {
237
+ contentWidth = getXPosition(line.length) - legend.itemDistance;
238
+ }
239
+ else {
240
+ contentWidth = line.reduce((sum, l, index) => {
241
+ sum += l.textWidth + l.symbol.width + l.symbol.padding;
242
+ if (index > 0) {
243
+ sum += legend.itemDistance;
241
244
  }
242
- });
245
+ return sum;
246
+ }, 0);
243
247
  }
244
- const contentWidth = (legend.html
245
- ? getXPosition(line.length) - legend.itemDistance
246
- : (_a = legendLine.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
247
248
  let left = 0;
248
249
  switch (legend.justifyContent) {
249
250
  case 'center': {
@@ -1,11 +1,12 @@
1
1
  import React from 'react';
2
- import type { ChartXAxis, ChartYAxis } from '../../types';
2
+ import type { ChartXAxis, ChartYAxis, LegendConfig } from '../../types';
3
3
  import type { PreparedChart } from '../useChartOptions/types';
4
4
  import type { PreparedLegend, PreparedSeries, PreparedSeriesOptions } from '../useSeries/types';
5
5
  import type { AxesState } from './types';
6
6
  interface UseAxesProps {
7
7
  height: number;
8
8
  preparedChart: PreparedChart;
9
+ legendConfig: LegendConfig | undefined;
9
10
  preparedLegend: PreparedLegend | null;
10
11
  preparedSeries: PreparedSeries[];
11
12
  preparedSeriesOptions: PreparedSeriesOptions;
@@ -4,15 +4,20 @@ import { getWidthOccupiedByYAxis } from '../useChartDimensions/utils';
4
4
  import { getPreparedXAxis } from './x-axis';
5
5
  import { getPreparedYAxis } from './y-axis';
6
6
  export function useAxis(props) {
7
- const { boundsHeight, height, preparedChart, preparedLegend, preparedSeries, preparedSeriesOptions, width, xAxis, yAxis, } = props;
7
+ const { boundsHeight, height, preparedChart, legendConfig, preparedLegend, preparedSeries, preparedSeriesOptions, width, xAxis, yAxis, } = props;
8
8
  const [axesState, setAxes] = React.useState({ xAxis: null, yAxis: [] });
9
9
  const axesStateRunRef = React.useRef(0);
10
10
  const prevAxesStateValue = React.useRef(axesState);
11
11
  const axesStateReady = React.useRef(false);
12
12
  React.useEffect(() => {
13
+ const shouldWaitForLegendReady = !preparedLegend || ((preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && !legendConfig);
14
+ if (shouldWaitForLegendReady) {
15
+ return;
16
+ }
13
17
  axesStateRunRef.current++;
14
18
  axesStateReady.current = false;
15
19
  (async function () {
20
+ var _a, _b;
16
21
  const currentRun = axesStateRunRef.current;
17
22
  const seriesData = preparedSeries.filter((s) => s.visible);
18
23
  const estimatedPreparedYAxis = await getPreparedYAxis({
@@ -41,7 +46,8 @@ export function useAxis(props) {
41
46
  (preparedXAxis.rangeSlider.enabled
42
47
  ? preparedXAxis.rangeSlider.height + preparedXAxis.rangeSlider.margin
43
48
  : 0) +
44
- (preparedLegend ? preparedLegend.height + preparedLegend.margin : 0) +
49
+ ((_a = legendConfig === null || legendConfig === void 0 ? void 0 : legendConfig.height) !== null && _a !== void 0 ? _a : 0) +
50
+ ((_b = preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.margin) !== null && _b !== void 0 ? _b : 0) +
45
51
  preparedChart.margin.top +
46
52
  preparedChart.margin.bottom);
47
53
  }
@@ -65,6 +71,7 @@ export function useAxis(props) {
65
71
  boundsHeight,
66
72
  height,
67
73
  preparedChart.margin,
74
+ legendConfig,
68
75
  preparedLegend,
69
76
  preparedSeries,
70
77
  preparedSeriesOptions,
@@ -1,5 +1,5 @@
1
1
  import type { PreparedLegend, PreparedSeries, PreparedXAxis, PreparedYAxis } from '../../hooks';
2
- import type { ChartMargin } from '../../types';
2
+ import type { ChartMargin, LegendConfig } from '../../types';
3
3
  export { getBoundsWidth } from './utils';
4
4
  type Args = {
5
5
  height: number;
@@ -9,6 +9,7 @@ type Args = {
9
9
  preparedXAxis: PreparedXAxis | null;
10
10
  preparedYAxis: PreparedYAxis[] | null;
11
11
  width: number;
12
+ legendConfig: LegendConfig | undefined;
12
13
  };
13
14
  export declare const useChartDimensions: (args: Args) => {
14
15
  boundsWidth: number;