@gravity-ui/charts 1.39.1 → 1.41.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 (44) hide show
  1. package/README.md +4 -24
  2. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +2 -38
  3. package/dist/cjs/components/ChartInner/utils/axis.d.ts +1 -37
  4. package/dist/cjs/components/Tooltip/DefaultTooltipContent/Row.js +4 -4
  5. package/dist/cjs/components/Tooltip/DefaultTooltipContent/RowWithAggregation.js +3 -2
  6. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +128 -118
  7. package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.d.ts +6 -0
  8. package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.js +41 -0
  9. package/dist/cjs/components/Tooltip/index.js +2 -0
  10. package/dist/cjs/components/Tooltip/styles.css +33 -7
  11. package/dist/cjs/components/index.d.ts +4 -1
  12. package/dist/cjs/components/index.js +8 -3
  13. package/dist/cjs/hooks/useAxis/index.d.ts +1 -37
  14. package/dist/cjs/hooks/useAxis/types.d.ts +6 -4
  15. package/dist/cjs/hooks/useAxisScales/index.d.ts +2 -2
  16. package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +3 -2
  17. package/dist/cjs/hooks/useShapes/bar-x/types.d.ts +5 -0
  18. package/dist/cjs/hooks/useTooltip/index.d.ts +4 -1
  19. package/dist/cjs/hooks/useTooltip/index.js +12 -5
  20. package/dist/cjs/types/chart/axis.d.ts +2 -1
  21. package/dist/cjs/types/chart/tooltip.d.ts +44 -1
  22. package/dist/cjs/utils/chart/zoom.js +2 -2
  23. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +2 -38
  24. package/dist/esm/components/ChartInner/utils/axis.d.ts +1 -37
  25. package/dist/esm/components/Tooltip/DefaultTooltipContent/Row.js +4 -4
  26. package/dist/esm/components/Tooltip/DefaultTooltipContent/RowWithAggregation.js +3 -2
  27. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +128 -118
  28. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.d.ts +6 -0
  29. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +41 -0
  30. package/dist/esm/components/Tooltip/index.js +2 -0
  31. package/dist/esm/components/Tooltip/styles.css +33 -7
  32. package/dist/esm/components/index.d.ts +4 -1
  33. package/dist/esm/components/index.js +8 -3
  34. package/dist/esm/hooks/useAxis/index.d.ts +1 -37
  35. package/dist/esm/hooks/useAxis/types.d.ts +6 -4
  36. package/dist/esm/hooks/useAxisScales/index.d.ts +2 -2
  37. package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +3 -2
  38. package/dist/esm/hooks/useShapes/bar-x/types.d.ts +5 -0
  39. package/dist/esm/hooks/useTooltip/index.d.ts +4 -1
  40. package/dist/esm/hooks/useTooltip/index.js +12 -5
  41. package/dist/esm/types/chart/axis.d.ts +2 -1
  42. package/dist/esm/types/chart/tooltip.d.ts +44 -1
  43. package/dist/esm/utils/chart/zoom.js +2 -2
  44. package/package.json +2 -1
@@ -1,5 +1,5 @@
1
1
  import type { DashStyle } from '../../constants';
2
- import type { AxisCrosshair, AxisPlotBand, BaseTextStyle, ChartAxis, ChartAxisLabels, ChartAxisRangeSlider, ChartAxisTitleAlignment, ChartAxisType, DeepRequired, MeaningfulAny, PlotLayerPlacement } from '../../types';
2
+ import type { AxisCrosshair, AxisPlotBand, BaseTextStyle, ChartAxis, ChartAxisLabels, ChartAxisRangeSlider, ChartAxisTitleAlignment, ChartAxisTitleRotation, ChartAxisType, DeepRequired, MeaningfulAny, PlotLayerPlacement } from '../../types';
3
3
  type PreparedAxisLabels = Omit<ChartAxisLabels, 'enabled' | 'padding' | 'style' | 'autoRotation'> & Required<Pick<ChartAxisLabels, 'enabled' | 'padding' | 'margin' | 'rotation' | 'html'>> & {
4
4
  style: BaseTextStyle;
5
5
  rotation: number;
@@ -51,7 +51,7 @@ type PreparedBaseAxis = Omit<ChartAxis, 'type' | 'labels' | 'plotLines' | 'plotB
51
51
  style: BaseTextStyle;
52
52
  align: ChartAxisTitleAlignment;
53
53
  maxRowCount: number;
54
- rotation: number;
54
+ rotation: ChartAxisTitleRotation;
55
55
  maxWidth: number;
56
56
  html: boolean;
57
57
  };
@@ -64,7 +64,6 @@ type PreparedBaseAxis = Omit<ChartAxis, 'type' | 'labels' | 'plotLines' | 'plotB
64
64
  pixelInterval?: number;
65
65
  };
66
66
  tickMarks: PreparedAxisTickMarks;
67
- position: 'left' | 'right' | 'top' | 'bottom';
68
67
  plotIndex: number;
69
68
  plotLines: PreparedAxisPlotLine[];
70
69
  plotBands: PreparedAxisPlotBand[];
@@ -72,8 +71,11 @@ type PreparedBaseAxis = Omit<ChartAxis, 'type' | 'labels' | 'plotLines' | 'plotB
72
71
  };
73
72
  export type PreparedXAxis = PreparedBaseAxis & {
74
73
  rangeSlider: PreparedRangeSlider;
74
+ position: 'bottom';
75
+ };
76
+ export type PreparedYAxis = PreparedBaseAxis & {
77
+ position: 'left' | 'right';
75
78
  };
76
- export type PreparedYAxis = PreparedBaseAxis;
77
79
  export type PreparedAxis = PreparedXAxis | PreparedYAxis;
78
80
  export type AxesState = {
79
81
  xAxis: PreparedXAxis | null;
@@ -1,4 +1,4 @@
1
- import type { PreparedAxis, PreparedSeries, PreparedSplit, RangeSliderState, ZoomState } from '../../hooks';
1
+ import type { PreparedAxis, PreparedSeries, PreparedSplit, PreparedYAxis, RangeSliderState, ZoomState } from '../../hooks';
2
2
  import type { ChartScale } from './types';
3
3
  export { createXScale } from './x-scale';
4
4
  export { createYScale } from './y-scale';
@@ -7,7 +7,7 @@ type Args = {
7
7
  boundsHeight: number;
8
8
  series: PreparedSeries[];
9
9
  xAxis: PreparedAxis | null;
10
- yAxis: PreparedAxis[];
10
+ yAxis: PreparedYAxis[];
11
11
  split: PreparedSplit;
12
12
  isRangeSlider?: boolean;
13
13
  rangeSliderState?: RangeSliderState;
@@ -162,6 +162,7 @@ export const prepareBarXData = async (args) => {
162
162
  : yAxisTop + base + negativeStackHeight,
163
163
  width: rectWidth,
164
164
  height: shapeHeight,
165
+ _height: height,
165
166
  opacity: get(yValue.data, 'opacity', null),
166
167
  data: yValue.data,
167
168
  series: yValue.series,
@@ -178,9 +179,9 @@ export const prepareBarXData = async (args) => {
178
179
  }
179
180
  if (series.some((s) => s.stacking === 'percent')) {
180
181
  let acc = 0;
181
- const ratio = plotHeight / (positiveStackHeight - stackItems.length);
182
+ const ratio = plotHeight / positiveStackHeight;
182
183
  stackItems.forEach((item) => {
183
- item.height = item.height * ratio;
184
+ item.height = item._height * ratio;
184
185
  item.y = plotHeight - item.height - acc;
185
186
  acc += item.height + 1;
186
187
  });
@@ -10,4 +10,9 @@ export type PreparedBarXData = Omit<TooltipDataChunkBarX, 'series'> & {
10
10
  label?: LabelData;
11
11
  htmlElements: HtmlItem[];
12
12
  isLastStackItem: boolean;
13
+ /**
14
+ * the utility field for storing the original height (for recalculations, etc.)
15
+ * should not be used for displaying
16
+ */
17
+ _height: number;
13
18
  };
@@ -1,11 +1,14 @@
1
1
  import type { Dispatch } from 'd3';
2
2
  import type { AxisPlotBand, AxisPlotLine, PointPosition, TooltipDataChunk } from '../../types';
3
3
  import type { PreparedTooltip } from '../types';
4
+ import type { PreparedXAxis, PreparedYAxis } from '../useAxis/types';
4
5
  type Args = {
5
6
  dispatcher: Dispatch<object>;
6
7
  tooltip: PreparedTooltip;
8
+ xAxis?: PreparedXAxis | null;
9
+ yAxis?: PreparedYAxis;
7
10
  };
8
- export declare const useTooltip: ({ dispatcher, tooltip }: Args) => {
11
+ export declare const useTooltip: ({ dispatcher, tooltip, xAxis, yAxis }: Args) => {
9
12
  hovered: TooltipDataChunk[] | undefined;
10
13
  hoveredPlotLines: AxisPlotLine[] | undefined;
11
14
  hoveredPlotBands: AxisPlotBand[] | undefined;
@@ -1,21 +1,28 @@
1
1
  import React from 'react';
2
2
  import isEqual from 'lodash/isEqual';
3
- export const useTooltip = ({ dispatcher, tooltip }) => {
3
+ import { getSortedHovered } from '../../components/Tooltip/DefaultTooltipContent/utils';
4
+ export const useTooltip = ({ dispatcher, tooltip, xAxis, yAxis }) => {
4
5
  const [{ hovered, hoveredPlotLines, hoveredPlotBands, pointerPosition }, setTooltipState] = React.useState({});
5
6
  const prevHovered = React.useRef(hovered);
6
7
  React.useEffect(() => {
7
8
  if (tooltip === null || tooltip === void 0 ? void 0 : tooltip.enabled) {
8
9
  dispatcher.on('hover-shape.tooltip', (nextHovered, nextPointerPosition, nextHoveredPlots) => {
9
10
  const filteredNextHovered = nextHovered === null || nextHovered === void 0 ? void 0 : nextHovered.filter((item) => 'y' in item.data ? item.data.y !== null : true);
10
- const isHoveredChanged = !isEqual(prevHovered.current, filteredNextHovered);
11
+ const sortedHovered = getSortedHovered({
12
+ hovered: filteredNextHovered !== null && filteredNextHovered !== void 0 ? filteredNextHovered : [],
13
+ sorting: tooltip === null || tooltip === void 0 ? void 0 : tooltip.sorting,
14
+ xAxis,
15
+ yAxis,
16
+ });
17
+ const isHoveredChanged = !isEqual(prevHovered.current, sortedHovered);
11
18
  const newTooltipState = {
12
- hovered: isHoveredChanged ? filteredNextHovered : prevHovered.current,
19
+ hovered: isHoveredChanged ? sortedHovered : prevHovered.current,
13
20
  hoveredPlotLines: nextHoveredPlots === null || nextHoveredPlots === void 0 ? void 0 : nextHoveredPlots.lines,
14
21
  hoveredPlotBands: nextHoveredPlots === null || nextHoveredPlots === void 0 ? void 0 : nextHoveredPlots.bands,
15
22
  pointerPosition: nextPointerPosition,
16
23
  };
17
24
  if (isHoveredChanged) {
18
- prevHovered.current = filteredNextHovered;
25
+ prevHovered.current = sortedHovered;
19
26
  }
20
27
  setTooltipState(newTooltipState);
21
28
  });
@@ -25,7 +32,7 @@ export const useTooltip = ({ dispatcher, tooltip }) => {
25
32
  dispatcher.on('hover-shape.tooltip', null);
26
33
  }
27
34
  };
28
- }, [dispatcher, tooltip]);
35
+ }, [dispatcher, tooltip, xAxis, yAxis]);
29
36
  return {
30
37
  hovered,
31
38
  hoveredPlotLines,
@@ -6,6 +6,7 @@ import type { BaseTextStyle } from './base';
6
6
  import type { ChartBrush } from './brush';
7
7
  export type ChartAxisType = (typeof AXIS_TYPE)[keyof typeof AXIS_TYPE];
8
8
  export type ChartAxisTitleAlignment = 'left' | 'center' | 'right';
9
+ export type ChartAxisTitleRotation = 0 | 90 | -90;
9
10
  export interface ChartAxisLabels {
10
11
  /** Enable or disable the axis labels. */
11
12
  enabled?: boolean;
@@ -314,7 +315,7 @@ export interface ChartYAxisTitle extends ChartAxisTitle {
314
315
  *
315
316
  * The default values are -90 for the left axis and 90 for the right.
316
317
  */
317
- rotation?: 0 | 90 | -90;
318
+ rotation?: ChartAxisTitleRotation;
318
319
  /**
319
320
  * Interval of the tick marks(absolute or relative to the chart area).
320
321
  *
@@ -113,8 +113,13 @@ export type ChartTooltipRowRendererArgs = {
113
113
  value: string | number | null | undefined;
114
114
  formattedValue?: string;
115
115
  hovered?: TooltipDataChunk<unknown>[];
116
+ /**
117
+ * CSS class name pre-built with active/striped modifiers.
118
+ * Apply it to the root `<tr>` element of the returned row: `<tr className={className}>`.
119
+ */
116
120
  className?: string;
117
121
  };
122
+ export type ChartTooltipSortComparator<T = MeaningfulAny> = (a: TooltipDataChunk<T>, b: TooltipDataChunk<T>) => number;
118
123
  export interface ChartTooltip<T = MeaningfulAny> {
119
124
  enabled?: boolean;
120
125
  /** Specifies the renderer for the tooltip. If returned null default tooltip renderer will be used. */
@@ -122,7 +127,26 @@ export interface ChartTooltip<T = MeaningfulAny> {
122
127
  /**
123
128
  * Defines the way a single data/series is displayed (corresponding to a separate selected point/ruler/shape on the chart).
124
129
  * It is useful in cases where you need to display additional information, but keep the general format of the tooltip.
125
- * If a string is returned, it will be interpreted as raw HTML and inserted without escaping.
130
+ *
131
+ * The returned React element must be a `<tr>` so that it fits into the table layout used by the tooltip.
132
+ * Apply the `className` arg to the root `<tr>` to get the correct active/striped styles.
133
+ *
134
+ * If a string is returned, it will be parsed as HTML and rendered as-is — the string must be a complete
135
+ * `<tr>...</tr>` element.
136
+ * @example React element
137
+ * ```tsx
138
+ * rowRenderer: ({id, name, value, className}) => (
139
+ * <tr key={id} className={className}>
140
+ * <td>{name}</td>
141
+ * <td>{value}</td>
142
+ * </tr>
143
+ * )
144
+ * ```
145
+ * @example Raw HTML string
146
+ * ```ts
147
+ * rowRenderer: ({name, value, className}) =>
148
+ * `<tr class="${className}"><td>${name}</td><td>${value}</td></tr>`
149
+ * ```
126
150
  */
127
151
  rowRenderer?: ((args: ChartTooltipRowRendererArgs) => React.ReactElement | string) | null;
128
152
  pin?: {
@@ -158,4 +182,23 @@ export interface ChartTooltip<T = MeaningfulAny> {
158
182
  * It is assigned as a data-qa attribute to an element.
159
183
  */
160
184
  qa?: string;
185
+ /**
186
+ * Controls the order of tooltip rows. Applied to `hovered` before rendering.
187
+ * Use a custom comparator `(a, b) => number` for arbitrary ordering.
188
+ */
189
+ sorting?: {
190
+ /**
191
+ * Determines what data should be used to sort by.
192
+ * `'value'` uses the numeric value of each series point: `y` for most series
193
+ * (line, area, bar-x, scatter, waterfall), `x` for bar-y, and `value` for
194
+ * pie, radar, heatmap, treemap, funnel. `null` values are sorted as lowest.
195
+ * @default undefined (sorting disabled)
196
+ */
197
+ key?: 'value' | undefined;
198
+ /**
199
+ * Sorting direction.
200
+ * @default 'asc'
201
+ */
202
+ direction?: 'asc' | 'desc';
203
+ } | ChartTooltipSortComparator<T>;
161
204
  }
@@ -50,9 +50,9 @@ export function getZoomedSeriesData(args) {
50
50
  }
51
51
  const zoomedSeriesData = [];
52
52
  const zoomedShapesSeriesData = [];
53
- let prevPointInRange = false;
54
- let currentPointInRange = false;
55
53
  seriesData.forEach((seriesItem) => {
54
+ let prevPointInRange = false;
55
+ let currentPointInRange = false;
56
56
  const filteredData = [];
57
57
  const filteredShapesData = SERIES_TYPE_WITH_HIDDEN_POINTS.includes(seriesItem.type) && (xAxis === null || xAxis === void 0 ? void 0 : xAxis.type) !== 'category'
58
58
  ? []
@@ -1,6 +1,6 @@
1
1
  import React from 'react';
2
2
  import type { Dispatch } from 'd3';
3
- import type { ChartScale, LegendItem, OnLegendItemClick, PreparedLegend, PreparedSeries, PreparedSplit, PreparedXAxis, RangeSliderState, ShapeData, ZoomState } from '../../hooks';
3
+ import type { ChartScale, LegendItem, OnLegendItemClick, PreparedLegend, PreparedSeries, PreparedSplit, PreparedXAxis, PreparedYAxis, RangeSliderState, ShapeData, ZoomState } from '../../hooks';
4
4
  import type { PreparedChart } from '../../hooks/types';
5
5
  import type { LegendConfig } from '../../types';
6
6
  import type { ChartInnerProps } from './types';
@@ -21,43 +21,7 @@ export declare function useChartInnerProps(props: Props): {
21
21
  boundsHeight: number;
22
22
  boundsWidth: number;
23
23
  xAxis: PreparedXAxis | null;
24
- yAxis: (Omit<import("../..").ChartAxis, "type" | "labels" | "plotLines" | "plotBands"> & {
25
- type: import("../..").ChartAxisType;
26
- labels: Omit<import("../..").ChartAxisLabels, "style" | "enabled" | "padding" | "autoRotation"> & Required<Pick<import("../..").ChartAxisLabels, "margin" | "enabled" | "html" | "rotation" | "padding">> & {
27
- style: import("../..").BaseTextStyle;
28
- rotation: number;
29
- height: number;
30
- width: number;
31
- lineHeight: number;
32
- maxWidth: number;
33
- };
34
- title: {
35
- height: number;
36
- width: number;
37
- text: string;
38
- margin: number;
39
- style: import("../..").BaseTextStyle;
40
- align: import("../..").ChartAxisTitleAlignment;
41
- maxRowCount: number;
42
- rotation: number;
43
- maxWidth: number;
44
- html: boolean;
45
- };
46
- min?: number;
47
- grid: {
48
- enabled: boolean;
49
- };
50
- maxPadding: number;
51
- ticks: {
52
- pixelInterval?: number;
53
- };
54
- tickMarks: import("../../hooks").PreparedAxisTickMarks;
55
- position: "left" | "right" | "top" | "bottom";
56
- plotIndex: number;
57
- plotLines: import("../../hooks").PreparedAxisPlotLine[];
58
- plotBands: import("../../hooks").PreparedAxisPlotBand[];
59
- crosshair: Required<import("../..").AxisCrosshair>;
60
- })[];
24
+ yAxis: PreparedYAxis[];
61
25
  shapesData: ShapeData[];
62
26
  shapesReady: boolean;
63
27
  handleLegendItemClick: OnLegendItemClick;
@@ -5,40 +5,4 @@ export declare function recalculateYAxisLabelsWidth(props: {
5
5
  seriesData: PreparedSeries[];
6
6
  yAxis: PreparedYAxis[];
7
7
  yScale?: (ChartScale | undefined)[];
8
- }): Promise<(Omit<import("../../..").ChartAxis, "type" | "labels" | "plotLines" | "plotBands"> & {
9
- type: import("../../..").ChartAxisType;
10
- labels: Omit<import("../../..").ChartAxisLabels, "style" | "enabled" | "padding" | "autoRotation"> & Required<Pick<import("../../..").ChartAxisLabels, "margin" | "enabled" | "html" | "rotation" | "padding">> & {
11
- style: import("../../..").BaseTextStyle;
12
- rotation: number;
13
- height: number;
14
- width: number;
15
- lineHeight: number;
16
- maxWidth: number;
17
- };
18
- title: {
19
- height: number;
20
- width: number;
21
- text: string;
22
- margin: number;
23
- style: import("../../..").BaseTextStyle;
24
- align: import("../../..").ChartAxisTitleAlignment;
25
- maxRowCount: number;
26
- rotation: number;
27
- maxWidth: number;
28
- html: boolean;
29
- };
30
- min?: number;
31
- grid: {
32
- enabled: boolean;
33
- };
34
- maxPadding: number;
35
- ticks: {
36
- pixelInterval?: number;
37
- };
38
- tickMarks: import("../../../hooks").PreparedAxisTickMarks;
39
- position: "left" | "right" | "top" | "bottom";
40
- plotIndex: number;
41
- plotLines: import("../../../hooks").PreparedAxisPlotLine[];
42
- plotBands: import("../../../hooks").PreparedAxisPlotBand[];
43
- crosshair: Required<import("../../..").AxisCrosshair>;
44
- })[]>;
8
+ }): Promise<PreparedYAxis[]>;
@@ -12,8 +12,8 @@ export function Row(props) {
12
12
  }
13
13
  return null;
14
14
  }, [color, colorSymbol]);
15
- return (React.createElement("div", { className: b('content-row', { active, striped }, className), style: style },
16
- colorItem,
17
- React.createElement("span", { className: b('content-row-label') }, label),
18
- value && React.createElement("span", { className: b('content-row-value') }, value)));
15
+ return (React.createElement("tr", { className: b('content-row', { active, striped }, className), style: style },
16
+ colorItem && React.createElement("td", { className: b('content-row-color-cell') }, colorItem),
17
+ React.createElement("td", { className: b('content-row-label-cell') }, label),
18
+ value && React.createElement("td", { className: b('content-row-value-cell') }, value)));
19
19
  }
@@ -1,7 +1,6 @@
1
1
  import React from 'react';
2
2
  import { block } from '../../../utils';
3
3
  import { getFormattedValue } from '../../../utils/chart/format';
4
- import { Row } from './Row';
5
4
  import { getBuiltInAggregatedValue, getBuiltInAggregationLabel } from './utils';
6
5
  const b = block('tooltip');
7
6
  export function RowWithAggregation(props) {
@@ -19,5 +18,7 @@ export function RowWithAggregation(props) {
19
18
  format: valueFormat || { type: 'number' },
20
19
  })
21
20
  : resultValue;
22
- return (React.createElement(Row, { className: b('content-row-totals'), label: resultLabel, style: style, value: formattedResultValue }));
21
+ return (React.createElement("div", { className: b('content-row', { totals: true }), style: style },
22
+ React.createElement("span", { className: b('content-row-totals-label') }, resultLabel),
23
+ formattedResultValue && (React.createElement("span", { className: b('content-row-totals-value') }, formattedResultValue))));
23
24
  }
@@ -1,5 +1,6 @@
1
1
  import React from 'react';
2
2
  import { Divider } from '@gravity-ui/uikit';
3
+ import parse from 'html-react-parser';
3
4
  import get from 'lodash/get';
4
5
  import isEqual from 'lodash/isEqual';
5
6
  import { usePrevious } from '../../../hooks';
@@ -35,7 +36,7 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
35
36
  hovered,
36
37
  });
37
38
  if (typeof result === 'string') {
38
- return React.createElement("div", { key: id, dangerouslySetInnerHTML: { __html: result } });
39
+ return React.createElement(React.Fragment, { key: id }, parse(result));
39
40
  }
40
41
  return result;
41
42
  }
@@ -75,127 +76,136 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
75
76
  setScrollBarWidth(0);
76
77
  }
77
78
  }, [pinned]);
79
+ const rowsContent = (React.createElement(React.Fragment, null, visibleHovered.map((seriesItem, i) => {
80
+ var _a;
81
+ const { data, series, closest } = seriesItem;
82
+ const id = `${get(series, 'id')}_${i}`;
83
+ const color = get(data, 'color') || get(series, 'color');
84
+ // TODO: improve active item display https://github.com/gravity-ui/charts/issues/208
85
+ const active = closest && hovered.length > 1;
86
+ const striped = (i + 1) % 2 === 0;
87
+ const rowValueFormat = get(series, 'tooltip.valueFormat', valueFormat);
88
+ switch (series.type) {
89
+ case 'scatter':
90
+ case 'line':
91
+ case 'area':
92
+ case 'bar-x': {
93
+ const format = rowValueFormat || getDefaultValueFormat({ axis: yAxis });
94
+ const formattedValue = getFormattedValue({
95
+ value: hoveredValues[i],
96
+ format,
97
+ });
98
+ return renderRow({
99
+ id,
100
+ active,
101
+ color,
102
+ name: series.name,
103
+ striped,
104
+ value: hoveredValues[i],
105
+ formattedValue,
106
+ series,
107
+ });
108
+ }
109
+ case 'waterfall': {
110
+ const isTotal = get(data, 'total', false);
111
+ const subTotalValue = (_a = seriesItem.subTotal) !== null && _a !== void 0 ? _a : 0;
112
+ const format = rowValueFormat || getDefaultValueFormat({ axis: yAxis });
113
+ const subTotal = getFormattedValue({
114
+ value: subTotalValue,
115
+ format,
116
+ });
117
+ const formattedValue = getFormattedValue({
118
+ value: hoveredValues[i],
119
+ format,
120
+ });
121
+ return (React.createElement(React.Fragment, { key: id },
122
+ !isTotal && (React.createElement(React.Fragment, null,
123
+ React.createElement("div", { className: b('series-name') }, getXRowData(data, xAxis)),
124
+ React.createElement(Row, { label: series.name, value: formattedValue }))),
125
+ React.createElement(Row, { label: isTotal ? 'Total' : 'Subtotal', value: subTotal })));
126
+ }
127
+ case 'bar-y': {
128
+ const format = rowValueFormat || getDefaultValueFormat({ axis: xAxis });
129
+ const formattedValue = getFormattedValue({
130
+ value: hoveredValues[i],
131
+ format,
132
+ });
133
+ return renderRow({
134
+ id,
135
+ active,
136
+ color,
137
+ name: series.name,
138
+ striped,
139
+ value: hoveredValues[i],
140
+ formattedValue,
141
+ });
142
+ }
143
+ case 'pie':
144
+ case 'heatmap':
145
+ case 'treemap':
146
+ case 'funnel': {
147
+ const seriesData = data;
148
+ const formattedValue = getFormattedValue({
149
+ value: hoveredValues[i],
150
+ format: rowValueFormat || { type: 'number' },
151
+ });
152
+ return renderRow({
153
+ id,
154
+ color,
155
+ name: [seriesData.name || seriesData.id].flat().join('\n'),
156
+ value: hoveredValues[i],
157
+ formattedValue,
158
+ });
159
+ }
160
+ case 'sankey': {
161
+ const { target, data: source } = seriesItem;
162
+ const formattedValue = getFormattedValue({
163
+ value: hoveredValues[i],
164
+ format: rowValueFormat || { type: 'number' },
165
+ });
166
+ return renderRow({
167
+ id,
168
+ color,
169
+ name: `${source.name} → ${target === null || target === void 0 ? void 0 : target.name}`,
170
+ value: hoveredValues[i],
171
+ formattedValue,
172
+ });
173
+ }
174
+ case 'radar': {
175
+ const radarSeries = series;
176
+ const formattedValue = getFormattedValue({
177
+ value: hoveredValues[i],
178
+ format: rowValueFormat || { type: 'number' },
179
+ });
180
+ return renderRow({
181
+ id,
182
+ color,
183
+ active,
184
+ name: radarSeries.name || radarSeries.id,
185
+ value: hoveredValues[i],
186
+ formattedValue,
187
+ });
188
+ }
189
+ default: {
190
+ return null;
191
+ }
192
+ }
193
+ })));
78
194
  return (React.createElement("div", { className: b('content'), "data-qa": qa },
79
195
  formattedHeadValue && (React.createElement("div", { className: b('series-name') },
80
196
  React.createElement("div", { className: b('series-name-text'), dangerouslySetInnerHTML: { __html: formattedHeadValue } }))),
81
197
  React.createElement("div", { className: b('content-rows', { pinned }), ref: contentRowsRef, style: pinned ? { maxHeight: maxContentRowsHeight } : undefined },
82
- visibleHovered.map((seriesItem, i) => {
83
- var _a;
84
- const { data, series, closest } = seriesItem;
85
- const id = `${get(series, 'id')}_${i}`;
86
- const color = get(data, 'color') || get(series, 'color');
87
- // TODO: improve action item display https://github.com/gravity-ui/charts/issues/208
88
- const active = closest && hovered.length > 1;
89
- const striped = (i + 1) % 2 === 0;
90
- const rowValueFormat = get(series, 'tooltip.valueFormat', valueFormat);
91
- switch (series.type) {
92
- case 'scatter':
93
- case 'line':
94
- case 'area':
95
- case 'bar-x': {
96
- const format = rowValueFormat || getDefaultValueFormat({ axis: yAxis });
97
- const formattedValue = getFormattedValue({
98
- value: hoveredValues[i],
99
- format,
100
- });
101
- return renderRow({
102
- id,
103
- active,
104
- color,
105
- name: series.name,
106
- striped,
107
- value: hoveredValues[i],
108
- formattedValue,
109
- series,
110
- });
111
- }
112
- case 'waterfall': {
113
- const isTotal = get(data, 'total', false);
114
- const subTotalValue = (_a = seriesItem.subTotal) !== null && _a !== void 0 ? _a : 0;
115
- const format = rowValueFormat || getDefaultValueFormat({ axis: yAxis });
116
- const subTotal = getFormattedValue({
117
- value: subTotalValue,
118
- format,
119
- });
120
- const formattedValue = getFormattedValue({
121
- value: hoveredValues[i],
122
- format,
123
- });
124
- return (React.createElement(React.Fragment, { key: id },
125
- !isTotal && (React.createElement(React.Fragment, null,
126
- React.createElement("div", { className: b('series-name') }, getXRowData(data, xAxis)),
127
- React.createElement(Row, { label: series.name, value: formattedValue }))),
128
- React.createElement(Row, { label: isTotal ? 'Total' : 'Subtotal', value: subTotal })));
129
- }
130
- case 'bar-y': {
131
- const format = rowValueFormat || getDefaultValueFormat({ axis: xAxis });
132
- const formattedValue = getFormattedValue({
133
- value: hoveredValues[i],
134
- format,
135
- });
136
- return renderRow({
137
- id,
138
- active,
139
- color,
140
- name: series.name,
141
- striped,
142
- value: hoveredValues[i],
143
- formattedValue,
144
- });
145
- }
146
- case 'pie':
147
- case 'heatmap':
148
- case 'treemap':
149
- case 'funnel': {
150
- const seriesData = data;
151
- const formattedValue = getFormattedValue({
152
- value: hoveredValues[i],
153
- format: rowValueFormat || { type: 'number' },
154
- });
155
- return renderRow({
156
- id,
157
- color,
158
- name: [seriesData.name || seriesData.id].flat().join('\n'),
159
- value: hoveredValues[i],
160
- formattedValue,
161
- });
162
- }
163
- case 'sankey': {
164
- const { target, data: source } = seriesItem;
165
- const formattedValue = getFormattedValue({
166
- value: hoveredValues[i],
167
- format: rowValueFormat || { type: 'number' },
168
- });
169
- return renderRow({
170
- id,
171
- color,
172
- name: `${source.name} → ${target === null || target === void 0 ? void 0 : target.name}`,
173
- value: hoveredValues[i],
174
- formattedValue,
175
- });
176
- }
177
- case 'radar': {
178
- const radarSeries = series;
179
- const formattedValue = getFormattedValue({
180
- value: hoveredValues[i],
181
- format: rowValueFormat || { type: 'number' },
182
- });
183
- return renderRow({
184
- id,
185
- color,
186
- active,
187
- name: radarSeries.name || radarSeries.id,
188
- value: hoveredValues[i],
189
- formattedValue,
190
- });
191
- }
192
- default: {
193
- return null;
194
- }
195
- }
196
- }),
197
- Boolean(restHoveredValues.length) && (React.createElement(Row, { label: i18n('tooltip', 'label_more', { count: restHoveredValues.length }), striped: (visibleHovered.length + 1) % 2 === 0 }))),
198
+ React.createElement("table", { className: b('content-rows-table') },
199
+ React.createElement("tbody", null, rowsContent))),
200
+ Boolean(restHoveredValues.length) && (React.createElement("div", { className: b('content-row', {
201
+ striped: (visibleHovered.length + 1) % 2 === 0,
202
+ }) }, i18n('tooltip', 'label_more', { count: restHoveredValues.length }))),
198
203
  (totals === null || totals === void 0 ? void 0 : totals.enabled) && hovered.length > 1 && (React.createElement(React.Fragment, null,
199
204
  React.createElement(Divider, { className: b('content-row-totals-divider') }),
200
- React.createElement(RowWithAggregation, { aggregation: getPreparedAggregation({ hovered, totals, xAxis, yAxis }), label: totals.label, style: { marginRight: scrollBarWidth }, values: hoveredValues, valueFormat: (_a = totals.valueFormat) !== null && _a !== void 0 ? _a : valueFormat })))));
205
+ React.createElement(RowWithAggregation, { aggregation: getPreparedAggregation({
206
+ hovered,
207
+ totals,
208
+ xAxis,
209
+ yAxis,
210
+ }), label: totals.label, style: { marginRight: scrollBarWidth }, values: hoveredValues, valueFormat: (_a = totals.valueFormat) !== null && _a !== void 0 ? _a : valueFormat })))));
201
211
  };
@@ -35,6 +35,12 @@ export declare function getPreparedAggregation(args: {
35
35
  xAxis?: ChartXAxis | null;
36
36
  yAxis?: ChartYAxis;
37
37
  }): ChartTooltipTotalsBuiltInAggregation | (() => ChartTooltipTotalsAggregationValue);
38
+ export declare function getSortedHovered(args: {
39
+ hovered: TooltipDataChunk[];
40
+ sorting?: ChartTooltip['sorting'];
41
+ xAxis?: ChartXAxis | null;
42
+ yAxis?: ChartYAxis;
43
+ }): TooltipDataChunk[];
38
44
  export declare function getTooltipRowColorSymbol({ series, color, height, width, }: {
39
45
  color?: string;
40
46
  series?: TooltipDataChunk['series'];