@gravity-ui/charts 1.7.1 → 1.9.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 (95) hide show
  1. package/dist/cjs/components/Axis/AxisX.js +10 -8
  2. package/dist/cjs/components/Axis/AxisY.js +16 -36
  3. package/dist/cjs/components/ChartInner/index.js +15 -1
  4. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +4 -2
  5. package/dist/cjs/components/ChartInner/useChartInnerProps.js +4 -1
  6. package/dist/cjs/components/Legend/index.d.ts +1 -0
  7. package/dist/cjs/components/Legend/index.js +113 -51
  8. package/dist/cjs/components/Legend/styles.css +13 -0
  9. package/dist/cjs/components/Tooltip/DefaultContent.js +6 -3
  10. package/dist/cjs/constants/chart-types.d.ts +12 -0
  11. package/dist/cjs/constants/chart-types.js +12 -0
  12. package/dist/cjs/constants/defaults/axis.d.ts +3 -1
  13. package/dist/cjs/constants/defaults/axis.js +10 -0
  14. package/dist/cjs/constants/index.d.ts +6 -47
  15. package/dist/cjs/constants/index.js +6 -72
  16. package/dist/cjs/constants/layout-algorithms.d.ts +7 -0
  17. package/dist/cjs/constants/layout-algorithms.js +8 -0
  18. package/dist/cjs/constants/line-styles.d.ts +20 -0
  19. package/dist/cjs/constants/line-styles.js +20 -0
  20. package/dist/cjs/constants/palette.d.ts +1 -0
  21. package/dist/cjs/constants/palette.js +22 -0
  22. package/dist/cjs/constants/symbol-types.d.ts +7 -0
  23. package/dist/cjs/constants/symbol-types.js +8 -0
  24. package/dist/cjs/constants/typography.d.ts +1 -0
  25. package/dist/cjs/constants/typography.js +1 -0
  26. package/dist/cjs/hooks/index.d.ts +1 -0
  27. package/dist/cjs/hooks/index.js +1 -0
  28. package/dist/cjs/hooks/useChartOptions/types.d.ts +4 -2
  29. package/dist/cjs/hooks/useChartOptions/x-axis.js +11 -2
  30. package/dist/cjs/hooks/useChartOptions/y-axis.js +14 -3
  31. package/dist/cjs/hooks/useCrosshair/index.d.ts +17 -0
  32. package/dist/cjs/hooks/useCrosshair/index.js +139 -0
  33. package/dist/cjs/hooks/useCrosshair/useCrosshairHover.d.ts +11 -0
  34. package/dist/cjs/hooks/useCrosshair/useCrosshairHover.js +18 -0
  35. package/dist/cjs/hooks/useSeries/index.d.ts +4 -2
  36. package/dist/cjs/hooks/useSeries/prepare-legend.d.ts +11 -9
  37. package/dist/cjs/hooks/useSeries/prepare-legend.js +78 -23
  38. package/dist/cjs/hooks/useSeries/prepare-line.d.ts +1 -2
  39. package/dist/cjs/hooks/useSeries/prepare-line.js +3 -3
  40. package/dist/cjs/hooks/useSeries/types.d.ts +7 -3
  41. package/dist/cjs/hooks/useShapes/waterfall/index.js +2 -2
  42. package/dist/cjs/types/chart/axis.d.ts +26 -21
  43. package/dist/cjs/types/chart/legend.d.ts +6 -0
  44. package/dist/cjs/types/chart/line.d.ts +1 -1
  45. package/dist/cjs/types/chart/series.d.ts +1 -1
  46. package/dist/cjs/utils/chart/axis.d.ts +1 -0
  47. package/dist/cjs/utils/chart/axis.js +8 -0
  48. package/dist/esm/components/Axis/AxisX.js +10 -8
  49. package/dist/esm/components/Axis/AxisY.js +16 -36
  50. package/dist/esm/components/ChartInner/index.js +15 -1
  51. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +4 -2
  52. package/dist/esm/components/ChartInner/useChartInnerProps.js +4 -1
  53. package/dist/esm/components/Legend/index.d.ts +1 -0
  54. package/dist/esm/components/Legend/index.js +113 -51
  55. package/dist/esm/components/Legend/styles.css +13 -0
  56. package/dist/esm/components/Tooltip/DefaultContent.js +6 -3
  57. package/dist/esm/constants/chart-types.d.ts +12 -0
  58. package/dist/esm/constants/chart-types.js +12 -0
  59. package/dist/esm/constants/defaults/axis.d.ts +3 -1
  60. package/dist/esm/constants/defaults/axis.js +10 -0
  61. package/dist/esm/constants/index.d.ts +6 -47
  62. package/dist/esm/constants/index.js +6 -72
  63. package/dist/esm/constants/layout-algorithms.d.ts +7 -0
  64. package/dist/esm/constants/layout-algorithms.js +8 -0
  65. package/dist/esm/constants/line-styles.d.ts +20 -0
  66. package/dist/esm/constants/line-styles.js +20 -0
  67. package/dist/esm/constants/palette.d.ts +1 -0
  68. package/dist/esm/constants/palette.js +22 -0
  69. package/dist/esm/constants/symbol-types.d.ts +7 -0
  70. package/dist/esm/constants/symbol-types.js +8 -0
  71. package/dist/esm/constants/typography.d.ts +1 -0
  72. package/dist/esm/constants/typography.js +1 -0
  73. package/dist/esm/hooks/index.d.ts +1 -0
  74. package/dist/esm/hooks/index.js +1 -0
  75. package/dist/esm/hooks/useChartOptions/types.d.ts +4 -2
  76. package/dist/esm/hooks/useChartOptions/x-axis.js +11 -2
  77. package/dist/esm/hooks/useChartOptions/y-axis.js +14 -3
  78. package/dist/esm/hooks/useCrosshair/index.d.ts +17 -0
  79. package/dist/esm/hooks/useCrosshair/index.js +139 -0
  80. package/dist/esm/hooks/useCrosshair/useCrosshairHover.d.ts +11 -0
  81. package/dist/esm/hooks/useCrosshair/useCrosshairHover.js +18 -0
  82. package/dist/esm/hooks/useSeries/index.d.ts +4 -2
  83. package/dist/esm/hooks/useSeries/prepare-legend.d.ts +11 -9
  84. package/dist/esm/hooks/useSeries/prepare-legend.js +78 -23
  85. package/dist/esm/hooks/useSeries/prepare-line.d.ts +1 -2
  86. package/dist/esm/hooks/useSeries/prepare-line.js +3 -3
  87. package/dist/esm/hooks/useSeries/types.d.ts +7 -3
  88. package/dist/esm/hooks/useShapes/waterfall/index.js +2 -2
  89. package/dist/esm/types/chart/axis.d.ts +26 -21
  90. package/dist/esm/types/chart/legend.d.ts +6 -0
  91. package/dist/esm/types/chart/line.d.ts +1 -1
  92. package/dist/esm/types/chart/series.d.ts +1 -1
  93. package/dist/esm/utils/chart/axis.d.ts +1 -0
  94. package/dist/esm/utils/chart/axis.js +8 -0
  95. package/package.json +1 -1
@@ -1,5 +1,5 @@
1
- import type { DashStyle } from 'src/constants';
2
- import type { AxisPlotBand, BaseTextStyle, ChartAxis, ChartAxisLabels, ChartAxisTitleAlignment, ChartAxisType, ChartData, ChartMargin, PlotLayerPlacement } from '../../types';
1
+ import type { DashStyle } from '../../constants';
2
+ import type { AxisCrosshair, AxisPlotBand, BaseTextStyle, ChartAxis, ChartAxisLabels, ChartAxisTitleAlignment, ChartAxisType, ChartData, ChartMargin, PlotLayerPlacement } from '../../types';
3
3
  type PreparedAxisLabels = Omit<ChartAxisLabels, 'enabled' | 'padding' | 'style' | 'autoRotation'> & Required<Pick<ChartAxisLabels, 'enabled' | 'padding' | 'margin' | 'rotation'>> & {
4
4
  style: BaseTextStyle;
5
5
  rotation: number;
@@ -12,6 +12,7 @@ export type PreparedChart = {
12
12
  margin: ChartMargin;
13
13
  };
14
14
  export type PreparedAxisPlotBand = Required<AxisPlotBand>;
15
+ export type PreparedAxisCrosshair = Required<AxisCrosshair>;
15
16
  export type PreparedAxisPlotLine = {
16
17
  value: number;
17
18
  color: string;
@@ -44,6 +45,7 @@ export type PreparedAxis = Omit<ChartAxis, 'type' | 'labels' | 'plotLines' | 'pl
44
45
  plotIndex: number;
45
46
  plotLines: PreparedAxisPlotLine[];
46
47
  plotBands: PreparedAxisPlotBand[];
48
+ crosshair: PreparedAxisCrosshair;
47
49
  };
48
50
  export type PreparedTitle = ChartData['title'] & {
49
51
  height: number;
@@ -1,5 +1,5 @@
1
1
  import get from 'lodash/get';
2
- import { DEFAULT_AXIS_LABEL_FONT_SIZE, DashStyle, axisLabelsDefaults, xAxisTitleDefaults, } from '../../constants';
2
+ import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, axisCrosshairDefaults, axisLabelsDefaults, xAxisTitleDefaults, } from '../../constants';
3
3
  import { CHART_SERIES_WITH_VOLUME_ON_X_AXIS, calculateCos, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, getXAxisItems, hasOverlappingLabels, wrapText, } from '../../utils';
4
4
  import { createXScale } from '../useAxisScales';
5
5
  function getLabelSettings({ axis, series, width, autoRotation = true, }) {
@@ -106,7 +106,7 @@ export const getPreparedXAxis = ({ xAxis, series, width, }) => {
106
106
  value: get(d, 'value', 0),
107
107
  color: get(d, 'color', 'var(--g-color-base-brand)'),
108
108
  width: get(d, 'width', 1),
109
- dashStyle: get(d, 'dashStyle', DashStyle.Solid),
109
+ dashStyle: get(d, 'dashStyle', DASH_STYLE.Solid),
110
110
  opacity: get(d, 'opacity', 1),
111
111
  layerPlacement: get(d, 'layerPlacement', 'before'),
112
112
  })),
@@ -117,6 +117,15 @@ export const getPreparedXAxis = ({ xAxis, series, width, }) => {
117
117
  to: get(d, 'to', 0),
118
118
  layerPlacement: get(d, 'layerPlacement', 'before'),
119
119
  })),
120
+ crosshair: {
121
+ enabled: get(xAxis, 'crosshair.enabled', axisCrosshairDefaults.enabled),
122
+ color: get(xAxis, 'crosshair.color', axisCrosshairDefaults.color),
123
+ layerPlacement: get(xAxis, 'crosshair.layerPlacement', axisCrosshairDefaults.layerPlacement),
124
+ snap: get(xAxis, 'crosshair.snap', axisCrosshairDefaults.snap),
125
+ dashStyle: get(xAxis, 'crosshair.dashStyle', axisCrosshairDefaults.dashStyle),
126
+ width: get(xAxis, 'crosshair.width', axisCrosshairDefaults.width),
127
+ opacity: get(xAxis, 'crosshair.opacity', axisCrosshairDefaults.opacity),
128
+ },
120
129
  visible: get(xAxis, 'visible', true),
121
130
  };
122
131
  const { height, rotation } = getLabelSettings({
@@ -1,5 +1,5 @@
1
1
  import get from 'lodash/get';
2
- import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, DashStyle, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
2
+ import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, axisCrosshairDefaults, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
3
3
  import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, isAxisRelatedSeries, wrapText, } from '../../utils';
4
4
  import { createYScale } from '../useAxisScales';
5
5
  const getAxisLabelMaxWidth = (args) => {
@@ -50,6 +50,7 @@ export const getPreparedYAxis = ({ series, yAxis, height, }) => {
50
50
  return [];
51
51
  }
52
52
  return axisItems.map((axisItem) => {
53
+ var _a;
53
54
  const plotIndex = get(axisItem, 'plotIndex', 0);
54
55
  const firstPlotAxis = !axisByPlot[plotIndex];
55
56
  if (firstPlotAxis) {
@@ -105,7 +106,8 @@ export const getPreparedYAxis = ({ series, yAxis, height, }) => {
105
106
  min: getAxisMin(axisItem, series),
106
107
  maxPadding: get(axisItem, 'maxPadding', 0.05),
107
108
  grid: {
108
- enabled: get(axisItem, 'grid.enabled', firstPlotAxis),
109
+ enabled: get(axisItem, 'grid.enabled', firstPlotAxis ||
110
+ (!firstPlotAxis && !((_a = axisByPlot[plotIndex][0].visible) !== null && _a !== void 0 ? _a : true))),
109
111
  },
110
112
  ticks: {
111
113
  pixelInterval: get(axisItem, 'ticks.pixelInterval'),
@@ -116,7 +118,7 @@ export const getPreparedYAxis = ({ series, yAxis, height, }) => {
116
118
  value: get(d, 'value', 0),
117
119
  color: get(d, 'color', 'var(--g-color-base-brand)'),
118
120
  width: get(d, 'width', 1),
119
- dashStyle: get(d, 'dashStyle', DashStyle.Solid),
121
+ dashStyle: get(d, 'dashStyle', DASH_STYLE.Solid),
120
122
  opacity: get(d, 'opacity', 1),
121
123
  layerPlacement: get(d, 'layerPlacement', 'before'),
122
124
  })),
@@ -127,6 +129,15 @@ export const getPreparedYAxis = ({ series, yAxis, height, }) => {
127
129
  to: get(d, 'to', 0),
128
130
  layerPlacement: get(d, 'layerPlacement', 'before'),
129
131
  })),
132
+ crosshair: {
133
+ enabled: get(axisItem, 'crosshair.enabled', axisCrosshairDefaults.enabled),
134
+ color: get(axisItem, 'crosshair.color', axisCrosshairDefaults.color),
135
+ layerPlacement: get(axisItem, 'crosshair.layerPlacement', axisCrosshairDefaults.layerPlacement),
136
+ snap: get(axisItem, 'crosshair.snap', axisCrosshairDefaults.snap),
137
+ dashStyle: get(axisItem, 'crosshair.dashStyle', axisCrosshairDefaults.dashStyle),
138
+ width: get(axisItem, 'crosshair.width', axisCrosshairDefaults.width),
139
+ opacity: get(axisItem, 'crosshair.opacity', axisCrosshairDefaults.opacity),
140
+ },
130
141
  visible: get(axisItem, 'visible', true),
131
142
  };
132
143
  if (labelsEnabled) {
@@ -0,0 +1,17 @@
1
+ import type { Dispatch } from 'd3';
2
+ import type { ChartScale, PreparedAxis, PreparedSplit } from '../../hooks';
3
+ type Props = {
4
+ xAxis: PreparedAxis;
5
+ yAxes: PreparedAxis[];
6
+ width: number;
7
+ height: number;
8
+ xScale?: ChartScale;
9
+ yScale?: ChartScale[];
10
+ split: PreparedSplit;
11
+ plotElement: SVGGElement | null;
12
+ dispatcher: Dispatch<object>;
13
+ boundsOffsetLeft: number;
14
+ boundsOffsetTop: number;
15
+ };
16
+ export declare const useCrosshair: (props: Props) => void;
17
+ export {};
@@ -0,0 +1,139 @@
1
+ import React from 'react';
2
+ import { line, select } from 'd3';
3
+ import { getAxisPlotsPosition, getLineDashArray } from '../../utils';
4
+ import { useCrosshairHover } from './useCrosshairHover';
5
+ export const useCrosshair = (props) => {
6
+ var _a, _b;
7
+ const { xScale, plotElement, yScale, dispatcher, xAxis, yAxes, split, width, height: totalHeight, boundsOffsetTop, boundsOffsetLeft, } = props;
8
+ const crosshairEnabled = xAxis.crosshair.enabled || yAxes.some((axis) => axis.crosshair.enabled);
9
+ const { hovered, pointerPosition } = useCrosshairHover({ dispatcher, enabled: crosshairEnabled });
10
+ const pointerXPos = (_a = pointerPosition === null || pointerPosition === void 0 ? void 0 : pointerPosition[0]) !== null && _a !== void 0 ? _a : 0;
11
+ const pointerYPos = (_b = pointerPosition === null || pointerPosition === void 0 ? void 0 : pointerPosition[1]) !== null && _b !== void 0 ? _b : 0;
12
+ React.useEffect(() => {
13
+ if (!plotElement || !xScale || !(yScale === null || yScale === void 0 ? void 0 : yScale.length) || !crosshairEnabled) {
14
+ return;
15
+ }
16
+ const plotCrosshairDataAttr = 'data-crosshair';
17
+ const svgElement = select(plotElement);
18
+ svgElement.selectAll(`[${plotCrosshairDataAttr}]`).remove();
19
+ const lineGenerator = line();
20
+ if (xAxis.crosshair.enabled && (hovered === null || hovered === void 0 ? void 0 : hovered.length)) {
21
+ const xAxisScale = xScale;
22
+ const crosshairDataAttr = 'data-crosshair-x-line';
23
+ const crosshairSelection = svgElement
24
+ .selectAll(`[${crosshairDataAttr}]`)
25
+ .data(hovered)
26
+ .join('g')
27
+ .attr(plotCrosshairDataAttr, 1)
28
+ .attr(crosshairDataAttr, 1);
29
+ crosshairSelection
30
+ .append('path')
31
+ .attr('d', (hoveredElement) => {
32
+ var _a, _b, _c;
33
+ let lineValue = 0;
34
+ if (xAxis.crosshair.snap) {
35
+ const offset = ((_b = (_a = xAxisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(xAxisScale)) !== null && _b !== void 0 ? _b : 0) / 2;
36
+ if (typeof hoveredElement.data === 'object' && 'x' in hoveredElement.data) {
37
+ lineValue = Number(xAxisScale((_c = hoveredElement.data.x) !== null && _c !== void 0 ? _c : 0)) + offset;
38
+ }
39
+ }
40
+ else {
41
+ lineValue = pointerXPos - boundsOffsetLeft;
42
+ }
43
+ const points = [
44
+ [lineValue, 0],
45
+ [lineValue, totalHeight],
46
+ ];
47
+ return lineGenerator(points);
48
+ })
49
+ .attr('stroke', xAxis.crosshair.color)
50
+ .attr('stroke-width', xAxis.crosshair.width)
51
+ .attr('stroke-dasharray', getLineDashArray(xAxis.crosshair.dashStyle, xAxis.crosshair.width))
52
+ .attr('opacity', xAxis.crosshair.opacity);
53
+ // set layer placement
54
+ crosshairSelection.each((_, i, nodes) => {
55
+ const crossSelection = select(nodes[i]);
56
+ if (xAxis.crosshair.layerPlacement === 'before') {
57
+ crossSelection.lower();
58
+ }
59
+ else {
60
+ crossSelection.raise();
61
+ }
62
+ });
63
+ }
64
+ yAxes.forEach((yAxis, index, currentArr) => {
65
+ const yAxisScale = yScale[index];
66
+ if (index !== 0 && !yAxis.crosshair.snap && !currentArr[0].crosshair.snap) {
67
+ return;
68
+ }
69
+ if (yAxis.crosshair.enabled && (hovered === null || hovered === void 0 ? void 0 : hovered.length)) {
70
+ const crosshairDataAttr = `data-crosshair-y-line-${index}`;
71
+ const crosshairSelection = svgElement
72
+ .selectAll(`[${crosshairDataAttr}]`)
73
+ .data(hovered.filter((node) => {
74
+ var _a;
75
+ const n = node;
76
+ const yAxisIndex = (_a = n.series.yAxis) !== null && _a !== void 0 ? _a : 0;
77
+ return yAxis.crosshair.snap
78
+ ? yAxisIndex === index && node.closest
79
+ : true;
80
+ }))
81
+ .join('g')
82
+ .attr(plotCrosshairDataAttr, 1)
83
+ .attr(crosshairDataAttr, 1)
84
+ .style('transform', yAxis.crosshair.snap
85
+ ? getAxisPlotsPosition(yAxis, split)
86
+ : 'translate(0, 0)');
87
+ crosshairSelection
88
+ .append('path')
89
+ .attr('d', (hoveredElement) => {
90
+ var _a, _b, _c;
91
+ let lineValue = 0;
92
+ if (yAxis.crosshair.snap) {
93
+ const offset = ((_b = (_a = yAxisScale.bandwidth) === null || _a === void 0 ? void 0 : _a.call(yAxisScale)) !== null && _b !== void 0 ? _b : 0) / 2;
94
+ if (typeof hoveredElement.data === 'object' &&
95
+ 'y' in hoveredElement.data) {
96
+ lineValue = Number(yAxisScale((_c = hoveredElement.data.y) !== null && _c !== void 0 ? _c : 0)) + offset;
97
+ }
98
+ }
99
+ else {
100
+ lineValue = pointerYPos - boundsOffsetTop;
101
+ }
102
+ const points = [
103
+ [0, lineValue],
104
+ [width, lineValue],
105
+ ];
106
+ return lineGenerator(points);
107
+ })
108
+ .attr('stroke', yAxis.crosshair.color)
109
+ .attr('stroke-width', yAxis.crosshair.width)
110
+ .attr('stroke-dasharray', getLineDashArray(yAxis.crosshair.dashStyle, yAxis.crosshair.width))
111
+ .attr('opacity', yAxis.crosshair.opacity);
112
+ crosshairSelection.each((_, i, nodes) => {
113
+ const plotLineSelection = select(nodes[i]);
114
+ if (yAxis.crosshair.layerPlacement === 'before') {
115
+ plotLineSelection.lower();
116
+ }
117
+ else {
118
+ plotLineSelection.raise();
119
+ }
120
+ });
121
+ }
122
+ });
123
+ }, [
124
+ plotElement,
125
+ hovered,
126
+ crosshairEnabled,
127
+ xAxis,
128
+ yAxes,
129
+ xScale,
130
+ totalHeight,
131
+ pointerXPos,
132
+ boundsOffsetLeft,
133
+ yScale,
134
+ split,
135
+ width,
136
+ pointerYPos,
137
+ boundsOffsetTop,
138
+ ]);
139
+ };
@@ -0,0 +1,11 @@
1
+ import type { Dispatch } from 'd3';
2
+ import type { PointPosition, TooltipDataChunk } from '../../types';
3
+ type Args = {
4
+ dispatcher: Dispatch<object>;
5
+ enabled: boolean;
6
+ };
7
+ export declare const useCrosshairHover: ({ dispatcher, enabled }: Args) => {
8
+ hovered: TooltipDataChunk[] | undefined;
9
+ pointerPosition: PointPosition | undefined;
10
+ };
11
+ export {};
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ import { EventType } from '../../utils';
3
+ export const useCrosshairHover = ({ dispatcher, enabled }) => {
4
+ const [{ hovered, pointerPosition }, setCrosshairState] = React.useState({});
5
+ React.useEffect(() => {
6
+ if (enabled) {
7
+ dispatcher.on(`${EventType.HOVER_SHAPE}.crosshair`, (nextHovered, nextPointerPosition) => {
8
+ setCrosshairState({ hovered: nextHovered, pointerPosition: nextPointerPosition });
9
+ });
10
+ }
11
+ return () => {
12
+ if (enabled) {
13
+ dispatcher.on(`${EventType.HOVER_SHAPE}.crosshair`, null);
14
+ }
15
+ };
16
+ }, [dispatcher, enabled]);
17
+ return { hovered, pointerPosition };
18
+ };
@@ -18,8 +18,10 @@ export declare const useSeries: (args: Args) => {
18
18
  top: number;
19
19
  };
20
20
  pagination: {
21
- limit: number;
22
- maxPage: number;
21
+ pages: {
22
+ start: number;
23
+ end: number;
24
+ }[];
23
25
  } | undefined;
24
26
  };
25
27
  preparedLegend: import("./types").PreparedLegend;
@@ -1,26 +1,28 @@
1
1
  import type { ChartData } from '../../types';
2
2
  import type { PreparedAxis, PreparedChart } from '../useChartOptions/types';
3
3
  import type { LegendItem, PreparedLegend, PreparedSeries } from './types';
4
- export declare const getPreparedLegend: (args: {
5
- legend: ChartData["legend"];
6
- series: ChartData["series"]["data"];
7
- }) => PreparedLegend;
8
- export declare const getLegendComponents: (args: {
4
+ export declare function getPreparedLegend(args: {
5
+ legend: ChartData['legend'];
6
+ series: ChartData['series']['data'];
7
+ }): PreparedLegend;
8
+ export declare function getLegendComponents(args: {
9
9
  chartWidth: number;
10
10
  chartHeight: number;
11
- chartMargin: PreparedChart["margin"];
11
+ chartMargin: PreparedChart['margin'];
12
12
  series: PreparedSeries[];
13
13
  preparedLegend: PreparedLegend;
14
14
  preparedYAxis: PreparedAxis[];
15
- }) => {
15
+ }): {
16
16
  legendConfig: {
17
17
  offset: {
18
18
  left: number;
19
19
  top: number;
20
20
  };
21
21
  pagination: {
22
- limit: number;
23
- maxPage: number;
22
+ pages: {
23
+ start: number;
24
+ end: number;
25
+ }[];
24
26
  } | undefined;
25
27
  };
26
28
  legendItems: LegendItem[][];
@@ -6,7 +6,7 @@ import { CONTINUOUS_LEGEND_SIZE, legendDefaults } from '../../constants';
6
6
  import { getDefaultColorStops, getDomainForContinuousColorScale, getHorisontalSvgTextHeight, getLabelsSize, } from '../../utils';
7
7
  import { getBoundsWidth } from '../useChartDimensions';
8
8
  import { getYAxisWidth } from '../useChartDimensions/utils';
9
- export const getPreparedLegend = (args) => {
9
+ export function getPreparedLegend(args) {
10
10
  var _a, _b, _c, _d, _e, _f, _g;
11
11
  const { legend, series } = args;
12
12
  const enabled = Boolean(typeof (legend === null || legend === void 0 ? void 0 : legend.enabled) === 'boolean' ? legend === null || legend === void 0 ? void 0 : legend.enabled : series.length > 1);
@@ -69,40 +69,65 @@ export const getPreparedLegend = (args) => {
69
69
  width: legendWidth,
70
70
  ticks,
71
71
  colorScale,
72
+ html: get(legend, 'html', false),
72
73
  };
73
- };
74
- const getFlattenLegendItems = (series) => {
74
+ }
75
+ function getFlattenLegendItems(series, preparedLegend) {
75
76
  return series.reduce((acc, s) => {
76
77
  const legendEnabled = get(s, 'legend.enabled', true);
77
78
  if (legendEnabled) {
78
- acc.push(Object.assign(Object.assign({}, s), { symbol: s.legend.symbol }));
79
+ acc.push(Object.assign(Object.assign({}, s), { height: preparedLegend.lineHeight, symbol: s.legend.symbol }));
79
80
  }
80
81
  return acc;
81
82
  }, []);
82
- };
83
- const getGroupedLegendItems = (args) => {
83
+ }
84
+ function getGroupedLegendItems(args) {
84
85
  const { maxLegendWidth, items, preparedLegend } = args;
85
86
  const result = [[]];
87
+ const bodySelection = select(document.body);
86
88
  let textWidthsInLine = [0];
87
89
  let lineIndex = 0;
88
90
  items.forEach((item) => {
89
- select(document.body)
90
- .append('text')
91
- .text(item.name)
91
+ const itemSelection = preparedLegend.html
92
+ ? bodySelection
93
+ .append('div')
94
+ .html(item.name)
95
+ .style('position', 'absolute')
96
+ .style('display', 'inline-block')
97
+ .style('white-space', 'nowrap')
98
+ : bodySelection.append('text').text(item.name).style('white-space', 'nowrap');
99
+ itemSelection
92
100
  .style('font-size', preparedLegend.itemStyle.fontSize)
93
101
  .each(function () {
94
102
  const resultItem = clone(item);
95
- const textWidth = this.getBoundingClientRect().width;
96
- resultItem.textWidth = textWidth;
103
+ const { height, width: textWidth } = this.getBoundingClientRect();
104
+ resultItem.height = height;
105
+ if (textWidth >
106
+ maxLegendWidth - resultItem.symbol.width - resultItem.symbol.padding) {
107
+ resultItem.overflowed = true;
108
+ resultItem.textWidth =
109
+ maxLegendWidth - resultItem.symbol.width - resultItem.symbol.padding;
110
+ }
111
+ else {
112
+ resultItem.textWidth = textWidth;
113
+ }
97
114
  textWidthsInLine.push(textWidth);
98
115
  const textsWidth = textWidthsInLine.reduce((acc, width) => acc + width, 0);
116
+ if (!result[lineIndex]) {
117
+ result[lineIndex] = [];
118
+ }
99
119
  result[lineIndex].push(resultItem);
100
120
  const symbolsWidth = result[lineIndex].reduce((acc, { symbol }) => {
101
121
  return acc + symbol.width + symbol.padding;
102
122
  }, 0);
103
123
  const distancesWidth = (result[lineIndex].length - 1) * preparedLegend.itemDistance;
104
- const isOverfilled = maxLegendWidth < textsWidth + symbolsWidth + distancesWidth;
105
- if (isOverfilled) {
124
+ const isOverflowedAsOnlyItemInLine = resultItem.overflowed && result[lineIndex].length === 1;
125
+ const isCurrentLineOverMaxWidth = maxLegendWidth < textsWidth + symbolsWidth + distancesWidth;
126
+ if (isOverflowedAsOnlyItemInLine) {
127
+ lineIndex += 1;
128
+ textWidthsInLine = [];
129
+ }
130
+ else if (isCurrentLineOverMaxWidth) {
106
131
  result[lineIndex].pop();
107
132
  lineIndex += 1;
108
133
  textWidthsInLine = [textWidth];
@@ -114,12 +139,36 @@ const getGroupedLegendItems = (args) => {
114
139
  .remove();
115
140
  });
116
141
  return result;
117
- };
118
- export const getLegendComponents = (args) => {
142
+ }
143
+ function getPagination(args) {
144
+ const { items, maxLegendHeight, paginatorHeight } = args;
145
+ const pages = [];
146
+ let currentPageIndex = 0;
147
+ let currentHeight = 0;
148
+ items.forEach((item, i) => {
149
+ if (!pages[currentPageIndex]) {
150
+ pages[currentPageIndex] = { start: i, end: i };
151
+ }
152
+ const legendLineHeight = Math.max(...item.map((item) => item.height));
153
+ currentHeight += legendLineHeight;
154
+ if (currentHeight > maxLegendHeight - paginatorHeight) {
155
+ pages[currentPageIndex].end = i;
156
+ currentPageIndex += 1;
157
+ currentHeight = legendLineHeight;
158
+ // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice#end
159
+ pages[currentPageIndex] = { start: i, end: i + (i === items.length - 1 ? 1 : 0) };
160
+ }
161
+ else if (i === items.length - 1) {
162
+ pages[currentPageIndex].end = i + 1;
163
+ }
164
+ });
165
+ return { pages };
166
+ }
167
+ export function getLegendComponents(args) {
119
168
  const { chartWidth, chartHeight, chartMargin, series, preparedLegend, preparedYAxis } = args;
120
169
  const maxLegendWidth = getBoundsWidth({ chartWidth, chartMargin, preparedYAxis });
121
170
  const maxLegendHeight = (chartHeight - chartMargin.top - chartMargin.bottom - preparedLegend.margin) / 2;
122
- const flattenLegendItems = getFlattenLegendItems(series);
171
+ const flattenLegendItems = getFlattenLegendItems(series, preparedLegend);
123
172
  const items = getGroupedLegendItems({
124
173
  maxLegendWidth,
125
174
  items: flattenLegendItems,
@@ -127,13 +176,19 @@ export const getLegendComponents = (args) => {
127
176
  });
128
177
  let pagination;
129
178
  if (preparedLegend.type === 'discrete') {
130
- let legendHeight = preparedLegend.lineHeight * items.length;
179
+ const lineHeights = items.reduce((acc, item) => {
180
+ acc.push(Math.max(...item.map((item) => item.height)));
181
+ return acc;
182
+ }, []);
183
+ let legendHeight = lineHeights.reduce((acc, height) => acc + height, 0);
131
184
  if (maxLegendHeight < legendHeight) {
132
- // extra line for paginator
133
- const limit = Math.floor(maxLegendHeight / preparedLegend.lineHeight) - 1;
134
- const maxPage = Math.ceil(items.length / limit);
135
- pagination = { limit, maxPage };
136
- legendHeight = preparedLegend.lineHeight * (limit + 1);
185
+ const lines = Math.floor(maxLegendHeight / preparedLegend.lineHeight);
186
+ legendHeight = preparedLegend.lineHeight * lines;
187
+ pagination = getPagination({
188
+ items,
189
+ maxLegendHeight: legendHeight,
190
+ paginatorHeight: preparedLegend.lineHeight,
191
+ });
137
192
  }
138
193
  preparedLegend.height = legendHeight;
139
194
  }
@@ -143,4 +198,4 @@ export const getLegendComponents = (args) => {
143
198
  top,
144
199
  };
145
200
  return { legendConfig: { offset, pagination }, legendItems: items };
146
- };
201
+ }
@@ -1,10 +1,9 @@
1
1
  import type { ScaleOrdinal } from 'd3';
2
- import { DashStyle } from '../../constants';
3
2
  import type { ChartSeriesOptions, LineSeries } from '../../types';
4
3
  import type { PreparedLegend, PreparedLineSeries } from './types';
5
4
  export declare const DEFAULT_LEGEND_SYMBOL_SIZE = 16;
6
5
  export declare const DEFAULT_LINE_WIDTH = 1;
7
- export declare const DEFAULT_DASH_STYLE = DashStyle.Solid;
6
+ export declare const DEFAULT_DASH_STYLE: "Solid";
8
7
  export declare const DEFAULT_MARKER: {
9
8
  enabled: boolean;
10
9
  symbol: `${import("../../constants").SymbolType}`;
@@ -1,14 +1,14 @@
1
1
  import get from 'lodash/get';
2
2
  import merge from 'lodash/merge';
3
- import { DEFAULT_DATALABELS_STYLE, DashStyle, LineCap } from '../../constants';
3
+ import { DASH_STYLE, DEFAULT_DATALABELS_STYLE, LineCap } from '../../constants';
4
4
  import { getUniqId } from '../../utils';
5
5
  import { DEFAULT_DATALABELS_PADDING, DEFAULT_HALO_OPTIONS, DEFAULT_LEGEND_SYMBOL_PADDING, DEFAULT_POINT_MARKER_OPTIONS, } from './constants';
6
6
  export const DEFAULT_LEGEND_SYMBOL_SIZE = 16;
7
7
  export const DEFAULT_LINE_WIDTH = 1;
8
- export const DEFAULT_DASH_STYLE = DashStyle.Solid;
8
+ export const DEFAULT_DASH_STYLE = DASH_STYLE.Solid;
9
9
  export const DEFAULT_MARKER = Object.assign(Object.assign({}, DEFAULT_POINT_MARKER_OPTIONS), { enabled: false });
10
10
  function prepareLinecap(dashStyle, series, seriesOptions) {
11
- const defaultLineCap = dashStyle === DashStyle.Solid ? LineCap.Round : LineCap.None;
11
+ const defaultLineCap = dashStyle === DASH_STYLE.Solid ? LineCap.Round : LineCap.None;
12
12
  const lineCapFromSeriesOptions = get(seriesOptions, 'line.linecap', defaultLineCap);
13
13
  return get(series, 'linecap', lineCapFromSeriesOptions);
14
14
  }
@@ -39,11 +39,13 @@ export type OnLegendItemClick = (data: {
39
39
  }) => void;
40
40
  export type LegendItem = {
41
41
  color: string;
42
+ height: number;
42
43
  name: string;
43
44
  symbol: PreparedLegendSymbol;
44
45
  textWidth: number;
45
- visible?: boolean;
46
46
  dashStyle?: DashStyle;
47
+ overflowed?: boolean;
48
+ visible?: boolean;
47
49
  };
48
50
  export type LegendConfig = {
49
51
  offset: {
@@ -51,8 +53,10 @@ export type LegendConfig = {
51
53
  top: number;
52
54
  };
53
55
  pagination?: {
54
- limit: number;
55
- maxPage: number;
56
+ pages: {
57
+ start: number;
58
+ end: number;
59
+ }[];
56
60
  };
57
61
  };
58
62
  export type PreparedHaloOptions = {
@@ -1,7 +1,7 @@
1
1
  import React from 'react';
2
2
  import { color, line as lineGenerator, select } from 'd3';
3
3
  import get from 'lodash/get';
4
- import { DashStyle } from '../../../constants';
4
+ import { DASH_STYLE } from '../../../constants';
5
5
  import { block, filterOverlappingLabels, getLineDashArray, getWaterfallPointColor, } from '../../../utils';
6
6
  import { HtmlLayer } from '../HtmlLayer';
7
7
  export { prepareWaterfallData } from './prepare-data';
@@ -77,7 +77,7 @@ export const WaterfallSeriesShapes = (args) => {
77
77
  return line(points);
78
78
  })
79
79
  .attr('stroke-width', 1)
80
- .attr('stroke-dasharray', () => getLineDashArray(DashStyle.Dash, 1));
80
+ .attr('stroke-dasharray', () => getLineDashArray(DASH_STYLE.Dash, 1));
81
81
  function handleShapeHover(data) {
82
82
  hoveredDataRef.current = data;
83
83
  const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;