@gravity-ui/charts 1.36.0 → 1.37.2

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 (39) hide show
  1. package/dist/cjs/components/ChartInner/index.js +1 -1
  2. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +24 -24
  3. package/dist/cjs/components/ChartInner/useChartInnerProps.js +57 -17
  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/components/Tooltip/ChartTooltipContent.js +8 -1
  7. package/dist/cjs/components/Tooltip/styles.css +1 -1
  8. package/dist/cjs/hooks/useAxis/index.d.ts +2 -1
  9. package/dist/cjs/hooks/useAxis/index.js +9 -2
  10. package/dist/cjs/hooks/useChartDimensions/index.d.ts +2 -1
  11. package/dist/cjs/hooks/useChartDimensions/index.js +27 -13
  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/types/chart/tooltip.d.ts +2 -0
  18. package/dist/cjs/types/chart-ui.d.ts +15 -0
  19. package/dist/cjs/utils/chart/index.js +4 -2
  20. package/dist/esm/components/ChartInner/index.js +1 -1
  21. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +17 -17
  22. package/dist/esm/components/ChartInner/useChartInnerProps.js +57 -17
  23. package/dist/esm/components/Legend/index.d.ts +2 -1
  24. package/dist/esm/components/Legend/index.js +17 -16
  25. package/dist/esm/components/Tooltip/ChartTooltipContent.js +8 -1
  26. package/dist/esm/components/Tooltip/styles.css +1 -1
  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/useRangeSlider/index.js +2 -1
  32. package/dist/esm/hooks/useRangeSlider/types.d.ts +2 -1
  33. package/dist/esm/hooks/useSeries/prepare-legend.d.ts +4 -2
  34. package/dist/esm/hooks/useSeries/prepare-legend.js +76 -55
  35. package/dist/esm/hooks/useSeries/types.d.ts +1 -13
  36. package/dist/esm/types/chart/tooltip.d.ts +2 -0
  37. package/dist/esm/types/chart-ui.d.ts +15 -0
  38. package/dist/esm/utils/chart/index.js +4 -2
  39. package/package.json +1 -1
@@ -3,10 +3,11 @@ import { isAxisRelatedSeries } from '../../utils';
3
3
  import { getBoundsWidth } from './utils';
4
4
  export { getBoundsWidth } from './utils';
5
5
  const getBottomOffset = (args) => {
6
- const { hasAxisRelatedSeries, preparedLegend, preparedXAxis } = args;
6
+ var _a;
7
+ const { hasAxisRelatedSeries, preparedLegend, legendConfig, preparedXAxis } = args;
7
8
  let result = 0;
8
9
  if ((preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && preparedLegend.position === 'bottom') {
9
- result += preparedLegend.height + preparedLegend.margin;
10
+ result += ((_a = legendConfig === null || legendConfig === void 0 ? void 0 : legendConfig.height) !== null && _a !== void 0 ? _a : 0) + preparedLegend.margin;
10
11
  }
11
12
  if (!(preparedXAxis === null || preparedXAxis === void 0 ? void 0 : preparedXAxis.visible)) {
12
13
  return result;
@@ -24,26 +25,29 @@ const getBottomOffset = (args) => {
24
25
  }
25
26
  return result;
26
27
  };
27
- const getTopOffset = ({ preparedLegend }) => {
28
+ const getTopOffset = ({ preparedLegend, legendConfig, }) => {
29
+ var _a;
28
30
  if ((preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && preparedLegend.position === 'top') {
29
- return preparedLegend.height + preparedLegend.margin;
31
+ return ((_a = legendConfig === null || legendConfig === void 0 ? void 0 : legendConfig.height) !== null && _a !== void 0 ? _a : 0) + preparedLegend.margin;
30
32
  }
31
33
  return 0;
32
34
  };
33
- const getRightOffset = ({ preparedLegend }) => {
35
+ const getRightOffset = ({ preparedLegend, legendConfig, }) => {
36
+ var _a;
34
37
  if ((preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && preparedLegend.position === 'right') {
35
- return preparedLegend.width + preparedLegend.margin;
38
+ return ((_a = legendConfig === null || legendConfig === void 0 ? void 0 : legendConfig.width) !== null && _a !== void 0 ? _a : 0) + preparedLegend.margin;
36
39
  }
37
40
  return 0;
38
41
  };
39
- const getLeftOffset = ({ preparedLegend }) => {
42
+ const getLeftOffset = ({ preparedLegend, legendConfig, }) => {
43
+ var _a;
40
44
  if ((preparedLegend === null || preparedLegend === void 0 ? void 0 : preparedLegend.enabled) && preparedLegend.position === 'left') {
41
- return preparedLegend.width + preparedLegend.margin;
45
+ return ((_a = legendConfig === null || legendConfig === void 0 ? void 0 : legendConfig.width) !== null && _a !== void 0 ? _a : 0) + preparedLegend.margin;
42
46
  }
43
47
  return 0;
44
48
  };
45
49
  export const useChartDimensions = (args) => {
46
- const { height, margin, preparedLegend, preparedSeries, preparedXAxis, preparedYAxis, width } = args;
50
+ const { height, margin, preparedLegend, preparedSeries, preparedXAxis, preparedYAxis, width, legendConfig, } = args;
47
51
  return React.useMemo(() => {
48
52
  const hasAxisRelatedSeries = preparedSeries.some(isAxisRelatedSeries);
49
53
  const boundsWidth = getBoundsWidth({ chartWidth: width, chartMargin: margin, preparedYAxis });
@@ -51,12 +55,22 @@ export const useChartDimensions = (args) => {
51
55
  hasAxisRelatedSeries,
52
56
  preparedLegend,
53
57
  preparedXAxis,
58
+ legendConfig,
54
59
  });
55
- const topOffset = getTopOffset({ preparedLegend });
56
- const rightOffset = getRightOffset({ preparedLegend });
57
- const leftOffset = getLeftOffset({ preparedLegend });
60
+ const topOffset = getTopOffset({ preparedLegend, legendConfig });
61
+ const rightOffset = getRightOffset({ preparedLegend, legendConfig });
62
+ const leftOffset = getLeftOffset({ preparedLegend, legendConfig });
58
63
  const boundsHeight = height - margin.top - margin.bottom - bottomOffset - topOffset;
59
64
  const adjustedBoundsWidth = boundsWidth - rightOffset - leftOffset;
60
65
  return { boundsWidth: adjustedBoundsWidth, boundsHeight };
61
- }, [height, margin, preparedLegend, preparedSeries, preparedXAxis, preparedYAxis, width]);
66
+ }, [
67
+ height,
68
+ margin,
69
+ preparedLegend,
70
+ legendConfig,
71
+ preparedSeries,
72
+ preparedXAxis,
73
+ preparedYAxis,
74
+ width,
75
+ ]);
62
76
  };
@@ -17,7 +17,7 @@ const CLIP_PATH_BY_SERIES_TYPE = {
17
17
  [SERIES_TYPE.Scatter]: true,
18
18
  };
19
19
  export function useRangeSlider(props) {
20
- const { boundsWidth, boundsOffsetLeft, clipPathId, height, htmlLayout, onUpdate, preparedChart, preparedLegend, preparedSeries, preparedSeriesOptions, preparedRangeSlider, rangeSliderState, width, xAxis, yAxis, } = props;
20
+ const { boundsWidth, boundsOffsetLeft, clipPathId, height, htmlLayout, onUpdate, preparedChart, preparedLegend, legendConfig, preparedSeries, preparedSeriesOptions, preparedRangeSlider, rangeSliderState, width, xAxis, yAxis, } = props;
21
21
  const filteredPreparedSeries = React.useMemo(() => {
22
22
  return preparedSeries.filter((s) => {
23
23
  if ('rangeSlider' in s && !s.rangeSlider.visible) {
@@ -31,6 +31,7 @@ export function useRangeSlider(props) {
31
31
  height,
32
32
  preparedChart,
33
33
  preparedLegend,
34
+ legendConfig,
34
35
  preparedSeries,
35
36
  preparedSeriesOptions,
36
37
  width,
@@ -1,4 +1,4 @@
1
- import type { ChartXAxis, ChartYAxis } from '../../types';
1
+ import type { ChartXAxis, ChartYAxis, LegendConfig } from '../../types';
2
2
  import type { PreparedRangeSlider, PreparedXAxis, PreparedYAxis } from '../useAxis/types';
3
3
  import type { ChartScale } from '../useAxisScales/types';
4
4
  import type { BrushSelection, UseBrushProps } from '../useBrush/types';
@@ -16,6 +16,7 @@ export interface RangeSliderProps {
16
16
  onUpdate: (nextRangeSliderState?: RangeSliderState) => void;
17
17
  preparedChart: PreparedChart;
18
18
  preparedLegend: PreparedLegend | null;
19
+ legendConfig: LegendConfig | undefined;
19
20
  preparedRangeSlider: PreparedRangeSlider;
20
21
  preparedSeries: PreparedSeries[];
21
22
  preparedSeriesOptions: PreparedSeriesOptions;
@@ -11,7 +11,7 @@ export declare function getLegendComponents(args: {
11
11
  chartMargin: PreparedChart['margin'];
12
12
  series: PreparedSeries[];
13
13
  preparedLegend: PreparedLegend;
14
- }): {
14
+ }): Promise<{
15
15
  legendConfig: {
16
16
  offset: {
17
17
  left: number;
@@ -24,6 +24,8 @@ export declare function getLegendComponents(args: {
24
24
  }[];
25
25
  } | undefined;
26
26
  maxWidth: number;
27
+ height: number;
28
+ width: number;
27
29
  };
28
30
  legendItems: LegendItem[][];
29
- };
31
+ }>;
@@ -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;
@@ -97,6 +97,8 @@ export interface ChartTooltipRendererArgs<T = MeaningfulAny> {
97
97
  hoveredPlotBands?: AxisPlotBand[];
98
98
  xAxis?: ChartXAxis | null;
99
99
  yAxis?: ChartYAxis;
100
+ /** Formatting settings for tooltip header row (includes computed default). */
101
+ headerFormat?: ValueFormat | CustomFormat;
100
102
  }
101
103
  export interface ChartTooltipTotalsAggregationArgs<T = MeaningfulAny> extends ChartTooltipRendererArgs<T> {
102
104
  }
@@ -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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/charts",
3
- "version": "1.36.0",
3
+ "version": "1.37.2",
4
4
  "description": "A flexible JavaScript library for data visualization and chart rendering using React",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",