@gravity-ui/charts 1.14.0 → 1.16.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 (79) hide show
  1. package/dist/cjs/components/ChartInner/useChartInnerProps.js +7 -2
  2. package/dist/cjs/components/Tooltip/ChartTooltipContent.d.ts +5 -2
  3. package/dist/cjs/components/Tooltip/ChartTooltipContent.js +2 -2
  4. package/dist/cjs/components/Tooltip/DefaultTooltipContent/Row.d.ts +2 -1
  5. package/dist/cjs/components/Tooltip/DefaultTooltipContent/Row.js +3 -3
  6. package/dist/cjs/components/Tooltip/DefaultTooltipContent/{RowTotals.d.ts → RowWithAggregation.d.ts} +2 -1
  7. package/dist/cjs/components/Tooltip/DefaultTooltipContent/{RowTotals.js → RowWithAggregation.js} +3 -3
  8. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.d.ts +4 -1
  9. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +187 -87
  10. package/dist/cjs/components/Tooltip/index.js +1 -1
  11. package/dist/cjs/components/Tooltip/styles.css +11 -1
  12. package/dist/cjs/hooks/useAxisScales/index.d.ts +1 -0
  13. package/dist/cjs/hooks/useAxisScales/index.js +53 -37
  14. package/dist/cjs/hooks/useChartOptions/x-axis.d.ts +3 -1
  15. package/dist/cjs/hooks/useChartOptions/x-axis.js +4 -3
  16. package/dist/cjs/hooks/useSeries/prepare-area.js +1 -0
  17. package/dist/cjs/hooks/useSeries/prepare-bar-x.js +1 -0
  18. package/dist/cjs/hooks/useSeries/prepare-bar-y.d.ts +1 -0
  19. package/dist/cjs/hooks/useSeries/prepare-bar-y.js +1 -0
  20. package/dist/cjs/hooks/useSeries/prepare-line.js +1 -0
  21. package/dist/cjs/hooks/useSeries/prepare-pie.js +1 -0
  22. package/dist/cjs/hooks/useSeries/prepare-radar.js +1 -0
  23. package/dist/cjs/hooks/useSeries/prepare-sankey.js +1 -0
  24. package/dist/cjs/hooks/useSeries/prepare-scatter.js +1 -0
  25. package/dist/cjs/hooks/useSeries/prepare-treemap.js +1 -0
  26. package/dist/cjs/hooks/useSeries/prepare-waterfall.js +1 -0
  27. package/dist/cjs/hooks/useSeries/types.d.ts +2 -1
  28. package/dist/cjs/hooks/utils/bar-x.d.ts +16 -0
  29. package/dist/cjs/hooks/utils/bar-x.js +41 -0
  30. package/dist/cjs/i18n/keysets/en.json +2 -1
  31. package/dist/cjs/i18n/keysets/ru.json +2 -1
  32. package/dist/cjs/types/chart/base.d.ts +13 -0
  33. package/dist/cjs/types/chart/tooltip.d.ts +19 -1
  34. package/dist/cjs/utils/chart/format.d.ts +9 -2
  35. package/dist/cjs/utils/chart/format.js +39 -5
  36. package/dist/cjs/utils/chart/index.d.ts +7 -11
  37. package/dist/cjs/utils/chart/index.js +13 -28
  38. package/dist/cjs/utils/misc.d.ts +1 -0
  39. package/dist/cjs/utils/misc.js +6 -0
  40. package/dist/esm/components/ChartInner/useChartInnerProps.js +7 -2
  41. package/dist/esm/components/Tooltip/ChartTooltipContent.d.ts +5 -2
  42. package/dist/esm/components/Tooltip/ChartTooltipContent.js +2 -2
  43. package/dist/esm/components/Tooltip/DefaultTooltipContent/Row.d.ts +2 -1
  44. package/dist/esm/components/Tooltip/DefaultTooltipContent/Row.js +3 -3
  45. package/dist/esm/components/Tooltip/DefaultTooltipContent/{RowTotals.d.ts → RowWithAggregation.d.ts} +2 -1
  46. package/dist/esm/components/Tooltip/DefaultTooltipContent/{RowTotals.js → RowWithAggregation.js} +3 -3
  47. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.d.ts +4 -1
  48. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +187 -87
  49. package/dist/esm/components/Tooltip/index.js +1 -1
  50. package/dist/esm/components/Tooltip/styles.css +11 -1
  51. package/dist/esm/hooks/useAxisScales/index.d.ts +1 -0
  52. package/dist/esm/hooks/useAxisScales/index.js +53 -37
  53. package/dist/esm/hooks/useChartOptions/x-axis.d.ts +3 -1
  54. package/dist/esm/hooks/useChartOptions/x-axis.js +4 -3
  55. package/dist/esm/hooks/useSeries/prepare-area.js +1 -0
  56. package/dist/esm/hooks/useSeries/prepare-bar-x.js +1 -0
  57. package/dist/esm/hooks/useSeries/prepare-bar-y.d.ts +1 -0
  58. package/dist/esm/hooks/useSeries/prepare-bar-y.js +1 -0
  59. package/dist/esm/hooks/useSeries/prepare-line.js +1 -0
  60. package/dist/esm/hooks/useSeries/prepare-pie.js +1 -0
  61. package/dist/esm/hooks/useSeries/prepare-radar.js +1 -0
  62. package/dist/esm/hooks/useSeries/prepare-sankey.js +1 -0
  63. package/dist/esm/hooks/useSeries/prepare-scatter.js +1 -0
  64. package/dist/esm/hooks/useSeries/prepare-treemap.js +1 -0
  65. package/dist/esm/hooks/useSeries/prepare-waterfall.js +1 -0
  66. package/dist/esm/hooks/useSeries/types.d.ts +2 -1
  67. package/dist/esm/hooks/utils/bar-x.d.ts +16 -0
  68. package/dist/esm/hooks/utils/bar-x.js +41 -0
  69. package/dist/esm/i18n/keysets/en.json +2 -1
  70. package/dist/esm/i18n/keysets/ru.json +2 -1
  71. package/dist/esm/types/chart/base.d.ts +13 -0
  72. package/dist/esm/types/chart/tooltip.d.ts +19 -1
  73. package/dist/esm/utils/chart/format.d.ts +9 -2
  74. package/dist/esm/utils/chart/format.js +39 -5
  75. package/dist/esm/utils/chart/index.d.ts +7 -11
  76. package/dist/esm/utils/chart/index.js +13 -28
  77. package/dist/esm/utils/misc.d.ts +1 -0
  78. package/dist/esm/utils/misc.js +6 -0
  79. package/package.json +1 -1
@@ -39,8 +39,13 @@ export function useChartInnerProps(props) {
39
39
  const [xAxis, setXAxis] = React.useState(null);
40
40
  React.useEffect(() => {
41
41
  setXAxis(null);
42
- getPreparedXAxis({ xAxis: data.xAxis, width, seriesData: zoomedSeriesData }).then((val) => setXAxis(val));
43
- }, [data.xAxis, width, zoomedSeriesData]);
42
+ getPreparedXAxis({
43
+ xAxis: data.xAxis,
44
+ width,
45
+ seriesData: zoomedSeriesData,
46
+ seriesOptions: preparedSeriesOptions,
47
+ }).then((val) => setXAxis(val));
48
+ }, [data.xAxis, preparedSeriesOptions, width, zoomedSeriesData]);
44
49
  const [yAxis, setYAxis] = React.useState([]);
45
50
  React.useEffect(() => {
46
51
  setYAxis([]);
@@ -2,10 +2,13 @@ import React from 'react';
2
2
  import type { ChartTooltip, ChartXAxis, ChartYAxis, TooltipDataChunk } from '../../types';
3
3
  export interface ChartTooltipContentProps {
4
4
  hovered?: TooltipDataChunk[];
5
- xAxis?: ChartXAxis | null;
6
- yAxis?: ChartYAxis;
5
+ pinned?: boolean;
7
6
  renderer?: ChartTooltip['renderer'];
7
+ rowRenderer?: ChartTooltip['rowRenderer'];
8
8
  valueFormat?: ChartTooltip['valueFormat'];
9
+ headerFormat?: ChartTooltip['headerFormat'];
9
10
  totals?: ChartTooltip['totals'];
11
+ xAxis?: ChartXAxis | null;
12
+ yAxis?: ChartYAxis;
10
13
  }
11
14
  export declare const ChartTooltipContent: (props: ChartTooltipContentProps) => React.JSX.Element | null;
@@ -2,10 +2,10 @@ import React from 'react';
2
2
  import isNil from 'lodash/isNil';
3
3
  import { DefaultTooltipContent } from './DefaultTooltipContent';
4
4
  export const ChartTooltipContent = (props) => {
5
- const { hovered, xAxis, yAxis, renderer, valueFormat, totals } = props;
5
+ const { hovered, xAxis, yAxis, renderer, rowRenderer, valueFormat, headerFormat, totals, pinned, } = props;
6
6
  if (!hovered) {
7
7
  return null;
8
8
  }
9
9
  const customTooltip = renderer === null || renderer === void 0 ? void 0 : renderer({ hovered, xAxis, yAxis });
10
- return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, valueFormat: valueFormat, totals: totals })) : (customTooltip);
10
+ return isNil(customTooltip) ? (React.createElement(DefaultTooltipContent, { hovered: hovered, pinned: pinned, rowRenderer: rowRenderer, totals: totals, valueFormat: valueFormat, headerFormat: headerFormat, xAxis: xAxis, yAxis: yAxis })) : (customTooltip);
11
11
  };
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
  export declare function Row(props: {
3
3
  label: React.ReactNode;
4
- value: React.ReactNode;
5
4
  active?: boolean;
6
5
  className?: string;
7
6
  color?: string;
8
7
  striped?: boolean;
8
+ style?: React.CSSProperties;
9
+ value?: React.ReactNode;
9
10
  }): React.JSX.Element;
@@ -2,9 +2,9 @@ import React from 'react';
2
2
  import { block } from '../../../utils';
3
3
  const b = block('tooltip');
4
4
  export function Row(props) {
5
- const { label, value, active, color, className, striped } = props;
6
- return (React.createElement("div", { className: b('content-row', { active, striped }, className) },
5
+ const { label, value, active, color, className, striped, style } = props;
6
+ return (React.createElement("div", { className: b('content-row', { active, striped }, className), style: style },
7
7
  color && React.createElement("div", { className: b('content-row-color'), style: { backgroundColor: color } }),
8
8
  label,
9
- React.createElement("span", { className: b('content-row-value') }, value)));
9
+ value && React.createElement("span", { className: b('content-row-value') }, value)));
10
10
  }
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
2
  import type { ChartTooltipTotalsAggregationValue, ChartTooltipTotalsBuiltInAggregation, ValueFormat } from '../../../types';
3
3
  import type { HoveredValue } from './utils';
4
- export declare function RowTotals(props: {
4
+ export declare function RowWithAggregation(props: {
5
5
  aggregation: ChartTooltipTotalsBuiltInAggregation | (() => ChartTooltipTotalsAggregationValue);
6
6
  values: HoveredValue[];
7
7
  label?: string;
8
+ style?: React.CSSProperties;
8
9
  valueFormat?: ValueFormat;
9
10
  }): React.JSX.Element;
@@ -4,8 +4,8 @@ import { getFormattedValue } from '../../../utils/chart/format';
4
4
  import { Row } from './Row';
5
5
  import { getBuiltInAggregatedValue, getBuiltInAggregationLabel } from './utils';
6
6
  const b = block('tooltip');
7
- export function RowTotals(props) {
8
- const { aggregation, label, valueFormat, values } = props;
7
+ export function RowWithAggregation(props) {
8
+ const { aggregation, label, style, valueFormat, values } = props;
9
9
  let resultLabel = label;
10
10
  if (!resultLabel && typeof aggregation === 'string') {
11
11
  resultLabel = getBuiltInAggregationLabel({ aggregation });
@@ -19,5 +19,5 @@ export function RowTotals(props) {
19
19
  format: valueFormat || { type: 'number' },
20
20
  })
21
21
  : resultValue;
22
- return (React.createElement(Row, { className: b('content-row-totals'), label: resultLabel, value: formattedResultValue }));
22
+ return (React.createElement(Row, { className: b('content-row-totals'), label: resultLabel, style: style, value: formattedResultValue }));
23
23
  }
@@ -2,10 +2,13 @@ import React from 'react';
2
2
  import type { ChartTooltip, ChartXAxis, ChartYAxis, TooltipDataChunk, ValueFormat } from '../../../types';
3
3
  type Props = {
4
4
  hovered: TooltipDataChunk[];
5
+ pinned?: boolean;
6
+ rowRenderer?: ChartTooltip['rowRenderer'];
5
7
  totals?: ChartTooltip['totals'];
6
8
  valueFormat?: ValueFormat;
9
+ headerFormat?: ChartTooltip['headerFormat'];
7
10
  xAxis?: ChartXAxis | null;
8
11
  yAxis?: ChartYAxis;
9
12
  };
10
- export declare const DefaultTooltipContent: ({ hovered, xAxis, yAxis, valueFormat, totals }: Props) => React.JSX.Element;
13
+ export declare const DefaultTooltipContent: ({ hovered, pinned, rowRenderer, totals, valueFormat, headerFormat, xAxis, yAxis, }: Props) => React.JSX.Element;
11
14
  export {};
@@ -1,102 +1,202 @@
1
1
  import React from 'react';
2
2
  import { Divider } from '@gravity-ui/uikit';
3
3
  import get from 'lodash/get';
4
- import { block } from '../../../utils';
4
+ import isEqual from 'lodash/isEqual';
5
+ import { usePrevious } from '../../../hooks';
6
+ import { i18n } from '../../../i18n';
7
+ import { block, hasVerticalScrollbar } from '../../../utils';
5
8
  import { getFormattedValue } from '../../../utils/chart/format';
6
9
  import { Row } from './Row';
7
- import { RowTotals } from './RowTotals';
10
+ import { RowWithAggregation } from './RowWithAggregation';
8
11
  import { getDefaultValueFormat, getHoveredValues, getMeasureValue, getPreparedAggregation, getXRowData, } from './utils';
9
12
  const b = block('tooltip');
10
- export const DefaultTooltipContent = ({ hovered, xAxis, yAxis, valueFormat, totals }) => {
13
+ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, valueFormat, headerFormat, xAxis, yAxis, }) => {
14
+ var _a;
15
+ const [visibleRows, setVisibleRows] = React.useState();
16
+ const [maxContentRowsHeight, setMaxContentRowsHeight] = React.useState();
17
+ const [scrollBarWidth, setScrollBarWidth] = React.useState(0);
18
+ const contentRowsRef = React.useRef(null);
11
19
  const measureValue = getMeasureValue({ data: hovered, xAxis, yAxis });
12
20
  const hoveredValues = getHoveredValues({ hovered, xAxis, yAxis });
13
- return (React.createElement("div", { className: b('content') },
14
- measureValue && (React.createElement("div", { className: b('series-name'), dangerouslySetInnerHTML: { __html: measureValue } })),
15
- // eslint-disable-next-line complexity
16
- hovered.map((seriesItem, i) => {
17
- var _a;
18
- const { data, series, closest } = seriesItem;
19
- const id = `${get(series, 'id')}_${i}`;
20
- const color = get(data, 'color') || get(series, 'color');
21
- const active = closest && hovered.length > 1;
22
- const striped = (i + 1) % 2 === 0;
23
- switch (series.type) {
24
- case 'scatter':
25
- case 'line':
26
- case 'area':
27
- case 'bar-x': {
28
- const format = valueFormat || getDefaultValueFormat({ axis: yAxis });
29
- const formattedValue = getFormattedValue({
30
- value: hoveredValues[i],
31
- format,
32
- });
33
- return (React.createElement(Row, { key: id, active: active, color: color, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: series.name } }), striped: striped, value: formattedValue }));
34
- }
35
- case 'waterfall': {
36
- const isTotal = get(data, 'total', false);
37
- const subTotalValue = (_a = seriesItem.subTotal) !== null && _a !== void 0 ? _a : 0;
38
- const format = valueFormat || getDefaultValueFormat({ axis: yAxis });
39
- const subTotal = getFormattedValue({
40
- value: subTotalValue,
41
- format,
42
- });
43
- const formattedValue = getFormattedValue({
44
- value: hoveredValues[i],
45
- format,
46
- });
47
- return (React.createElement(React.Fragment, { key: id },
48
- !isTotal && (React.createElement(React.Fragment, null,
49
- React.createElement("div", { className: b('series-name') }, getXRowData(data, xAxis)),
50
- React.createElement(Row, { label: series.name, value: formattedValue }))),
51
- React.createElement(Row, { label: isTotal ? 'Total' : 'Subtotal', value: subTotal })));
52
- }
53
- case 'bar-y': {
54
- const format = valueFormat || getDefaultValueFormat({ axis: xAxis });
55
- const formattedValue = getFormattedValue({
56
- value: hoveredValues[i],
57
- format,
58
- });
59
- return (React.createElement(Row, { key: id, active: active, color: color, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: series.name } }), striped: striped, value: formattedValue }));
60
- }
61
- case 'pie':
62
- case 'treemap': {
63
- const seriesData = data;
64
- const formattedValue = getFormattedValue({
65
- value: hoveredValues[i],
66
- format: valueFormat || { type: 'number' },
67
- });
68
- return (React.createElement(Row, { key: id, color: color, label: React.createElement("span", { dangerouslySetInnerHTML: {
69
- __html: [seriesData.name || seriesData.id]
70
- .flat()
71
- .join('\n'),
72
- } }), value: formattedValue }));
21
+ const prevHoveredValues = usePrevious(hoveredValues);
22
+ const visibleHovered = pinned || !visibleRows ? hovered : hovered.slice(0, visibleRows);
23
+ const restHoveredValues = pinned || !visibleRows ? [] : hoveredValues.slice(visibleRows);
24
+ const renderRow = ({ id, name, color, active, striped, value, formattedValue, }) => {
25
+ if (typeof rowRenderer === 'function') {
26
+ return rowRenderer({
27
+ id,
28
+ name,
29
+ color,
30
+ value,
31
+ formattedValue,
32
+ striped,
33
+ active,
34
+ className: b('content-row', { active, striped }),
35
+ hovered,
36
+ });
37
+ }
38
+ return (React.createElement(Row, { key: id, active: active, color: color, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: name } }), striped: striped, value: formattedValue }));
39
+ };
40
+ const formattedHeadValue = headerFormat
41
+ ? getFormattedValue({ value: measureValue, format: headerFormat })
42
+ : measureValue;
43
+ React.useEffect(() => {
44
+ if (!contentRowsRef.current) {
45
+ return;
46
+ }
47
+ if (!hasVerticalScrollbar(contentRowsRef.current)) {
48
+ return;
49
+ }
50
+ if (!isEqual(hoveredValues, prevHoveredValues)) {
51
+ const { clientHeight } = contentRowsRef.current;
52
+ const { top: containerTop } = contentRowsRef.current.getBoundingClientRect();
53
+ const rows = contentRowsRef.current.querySelectorAll(`.${b('content-row')}`);
54
+ let nextVisibleRows = 0;
55
+ let nextMaxContentRowsHeight = 0;
56
+ for (let i = 0; i < rows.length; i++) {
57
+ const row = rows[i];
58
+ const { top, height } = row.getBoundingClientRect();
59
+ if (top - containerTop + height <= clientHeight) {
60
+ nextVisibleRows += 1;
61
+ nextMaxContentRowsHeight += height;
73
62
  }
74
- case 'sankey': {
75
- const { target, data: source } = seriesItem;
76
- const formattedValue = getFormattedValue({
77
- value: hoveredValues[i],
78
- format: valueFormat || { type: 'number' },
79
- });
80
- return (React.createElement(Row, { key: id, color: source.color, label: React.createElement("span", null,
81
- source.name,
82
- " \u2192 ", target === null || target === void 0 ? void 0 :
83
- target.name,
84
- ":"), value: formattedValue }));
85
- }
86
- case 'radar': {
87
- const radarSeries = series;
88
- const formattedValue = getFormattedValue({
89
- value: hoveredValues[i],
90
- format: valueFormat || { type: 'number' },
91
- });
92
- return (React.createElement(Row, { key: id, active: active, color: color, label: radarSeries.name || radarSeries.id, value: formattedValue }));
93
- }
94
- default: {
95
- return null;
63
+ else {
64
+ break;
96
65
  }
97
66
  }
98
- }),
67
+ setVisibleRows(nextVisibleRows - 1);
68
+ setMaxContentRowsHeight(nextMaxContentRowsHeight);
69
+ }
70
+ }, [hoveredValues, prevHoveredValues]);
71
+ React.useEffect(() => {
72
+ if (!contentRowsRef.current) {
73
+ return;
74
+ }
75
+ if (pinned) {
76
+ const { offsetWidth, clientWidth } = contentRowsRef.current;
77
+ setScrollBarWidth(offsetWidth - clientWidth);
78
+ }
79
+ else {
80
+ setScrollBarWidth(0);
81
+ }
82
+ }, [pinned]);
83
+ return (React.createElement("div", { className: b('content') },
84
+ formattedHeadValue && (React.createElement("div", { className: b('series-name'), dangerouslySetInnerHTML: { __html: formattedHeadValue } })),
85
+ React.createElement("div", { className: b('content-rows', { pinned }), ref: contentRowsRef, style: { maxHeight: maxContentRowsHeight } },
86
+ visibleHovered.map((seriesItem, i) => {
87
+ var _a;
88
+ const { data, series, closest } = seriesItem;
89
+ const id = `${get(series, 'id')}_${i}`;
90
+ const color = get(data, 'color') || get(series, 'color');
91
+ // TODO: improve action item display https://github.com/gravity-ui/charts/issues/208
92
+ const active = closest && hovered.length > 1;
93
+ const striped = (i + 1) % 2 === 0;
94
+ const rowValueFormat = get(series, 'tooltip.valueFormat', valueFormat);
95
+ switch (series.type) {
96
+ case 'scatter':
97
+ case 'line':
98
+ case 'area':
99
+ case 'bar-x': {
100
+ const format = rowValueFormat || getDefaultValueFormat({ axis: yAxis });
101
+ const formattedValue = getFormattedValue({
102
+ value: hoveredValues[i],
103
+ format,
104
+ });
105
+ return renderRow({
106
+ id,
107
+ active,
108
+ color,
109
+ name: series.name,
110
+ striped,
111
+ value: hoveredValues[i],
112
+ formattedValue,
113
+ });
114
+ }
115
+ case 'waterfall': {
116
+ const isTotal = get(data, 'total', false);
117
+ const subTotalValue = (_a = seriesItem.subTotal) !== null && _a !== void 0 ? _a : 0;
118
+ const format = rowValueFormat || getDefaultValueFormat({ axis: yAxis });
119
+ const subTotal = getFormattedValue({
120
+ value: subTotalValue,
121
+ format,
122
+ });
123
+ const formattedValue = getFormattedValue({
124
+ value: hoveredValues[i],
125
+ format,
126
+ });
127
+ return (React.createElement(React.Fragment, { key: id },
128
+ !isTotal && (React.createElement(React.Fragment, null,
129
+ React.createElement("div", { className: b('series-name') }, getXRowData(data, xAxis)),
130
+ React.createElement(Row, { label: series.name, value: formattedValue }))),
131
+ React.createElement(Row, { label: isTotal ? 'Total' : 'Subtotal', value: subTotal })));
132
+ }
133
+ case 'bar-y': {
134
+ const format = rowValueFormat || getDefaultValueFormat({ axis: xAxis });
135
+ const formattedValue = getFormattedValue({
136
+ value: hoveredValues[i],
137
+ format,
138
+ });
139
+ return renderRow({
140
+ id,
141
+ active,
142
+ color,
143
+ name: series.name,
144
+ striped,
145
+ value: hoveredValues[i],
146
+ formattedValue,
147
+ });
148
+ }
149
+ case 'pie':
150
+ case 'treemap': {
151
+ const seriesData = data;
152
+ const formattedValue = getFormattedValue({
153
+ value: hoveredValues[i],
154
+ format: rowValueFormat || { type: 'number' },
155
+ });
156
+ return renderRow({
157
+ id,
158
+ color,
159
+ name: [seriesData.name || seriesData.id].flat().join('\n'),
160
+ value: hoveredValues[i],
161
+ formattedValue,
162
+ });
163
+ }
164
+ case 'sankey': {
165
+ const { target, data: source } = seriesItem;
166
+ const formattedValue = getFormattedValue({
167
+ value: hoveredValues[i],
168
+ format: rowValueFormat || { type: 'number' },
169
+ });
170
+ return renderRow({
171
+ id,
172
+ color,
173
+ name: `${source.name} → ${target === null || target === void 0 ? void 0 : target.name}`,
174
+ value: hoveredValues[i],
175
+ formattedValue,
176
+ });
177
+ }
178
+ case 'radar': {
179
+ const radarSeries = series;
180
+ const formattedValue = getFormattedValue({
181
+ value: hoveredValues[i],
182
+ format: rowValueFormat || { type: 'number' },
183
+ });
184
+ return renderRow({
185
+ id,
186
+ color,
187
+ active,
188
+ name: radarSeries.name || radarSeries.id,
189
+ value: hoveredValues[i],
190
+ formattedValue,
191
+ });
192
+ }
193
+ default: {
194
+ return null;
195
+ }
196
+ }
197
+ }),
198
+ Boolean(restHoveredValues.length) && (React.createElement(Row, { label: i18n('tooltip', 'label_more', { count: restHoveredValues.length }), striped: (visibleHovered.length + 1) % 2 === 0 }))),
99
199
  (totals === null || totals === void 0 ? void 0 : totals.enabled) && hovered.length > 1 && (React.createElement(React.Fragment, null,
100
200
  React.createElement(Divider, { className: b('content-row-totals-divider') }),
101
- React.createElement(RowTotals, { aggregation: getPreparedAggregation({ hovered, totals, xAxis, yAxis }), label: totals.label, values: hoveredValues, valueFormat: valueFormat })))));
201
+ 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 })))));
102
202
  };
@@ -23,5 +23,5 @@ export const Tooltip = (props) => {
23
23
  }, [left, top]);
24
24
  return (hovered === null || hovered === void 0 ? void 0 : hovered.length) ? (React.createElement(Popup, { anchorElement: anchor, className: b({ pinned: tooltipPinned }), disableTransition: true, floatingStyles: tooltipPinned ? undefined : { pointerEvents: 'none' }, offset: { mainAxis: 20 }, onOpenChange: tooltipPinned ? handleOnOpenChange : undefined, open: true, placement: ['right', 'left', 'top', 'bottom'] },
25
25
  React.createElement("div", { className: b('popup-content') },
26
- React.createElement(ChartTooltipContent, { hovered: hovered, xAxis: xAxis, yAxis: yAxis, renderer: tooltip.renderer, valueFormat: tooltip.valueFormat, totals: tooltip.totals })))) : null;
26
+ React.createElement(ChartTooltipContent, { hovered: hovered, pinned: tooltipPinned, renderer: tooltip.renderer, rowRenderer: tooltip.rowRenderer, totals: tooltip.totals, valueFormat: tooltip.valueFormat, headerFormat: tooltip.headerFormat, xAxis: xAxis, yAxis: yAxis })))) : null;
27
27
  };
@@ -1,4 +1,5 @@
1
1
  .gcharts-tooltip {
2
+ padding: var(--gcharts-tooltip-content-padding, 8px 0);
2
3
  box-shadow: 0 2px 12px var(--g-color-sfx-shadow);
3
4
  }
4
5
  .gcharts-tooltip__popup-content {
@@ -7,7 +8,16 @@
7
8
  background-color: var(--g-color-infographics-tooltip-bg);
8
9
  }
9
10
  .gcharts-tooltip__content {
10
- padding: var(--gcharts-tooltip-content-padding, 8px 0);
11
+ display: flex;
12
+ overflow: hidden;
13
+ flex-direction: column;
14
+ max-height: 90vh;
15
+ }
16
+ .gcharts-tooltip__content-rows {
17
+ overflow: hidden;
18
+ }
19
+ .gcharts-tooltip__content-rows_pinned {
20
+ overflow: auto;
11
21
  }
12
22
  .gcharts-tooltip__series-name {
13
23
  padding: 2px 14px 6px;
@@ -29,6 +29,7 @@ export declare function createXScale(args: {
29
29
  axis: PreparedAxis | ChartAxis;
30
30
  boundsWidth: number;
31
31
  series: (PreparedSeries | ChartSeries)[];
32
+ seriesOptions: PreparedSeriesOptions;
32
33
  hasZoomX?: boolean;
33
34
  }): ScaleBand<string> | ScaleLinear<number, number, never> | ScaleTime<number, number, never>;
34
35
  /**
@@ -4,6 +4,7 @@ import get from 'lodash/get';
4
4
  import { DEFAULT_AXIS_TYPE, SeriesType } from '../../constants';
5
5
  import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
6
6
  import { getBarYLayoutForNumericScale, groupBarYDataByYValue } from '../utils';
7
+ import { getBarXLayoutForNumericScale, groupBarXDataByXValue } from '../utils/bar-x';
7
8
  const X_AXIS_ZOOM_PADDING = 0.02;
8
9
  function isNumericalArrayData(data) {
9
10
  return data.every((d) => typeof d === 'number' || d === null);
@@ -130,32 +131,55 @@ function calculateXAxisPadding(series) {
130
131
  });
131
132
  return result;
132
133
  }
134
+ function getXScaleRange({ boundsWidth, series, seriesOptions, hasZoomX, axis, maxPadding, }) {
135
+ const xAxisZoomPadding = boundsWidth * X_AXIS_ZOOM_PADDING;
136
+ const xRange = [0, boundsWidth - maxPadding];
137
+ const xRangeZoom = [0 + xAxisZoomPadding, boundsWidth - xAxisZoomPadding];
138
+ const range = hasZoomX ? xRangeZoom : xRange;
139
+ const barXSeries = series.filter((s) => s.type === SeriesType.BarX);
140
+ if (barXSeries.length) {
141
+ const groupedData = groupBarXDataByXValue(barXSeries, axis);
142
+ if (Object.keys(groupedData).length > 1) {
143
+ const { bandSize } = getBarXLayoutForNumericScale({
144
+ plotWidth: boundsWidth - maxPadding,
145
+ groupedData,
146
+ seriesOptions,
147
+ });
148
+ const offset = bandSize / 2;
149
+ return [range[0] + offset, range[1] - offset];
150
+ }
151
+ }
152
+ return range;
153
+ }
133
154
  // eslint-disable-next-line complexity
134
155
  export function createXScale(args) {
135
- const { axis, boundsWidth, series, hasZoomX } = args;
156
+ const { axis, boundsWidth, series, seriesOptions, hasZoomX } = args;
136
157
  const xMinProps = get(axis, 'min');
137
158
  const xMaxProps = get(axis, 'max');
138
159
  const xType = get(axis, 'type', DEFAULT_AXIS_TYPE);
139
160
  const xCategories = get(axis, 'categories');
140
- const xTimestamps = get(axis, 'timestamps');
141
161
  const maxPadding = get(axis, 'maxPadding', 0);
142
162
  const xAxisMaxPadding = boundsWidth * maxPadding + calculateXAxisPadding(series);
143
- const xAxisZoomPadding = boundsWidth * X_AXIS_ZOOM_PADDING;
144
- const xRange = [0, boundsWidth - xAxisMaxPadding];
145
- const xRangeZoom = [0 + xAxisZoomPadding, boundsWidth - xAxisZoomPadding];
163
+ const range = getXScaleRange({
164
+ boundsWidth,
165
+ series,
166
+ seriesOptions,
167
+ hasZoomX,
168
+ axis,
169
+ maxPadding: xAxisMaxPadding,
170
+ });
146
171
  switch (axis.order) {
147
172
  case 'sortDesc':
148
173
  case 'reverse': {
149
- xRange.reverse();
150
- xRangeZoom.reverse();
174
+ range.reverse();
151
175
  }
152
176
  }
153
177
  switch (xType) {
154
178
  case 'linear':
155
179
  case 'logarithmic': {
156
- const domain = getDomainDataXBySeries(series);
157
- if (isNumericalArrayData(domain)) {
158
- const [xMinDomain, xMaxDomain] = extent(domain);
180
+ const domainData = getDomainDataXBySeries(series);
181
+ if (isNumericalArrayData(domainData)) {
182
+ const [xMinDomain, xMaxDomain] = extent(domainData);
159
183
  let xMin;
160
184
  let xMax;
161
185
  if (typeof xMinProps === 'number') {
@@ -176,11 +200,10 @@ export function createXScale(args) {
176
200
  : xMaxDomain;
177
201
  }
178
202
  const scaleFn = xType === 'logarithmic' ? scaleLog : scaleLinear;
179
- const scale = scaleFn()
180
- .domain([xMin, xMax])
181
- .range(hasZoomX ? xRangeZoom : xRange);
203
+ const scale = scaleFn().domain([xMin, xMax]).range(range);
182
204
  if (!hasZoomX) {
183
- scale.nice();
205
+ // 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
206
+ scale.nice(Math.max(10, domainData.length));
184
207
  }
185
208
  return scale;
186
209
  }
@@ -195,40 +218,27 @@ export function createXScale(args) {
195
218
  });
196
219
  const xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]);
197
220
  if (xScale.step() / 2 < xAxisMaxPadding) {
198
- xScale.range(xRange);
221
+ xScale.range(range);
199
222
  }
200
223
  return xScale;
201
224
  }
202
225
  break;
203
226
  }
204
227
  case 'datetime': {
205
- if (xTimestamps) {
206
- const [xMinTimestamp, xMaxTimestamp] = extent(xTimestamps);
228
+ let domain = null;
229
+ const domainData = get(axis, 'timestamps') || getDomainDataXBySeries(series);
230
+ if (isNumericalArrayData(domainData)) {
231
+ const [xMinTimestamp, xMaxTimestamp] = extent(domainData);
207
232
  const xMin = typeof xMinProps === 'number' ? xMinProps : xMinTimestamp;
208
233
  const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
209
- const scale = scaleUtc()
210
- .domain([xMin, xMax])
211
- .range(hasZoomX ? xRangeZoom : xRange);
234
+ domain = [xMin, xMax];
235
+ const scale = scaleUtc().domain(domain).range(range);
212
236
  if (!hasZoomX) {
213
- scale.nice();
237
+ // 10 is the default value for the number of ticks. Here, to preserve the appearance of a series with a small number of points
238
+ scale.nice(Math.max(10, domainData.length));
214
239
  }
215
240
  return scale;
216
241
  }
217
- else {
218
- const domain = getDomainDataXBySeries(series);
219
- if (isNumericalArrayData(domain)) {
220
- const [xMinTimestamp, xMaxTimestamp] = extent(domain);
221
- const xMin = typeof xMinProps === 'number' ? xMinProps : xMinTimestamp;
222
- const xMax = typeof xMaxProps === 'number' ? xMaxProps : xMaxTimestamp;
223
- const scale = scaleUtc()
224
- .domain([xMin, xMax])
225
- .range(hasZoomX ? xRangeZoom : xRange);
226
- if (!hasZoomX) {
227
- scale.nice();
228
- }
229
- return scale;
230
- }
231
- }
232
242
  break;
233
243
  }
234
244
  }
@@ -242,7 +252,13 @@ const createScales = (args) => {
242
252
  visibleSeries = visibleSeries.length === 0 ? series : visibleSeries;
243
253
  return {
244
254
  xScale: xAxis
245
- ? createXScale({ axis: xAxis, boundsWidth, series: visibleSeries, hasZoomX })
255
+ ? createXScale({
256
+ axis: xAxis,
257
+ boundsWidth,
258
+ series: visibleSeries,
259
+ seriesOptions,
260
+ hasZoomX,
261
+ })
246
262
  : undefined,
247
263
  yScale: yAxis.map((axis, index) => {
248
264
  const axisSeries = series.filter((s) => {
@@ -1,7 +1,9 @@
1
1
  import type { ChartSeries, ChartXAxis } from '../../types';
2
+ import type { PreparedSeriesOptions } from '../useSeries/types';
2
3
  import type { PreparedAxis } from './types';
3
- export declare const getPreparedXAxis: ({ xAxis, seriesData, width, }: {
4
+ export declare const getPreparedXAxis: ({ xAxis, seriesData, seriesOptions, width, }: {
4
5
  xAxis?: ChartXAxis;
5
6
  seriesData: ChartSeries[];
7
+ seriesOptions: PreparedSeriesOptions;
6
8
  width: number;
7
9
  }) => Promise<PreparedAxis>;