@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.
- package/dist/cjs/components/Axis/AxisX.js +10 -8
- package/dist/cjs/components/Axis/AxisY.js +16 -36
- package/dist/cjs/components/ChartInner/index.js +15 -1
- package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +4 -2
- package/dist/cjs/components/ChartInner/useChartInnerProps.js +4 -1
- package/dist/cjs/components/Legend/index.d.ts +1 -0
- package/dist/cjs/components/Legend/index.js +113 -51
- package/dist/cjs/components/Legend/styles.css +13 -0
- package/dist/cjs/components/Tooltip/DefaultContent.js +6 -3
- package/dist/cjs/constants/chart-types.d.ts +12 -0
- package/dist/cjs/constants/chart-types.js +12 -0
- package/dist/cjs/constants/defaults/axis.d.ts +3 -1
- package/dist/cjs/constants/defaults/axis.js +10 -0
- package/dist/cjs/constants/index.d.ts +6 -47
- package/dist/cjs/constants/index.js +6 -72
- package/dist/cjs/constants/layout-algorithms.d.ts +7 -0
- package/dist/cjs/constants/layout-algorithms.js +8 -0
- package/dist/cjs/constants/line-styles.d.ts +20 -0
- package/dist/cjs/constants/line-styles.js +20 -0
- package/dist/cjs/constants/palette.d.ts +1 -0
- package/dist/cjs/constants/palette.js +22 -0
- package/dist/cjs/constants/symbol-types.d.ts +7 -0
- package/dist/cjs/constants/symbol-types.js +8 -0
- package/dist/cjs/constants/typography.d.ts +1 -0
- package/dist/cjs/constants/typography.js +1 -0
- package/dist/cjs/hooks/index.d.ts +1 -0
- package/dist/cjs/hooks/index.js +1 -0
- package/dist/cjs/hooks/useChartOptions/types.d.ts +4 -2
- package/dist/cjs/hooks/useChartOptions/x-axis.js +11 -2
- package/dist/cjs/hooks/useChartOptions/y-axis.js +14 -3
- package/dist/cjs/hooks/useCrosshair/index.d.ts +17 -0
- package/dist/cjs/hooks/useCrosshair/index.js +139 -0
- package/dist/cjs/hooks/useCrosshair/useCrosshairHover.d.ts +11 -0
- package/dist/cjs/hooks/useCrosshair/useCrosshairHover.js +18 -0
- package/dist/cjs/hooks/useSeries/index.d.ts +4 -2
- package/dist/cjs/hooks/useSeries/prepare-legend.d.ts +11 -9
- package/dist/cjs/hooks/useSeries/prepare-legend.js +78 -23
- package/dist/cjs/hooks/useSeries/prepare-line.d.ts +1 -2
- package/dist/cjs/hooks/useSeries/prepare-line.js +3 -3
- package/dist/cjs/hooks/useSeries/types.d.ts +7 -3
- package/dist/cjs/hooks/useShapes/waterfall/index.js +2 -2
- package/dist/cjs/types/chart/axis.d.ts +26 -21
- package/dist/cjs/types/chart/legend.d.ts +6 -0
- package/dist/cjs/types/chart/line.d.ts +1 -1
- package/dist/cjs/types/chart/series.d.ts +1 -1
- package/dist/cjs/utils/chart/axis.d.ts +1 -0
- package/dist/cjs/utils/chart/axis.js +8 -0
- package/dist/esm/components/Axis/AxisX.js +10 -8
- package/dist/esm/components/Axis/AxisY.js +16 -36
- package/dist/esm/components/ChartInner/index.js +15 -1
- package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +4 -2
- package/dist/esm/components/ChartInner/useChartInnerProps.js +4 -1
- package/dist/esm/components/Legend/index.d.ts +1 -0
- package/dist/esm/components/Legend/index.js +113 -51
- package/dist/esm/components/Legend/styles.css +13 -0
- package/dist/esm/components/Tooltip/DefaultContent.js +6 -3
- package/dist/esm/constants/chart-types.d.ts +12 -0
- package/dist/esm/constants/chart-types.js +12 -0
- package/dist/esm/constants/defaults/axis.d.ts +3 -1
- package/dist/esm/constants/defaults/axis.js +10 -0
- package/dist/esm/constants/index.d.ts +6 -47
- package/dist/esm/constants/index.js +6 -72
- package/dist/esm/constants/layout-algorithms.d.ts +7 -0
- package/dist/esm/constants/layout-algorithms.js +8 -0
- package/dist/esm/constants/line-styles.d.ts +20 -0
- package/dist/esm/constants/line-styles.js +20 -0
- package/dist/esm/constants/palette.d.ts +1 -0
- package/dist/esm/constants/palette.js +22 -0
- package/dist/esm/constants/symbol-types.d.ts +7 -0
- package/dist/esm/constants/symbol-types.js +8 -0
- package/dist/esm/constants/typography.d.ts +1 -0
- package/dist/esm/constants/typography.js +1 -0
- package/dist/esm/hooks/index.d.ts +1 -0
- package/dist/esm/hooks/index.js +1 -0
- package/dist/esm/hooks/useChartOptions/types.d.ts +4 -2
- package/dist/esm/hooks/useChartOptions/x-axis.js +11 -2
- package/dist/esm/hooks/useChartOptions/y-axis.js +14 -3
- package/dist/esm/hooks/useCrosshair/index.d.ts +17 -0
- package/dist/esm/hooks/useCrosshair/index.js +139 -0
- package/dist/esm/hooks/useCrosshair/useCrosshairHover.d.ts +11 -0
- package/dist/esm/hooks/useCrosshair/useCrosshairHover.js +18 -0
- package/dist/esm/hooks/useSeries/index.d.ts +4 -2
- package/dist/esm/hooks/useSeries/prepare-legend.d.ts +11 -9
- package/dist/esm/hooks/useSeries/prepare-legend.js +78 -23
- package/dist/esm/hooks/useSeries/prepare-line.d.ts +1 -2
- package/dist/esm/hooks/useSeries/prepare-line.js +3 -3
- package/dist/esm/hooks/useSeries/types.d.ts +7 -3
- package/dist/esm/hooks/useShapes/waterfall/index.js +2 -2
- package/dist/esm/types/chart/axis.d.ts +26 -21
- package/dist/esm/types/chart/legend.d.ts +6 -0
- package/dist/esm/types/chart/line.d.ts +1 -1
- package/dist/esm/types/chart/series.d.ts +1 -1
- package/dist/esm/utils/chart/axis.d.ts +1 -0
- package/dist/esm/utils/chart/axis.js +8 -0
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { DashStyle } from '
|
|
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,
|
|
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',
|
|
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,
|
|
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',
|
|
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
|
-
|
|
22
|
-
|
|
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
|
|
5
|
-
legend: ChartData[
|
|
6
|
-
series: ChartData[
|
|
7
|
-
})
|
|
8
|
-
export declare
|
|
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[
|
|
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
|
-
|
|
23
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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()
|
|
96
|
-
resultItem.
|
|
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
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
|
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 {
|
|
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 =
|
|
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 ===
|
|
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
|
-
|
|
55
|
-
|
|
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 {
|
|
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(
|
|
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;
|