@gravity-ui/charts 1.49.1 → 1.50.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/AxisY/types.d.ts +2 -1
- package/dist/cjs/components/ChartInner/utils/zoom.js +1 -1
- package/dist/cjs/core/series/prepare-area.js +1 -0
- package/dist/cjs/core/series/prepare-bar-x.js +1 -0
- package/dist/cjs/core/series/prepare-bar-y.d.ts +1 -0
- package/dist/cjs/core/series/prepare-bar-y.js +1 -0
- package/dist/cjs/core/series/types.d.ts +3 -0
- package/dist/cjs/core/shapes/bar-y/prepare-data.js +11 -9
- package/dist/cjs/core/shapes/funnel/prepare-data.js +39 -18
- package/dist/cjs/core/shapes/funnel/types.d.ts +2 -1
- package/dist/cjs/core/shapes/utils.d.ts +6 -0
- package/dist/cjs/core/shapes/utils.js +9 -0
- package/dist/cjs/core/types/chart/funnel.d.ts +1 -1
- package/dist/cjs/core/types/chart/series.d.ts +2 -2
- package/dist/cjs/core/types/chart/tooltip.d.ts +3 -2
- package/dist/cjs/core/types/chart/zoom.d.ts +1 -1
- package/dist/cjs/core/types/css.d.ts +2 -0
- package/dist/cjs/core/types/css.js +1 -0
- package/dist/cjs/core/types/renderer.d.ts +15 -0
- package/dist/cjs/core/types/renderer.js +1 -0
- package/dist/cjs/core/utils/text.d.ts +2 -1
- package/dist/cjs/core/zoom/zoom.js +10 -2
- package/dist/cjs/hooks/useShapes/funnel/index.js +5 -2
- package/dist/cjs/hooks/useShapes/utils.d.ts +0 -7
- package/dist/cjs/hooks/useShapes/utils.js +0 -11
- package/dist/cjs/types/chart-ui.d.ts +2 -1
- package/dist/esm/components/AxisY/types.d.ts +2 -1
- package/dist/esm/components/ChartInner/utils/zoom.js +1 -1
- package/dist/esm/core/series/prepare-area.js +1 -0
- package/dist/esm/core/series/prepare-bar-x.js +1 -0
- package/dist/esm/core/series/prepare-bar-y.d.ts +1 -0
- package/dist/esm/core/series/prepare-bar-y.js +1 -0
- package/dist/esm/core/series/types.d.ts +3 -0
- package/dist/esm/core/shapes/bar-y/prepare-data.js +11 -9
- package/dist/esm/core/shapes/funnel/prepare-data.js +39 -18
- package/dist/esm/core/shapes/funnel/types.d.ts +2 -1
- package/dist/esm/core/shapes/utils.d.ts +6 -0
- package/dist/esm/core/shapes/utils.js +9 -0
- package/dist/esm/core/types/chart/funnel.d.ts +1 -1
- package/dist/esm/core/types/chart/series.d.ts +2 -2
- package/dist/esm/core/types/chart/tooltip.d.ts +3 -2
- package/dist/esm/core/types/chart/zoom.d.ts +1 -1
- package/dist/esm/core/types/css.d.ts +2 -0
- package/dist/esm/core/types/css.js +1 -0
- package/dist/esm/core/types/renderer.d.ts +15 -0
- package/dist/esm/core/types/renderer.js +1 -0
- package/dist/esm/core/utils/text.d.ts +2 -1
- package/dist/esm/core/zoom/zoom.js +10 -2
- package/dist/esm/hooks/useShapes/funnel/index.js +5 -2
- package/dist/esm/hooks/useShapes/utils.d.ts +0 -7
- package/dist/esm/hooks/useShapes/utils.js +0 -11
- package/dist/esm/types/chart-ui.d.ts +2 -1
- package/package.json +2 -1
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DashStyle } from 'src/core/constants';
|
|
2
2
|
import type { AxisPlotShape } from '../../core/types/chart/axis';
|
|
3
|
+
import type { CSSProperties } from '../../core/types/css';
|
|
3
4
|
import type { BaseTextStyle, HtmlItem, PlotLayerPlacement, PointPosition } from '../../types';
|
|
4
5
|
import type { TextRowData } from '../types';
|
|
5
6
|
export type AxisSvgLabelData = {
|
|
@@ -42,7 +43,7 @@ export type SvgAxisTitleData = {
|
|
|
42
43
|
export type HtmlAxisTitleData = {
|
|
43
44
|
html: true;
|
|
44
45
|
content: string;
|
|
45
|
-
style: BaseTextStyle &
|
|
46
|
+
style: BaseTextStyle & CSSProperties;
|
|
46
47
|
size: {
|
|
47
48
|
width: number;
|
|
48
49
|
height: number;
|
|
@@ -63,6 +63,7 @@ export function prepareArea(args) {
|
|
|
63
63
|
data: prepareSeriesData(series),
|
|
64
64
|
stacking: series.stacking,
|
|
65
65
|
stackId: getSeriesStackId(series),
|
|
66
|
+
valueAxis: 'y',
|
|
66
67
|
dataLabels: {
|
|
67
68
|
enabled: ((_e = series.dataLabels) === null || _e === void 0 ? void 0 : _e.enabled) || false,
|
|
68
69
|
style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_f = series.dataLabels) === null || _f === void 0 ? void 0 : _f.style),
|
|
@@ -37,6 +37,7 @@ export function prepareBarXSeries(args) {
|
|
|
37
37
|
data: prepareSeriesData(series),
|
|
38
38
|
stacking: series.stacking,
|
|
39
39
|
stackId: getSeriesStackId(series),
|
|
40
|
+
valueAxis: 'y',
|
|
40
41
|
dataLabels: {
|
|
41
42
|
enabled: ((_e = series.dataLabels) === null || _e === void 0 ? void 0 : _e.enabled) || false,
|
|
42
43
|
inside: dataLabelsInside,
|
|
@@ -64,6 +64,7 @@ export function prepareBarYSeries(args) {
|
|
|
64
64
|
data: prepareSeriesData(series),
|
|
65
65
|
stacking: series.stacking,
|
|
66
66
|
stackId: getSeriesStackId(series),
|
|
67
|
+
valueAxis: 'x',
|
|
67
68
|
dataLabels: await prepareDataLabels(series),
|
|
68
69
|
cursor: get(series, 'cursor', null),
|
|
69
70
|
borderRadius: (_g = (_e = series.borderRadius) !== null && _e !== void 0 ? _e : (_f = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions['bar-y']) === null || _f === void 0 ? void 0 : _f.borderRadius) !== null && _g !== void 0 ? _g : 0,
|
|
@@ -134,6 +134,7 @@ export type PreparedBarXSeries = {
|
|
|
134
134
|
data: BarXSeriesData[];
|
|
135
135
|
stackId: string;
|
|
136
136
|
stacking: BarXSeries['stacking'];
|
|
137
|
+
valueAxis: 'y';
|
|
137
138
|
dataLabels: {
|
|
138
139
|
enabled: boolean;
|
|
139
140
|
inside: boolean;
|
|
@@ -151,6 +152,7 @@ export type PreparedBarYSeries = {
|
|
|
151
152
|
data: BarYSeriesData[];
|
|
152
153
|
stackId: string;
|
|
153
154
|
stacking: BarYSeries['stacking'];
|
|
155
|
+
valueAxis: 'x';
|
|
154
156
|
dataLabels: {
|
|
155
157
|
padding: number;
|
|
156
158
|
enabled: boolean;
|
|
@@ -252,6 +254,7 @@ export type PreparedAreaSeries = {
|
|
|
252
254
|
data: AreaSeriesData[];
|
|
253
255
|
stacking: AreaSeries['stacking'];
|
|
254
256
|
stackId: string;
|
|
257
|
+
valueAxis: 'y';
|
|
255
258
|
lineWidth: number;
|
|
256
259
|
opacity: number;
|
|
257
260
|
nullMode: AreaSeries['nullMode'];
|
|
@@ -9,7 +9,6 @@ export async function prepareBarYData(args) {
|
|
|
9
9
|
const stackGap = seriesOptions['bar-y'].stackGap;
|
|
10
10
|
const xLinearScale = xScale;
|
|
11
11
|
const yLinearScale = yScale;
|
|
12
|
-
const [baseRangeValue] = xLinearScale.range();
|
|
13
12
|
if (!yLinearScale) {
|
|
14
13
|
return {
|
|
15
14
|
shapes: [],
|
|
@@ -92,19 +91,22 @@ export async function prepareBarYData(args) {
|
|
|
92
91
|
const borderWidth = barSize > s.borderWidth * 2 ? s.borderWidth : 0;
|
|
93
92
|
const isFirstInStack = xValueIndex === 0;
|
|
94
93
|
const isLastStackItem = xValueIndex === sortedData.length - 1;
|
|
94
|
+
const extendsRight = xLinearScale(xValue) > baseValue;
|
|
95
95
|
// Calculate position with border compensation
|
|
96
96
|
// Border extends halfBorder outward from the shape, so we need to adjust position
|
|
97
|
-
let itemX =
|
|
97
|
+
let itemX = extendsRight ? positiveStack : negativeStack - width;
|
|
98
98
|
itemX += itemStackGap;
|
|
99
99
|
const halfBorder = borderWidth / 2;
|
|
100
|
-
if (isFirstInStack &&
|
|
101
|
-
//
|
|
102
|
-
//
|
|
100
|
+
if (isFirstInStack && extendsRight) {
|
|
101
|
+
// Bar extends right from base, border extends outward to the
|
|
102
|
+
// left → shift left by halfBorder to keep the visual left
|
|
103
|
+
// edge at the zero line.
|
|
103
104
|
itemX -= halfBorder;
|
|
104
105
|
}
|
|
105
|
-
else if (isFirstInStack && xValue
|
|
106
|
-
//
|
|
107
|
-
//
|
|
106
|
+
else if (isFirstInStack && !extendsRight && xValue !== 0) {
|
|
107
|
+
// Bar extends left from base, border extends outward to the
|
|
108
|
+
// right → shift right by halfBorder to keep the visual
|
|
109
|
+
// right edge at the zero line.
|
|
108
110
|
itemX += halfBorder;
|
|
109
111
|
}
|
|
110
112
|
const item = {
|
|
@@ -121,7 +123,7 @@ export async function prepareBarYData(args) {
|
|
|
121
123
|
isLastStackItem,
|
|
122
124
|
};
|
|
123
125
|
stackItems.push(item);
|
|
124
|
-
if (
|
|
126
|
+
if (extendsRight) {
|
|
125
127
|
positiveStack += width;
|
|
126
128
|
}
|
|
127
129
|
else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { path } from 'd3-path';
|
|
2
|
-
import { calculateNumericProperty, getFormattedValue, getTextSizeFn } from '../../utils';
|
|
2
|
+
import { calculateNumericProperty, getFormattedValue, getLabelsSize, getTextSizeFn, } from '../../utils';
|
|
3
3
|
function getLineConnectorPaths(args) {
|
|
4
4
|
const { points } = args;
|
|
5
5
|
const leftPath = path();
|
|
@@ -23,6 +23,7 @@ export async function prepareFunnelData(args) {
|
|
|
23
23
|
const { series, boundsWidth, boundsHeight } = args;
|
|
24
24
|
const items = [];
|
|
25
25
|
const svgLabels = [];
|
|
26
|
+
const htmlLabels = [];
|
|
26
27
|
const connectors = [];
|
|
27
28
|
const maxValue = Math.max(...series.map((s) => s.data.value));
|
|
28
29
|
const itemBandSpace = boundsHeight / series.length;
|
|
@@ -42,36 +43,55 @@ export async function prepareFunnelData(args) {
|
|
|
42
43
|
if (s.dataLabels.enabled) {
|
|
43
44
|
const d = s.data;
|
|
44
45
|
const labelContent = (_c = d.label) !== null && _c !== void 0 ? _c : getFormattedValue({ value: d.value, format: s.dataLabels.format });
|
|
45
|
-
const
|
|
46
|
+
const { width, height, hangingOffset } = s.dataLabels.html
|
|
47
|
+
? await getLabelsSize({
|
|
48
|
+
labels: [labelContent],
|
|
49
|
+
style: s.dataLabels.style,
|
|
50
|
+
html: true,
|
|
51
|
+
}).then((size) => ({
|
|
52
|
+
width: size.maxWidth,
|
|
53
|
+
height: size.maxHeight,
|
|
54
|
+
hangingOffset: 0,
|
|
55
|
+
}))
|
|
56
|
+
: await getTextSize(labelContent);
|
|
46
57
|
let x;
|
|
47
58
|
switch (s.dataLabels.align) {
|
|
48
59
|
case 'left': {
|
|
49
60
|
x = 0;
|
|
50
|
-
segmentLeftOffset = Math.max(segmentLeftOffset,
|
|
61
|
+
segmentLeftOffset = Math.max(segmentLeftOffset, width);
|
|
51
62
|
break;
|
|
52
63
|
}
|
|
53
64
|
case 'right': {
|
|
54
|
-
x = boundsWidth -
|
|
55
|
-
segmentRightOffset = Math.max(segmentRightOffset,
|
|
65
|
+
x = boundsWidth - width;
|
|
66
|
+
segmentRightOffset = Math.max(segmentRightOffset, width);
|
|
56
67
|
break;
|
|
57
68
|
}
|
|
58
69
|
case 'center': {
|
|
59
|
-
x = boundsWidth / 2 -
|
|
70
|
+
x = boundsWidth / 2 - width / 2;
|
|
60
71
|
break;
|
|
61
72
|
}
|
|
62
73
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
const y = getSegmentY(index) + itemHeight / 2 - height / 2 + hangingOffset;
|
|
75
|
+
if (s.dataLabels.html) {
|
|
76
|
+
htmlLabels.push({
|
|
77
|
+
x,
|
|
78
|
+
y,
|
|
79
|
+
content: labelContent,
|
|
80
|
+
size: { width, height },
|
|
81
|
+
style: s.dataLabels.style,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
svgLabels.push({
|
|
86
|
+
x,
|
|
87
|
+
y,
|
|
88
|
+
text: labelContent,
|
|
89
|
+
style: s.dataLabels.style,
|
|
90
|
+
size: { width, height, hangingOffset },
|
|
91
|
+
textAnchor: 'start',
|
|
92
|
+
series: s,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
75
95
|
}
|
|
76
96
|
}
|
|
77
97
|
const segmentMaxWidth = boundsWidth - segmentLeftOffset - segmentRightOffset;
|
|
@@ -117,6 +137,7 @@ export async function prepareFunnelData(args) {
|
|
|
117
137
|
type: 'funnel',
|
|
118
138
|
items,
|
|
119
139
|
svgLabels,
|
|
140
|
+
htmlLabels,
|
|
120
141
|
connectors,
|
|
121
142
|
};
|
|
122
143
|
return data;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Path } from 'd3-path';
|
|
2
|
-
import type { FunnelSeriesData, LabelData } from '../../../types';
|
|
2
|
+
import type { FunnelSeriesData, HtmlItem, LabelData } from '../../../types';
|
|
3
3
|
import type { DashStyle } from '../../constants';
|
|
4
4
|
import type { PreparedFunnelSeries } from '../../series/types';
|
|
5
5
|
export type FunnelItemData = {
|
|
@@ -29,4 +29,5 @@ export type PreparedFunnelData = {
|
|
|
29
29
|
items: FunnelItemData[];
|
|
30
30
|
connectors: FunnelConnectorData[];
|
|
31
31
|
svgLabels: LabelData[];
|
|
32
|
+
htmlLabels: HtmlItem[];
|
|
32
33
|
};
|
|
@@ -2,6 +2,7 @@ import type { BaseType } from 'd3-selection';
|
|
|
2
2
|
import type { PreparedXAxis, PreparedYAxis } from '../axes/types';
|
|
3
3
|
import type { ChartScale } from '../scales/types';
|
|
4
4
|
import type { BasicInactiveState } from '../types';
|
|
5
|
+
import type { ZoomState } from '../zoom/types';
|
|
5
6
|
export declare function getXValue(args: {
|
|
6
7
|
point: {
|
|
7
8
|
x?: number | string | null;
|
|
@@ -74,3 +75,8 @@ export declare function getClipPathIdByBounds(args: {
|
|
|
74
75
|
clipPathId: string;
|
|
75
76
|
bounds?: 'horizontal';
|
|
76
77
|
}): string;
|
|
78
|
+
export declare function getSeriesClipPathId(args: {
|
|
79
|
+
clipPathId: string;
|
|
80
|
+
yAxis: PreparedYAxis[];
|
|
81
|
+
zoomState?: Partial<ZoomState>;
|
|
82
|
+
}): string;
|
|
@@ -184,3 +184,12 @@ export function getClipPathIdByBounds(args) {
|
|
|
184
184
|
const { bounds, clipPathId } = args;
|
|
185
185
|
return bounds ? `${clipPathId}-${bounds}` : clipPathId;
|
|
186
186
|
}
|
|
187
|
+
export function getSeriesClipPathId(args) {
|
|
188
|
+
const { clipPathId, yAxis, zoomState } = args;
|
|
189
|
+
const hasMinOrMax = yAxis.some((axis) => typeof (axis === null || axis === void 0 ? void 0 : axis.min) === 'number' || typeof (axis === null || axis === void 0 ? void 0 : axis.max) === 'number');
|
|
190
|
+
const hasZoom = zoomState && Object.keys(zoomState).length > 0;
|
|
191
|
+
if (!hasZoom && !hasMinOrMax) {
|
|
192
|
+
return `${clipPathId}-horizontal`;
|
|
193
|
+
}
|
|
194
|
+
return clipPathId;
|
|
195
|
+
}
|
|
@@ -39,7 +39,7 @@ export interface FunnelSeries<T = MeaningfulAny> extends Omit<BaseSeries, 'dataL
|
|
|
39
39
|
/** Opacity for the connector area. */
|
|
40
40
|
areaOpacity?: number;
|
|
41
41
|
};
|
|
42
|
-
dataLabels?: Omit<BaseDataLabels, '
|
|
42
|
+
dataLabels?: Omit<BaseDataLabels, 'allowOverlap'> & {
|
|
43
43
|
/** Horizontal alignment of the data labels. */
|
|
44
44
|
align?: 'left' | 'center' | 'right';
|
|
45
45
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type React from 'react';
|
|
2
1
|
import type { DashStyle, LineCap, LineJoin } from '../../constants';
|
|
3
2
|
import type { MeaningfulAny } from '../misc';
|
|
3
|
+
import type { SVGTextAttributes } from '../renderer';
|
|
4
4
|
import type { ChartAnnotationSeriesOptions } from './annotation';
|
|
5
5
|
import type { AreaSeries, AreaSeriesData } from './area';
|
|
6
6
|
import type { BarXSeries, BarXSeriesData } from './bar-x';
|
|
@@ -55,7 +55,7 @@ export interface ChartSeriesOptions {
|
|
|
55
55
|
/** Enable or disable the data labels */
|
|
56
56
|
enabled?: boolean;
|
|
57
57
|
/** Callback function to render the data label */
|
|
58
|
-
renderer?: (args: DataLabelRendererData) =>
|
|
58
|
+
renderer?: (args: DataLabelRendererData) => SVGTextAttributes;
|
|
59
59
|
};
|
|
60
60
|
'bar-x'?: {
|
|
61
61
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TOOLTIP_TOTALS_BUILT_IN_AGGREGATION } from '../../constants';
|
|
2
2
|
import type { DateTimeLabelFormats } from '../../utils/time';
|
|
3
3
|
import type { MeaningfulAny } from '../misc';
|
|
4
|
+
import type { RendererElement } from '../renderer';
|
|
4
5
|
import type { AreaSeries, AreaSeriesData } from './area';
|
|
5
6
|
import type { AxisPlotBand, AxisPlotLine, AxisPlotShape, ChartXAxis, ChartYAxis } from './axis';
|
|
6
7
|
import type { BarXSeries, BarXSeriesData } from './bar-x';
|
|
@@ -132,7 +133,7 @@ export type ChartTooltipSortComparator<T = MeaningfulAny> = (a: TooltipDataChunk
|
|
|
132
133
|
export interface ChartTooltip<T = MeaningfulAny> {
|
|
133
134
|
enabled?: boolean;
|
|
134
135
|
/** Specifies the renderer for the tooltip. If returned null default tooltip renderer will be used. */
|
|
135
|
-
renderer?: (args: ChartTooltipRendererArgs<T>) =>
|
|
136
|
+
renderer?: (args: ChartTooltipRendererArgs<T>) => RendererElement | null;
|
|
136
137
|
/**
|
|
137
138
|
* Defines the way a single data/series is displayed (corresponding to a separate selected point/ruler/shape on the chart).
|
|
138
139
|
* It is useful in cases where you need to display additional information, but keep the general format of the tooltip.
|
|
@@ -157,7 +158,7 @@ export interface ChartTooltip<T = MeaningfulAny> {
|
|
|
157
158
|
* `<tr class="${className}"><td>${name}</td><td>${value}</td></tr>`
|
|
158
159
|
* ```
|
|
159
160
|
*/
|
|
160
|
-
rowRenderer?: ((args: ChartTooltipRowRendererArgs) =>
|
|
161
|
+
rowRenderer?: ((args: ChartTooltipRowRendererArgs) => RendererElement | string) | null;
|
|
161
162
|
pin?: {
|
|
162
163
|
enabled?: boolean;
|
|
163
164
|
modifierKey?: 'altKey' | 'metaKey';
|
|
@@ -19,8 +19,8 @@ export interface ChartZoom {
|
|
|
19
19
|
* Supported zoom types by series type:
|
|
20
20
|
* - `Area`, `Line`, `Scatter`: `x`, `y`, `xy`
|
|
21
21
|
* - `BarX`: `x`, `xy`
|
|
22
|
+
* - `BarY`: `y`, `xy`
|
|
22
23
|
* - `XRange`: `x`
|
|
23
|
-
* - `BarY`: `y`
|
|
24
24
|
*
|
|
25
25
|
* Default zoom type by series type:
|
|
26
26
|
* - `BarY`: `y`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic renderer return type.
|
|
3
|
+
* Structurally compatible with React.ReactElement — callers in React contexts
|
|
4
|
+
* may narrow to it without changes.
|
|
5
|
+
*/
|
|
6
|
+
export interface RendererElement {
|
|
7
|
+
type: unknown;
|
|
8
|
+
props: unknown;
|
|
9
|
+
key: unknown;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* SVG text element attributes map.
|
|
13
|
+
* Replaces React.SVGTextElementAttributes<SVGTextElement> in core types.
|
|
14
|
+
*/
|
|
15
|
+
export type SVGTextAttributes = object;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Selection } from 'd3-selection';
|
|
2
2
|
import type { BaseTextStyle, MeaningfulAny } from '../../types';
|
|
3
|
+
import type { CSSProperties } from '../types/css';
|
|
3
4
|
/**
|
|
4
5
|
* Approximate ratio of descenders relative to the full font em height.
|
|
5
6
|
* Based on the Chromium hanging baseline algorithm where hanging offset ≈ ascent × 0.2.
|
|
@@ -12,7 +13,7 @@ export declare function setEllipsisForOverflowText<T>(selection: Selection<SVGTe
|
|
|
12
13
|
export declare function setEllipsisForOverflowTexts<T>(selection: Selection<SVGTextElement, T, MeaningfulAny, unknown>, maxWidth: ((datum: T) => number) | number, currentWidth?: (datum: T) => number): void;
|
|
13
14
|
export declare function getLabelsSize({ labels, style, rotation, html, }: {
|
|
14
15
|
labels: string[];
|
|
15
|
-
style?: BaseTextStyle &
|
|
16
|
+
style?: BaseTextStyle & CSSProperties;
|
|
16
17
|
rotation?: number;
|
|
17
18
|
html?: boolean;
|
|
18
19
|
}): Promise<{
|
|
@@ -60,6 +60,14 @@ export function getZoomedSeriesData(args) {
|
|
|
60
60
|
if (!isPreparedZoomableSeries(seriesItem)) {
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
+
// For stacked series the chart-space position of each point is the
|
|
64
|
+
// cumulative stack sum, not its individual value, so the value-axis
|
|
65
|
+
// filter would drop segments that still need to participate in the
|
|
66
|
+
// stack. Skip it and let the axis range / clip handle visibility.
|
|
67
|
+
const isStacked = 'stacking' in seriesItem && Boolean(seriesItem.stacking);
|
|
68
|
+
const stackedValueAxis = isStacked && 'valueAxis' in seriesItem ? seriesItem.valueAxis : undefined;
|
|
69
|
+
const skipXFilter = stackedValueAxis === 'x';
|
|
70
|
+
const skipYFilter = stackedValueAxis === 'y';
|
|
63
71
|
seriesItem.data.forEach((point, i) => {
|
|
64
72
|
var _a, _b;
|
|
65
73
|
const prevPoint = seriesItem.data[i - 1];
|
|
@@ -67,7 +75,7 @@ export function getZoomedSeriesData(args) {
|
|
|
67
75
|
let inXRange = true;
|
|
68
76
|
let inYRange = true;
|
|
69
77
|
prevPointInRange = currentPointInRange;
|
|
70
|
-
if (zoomState.x) {
|
|
78
|
+
if (zoomState.x && !skipXFilter) {
|
|
71
79
|
const [xMin, xMax] = zoomState.x;
|
|
72
80
|
if ('x0' in point && 'x1' in point) {
|
|
73
81
|
const isStartInRange = isValueInRange({
|
|
@@ -94,7 +102,7 @@ export function getZoomedSeriesData(args) {
|
|
|
94
102
|
});
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
|
-
if (zoomState.y) {
|
|
105
|
+
if (zoomState.y && !skipYFilter) {
|
|
98
106
|
const yAxisIndex = 'yAxis' in seriesItem && typeof seriesItem.yAxis === 'number'
|
|
99
107
|
? seriesItem.yAxis
|
|
100
108
|
: 0;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { renderFunnel } from '../../../core/shapes/funnel/renderer';
|
|
3
3
|
import { block } from '../../../utils';
|
|
4
|
+
import { HtmlLayer } from '../HtmlLayer';
|
|
4
5
|
export { prepareFunnelData } from '../../../core/shapes/funnel/prepare-data';
|
|
5
6
|
export * from '../../../core/shapes/funnel/types';
|
|
6
7
|
const b = block('funnel');
|
|
7
8
|
export const FunnelSeriesShapes = (args) => {
|
|
8
|
-
const { dispatcher, preparedData, seriesOptions } = args;
|
|
9
|
+
const { dispatcher, htmlLayout, preparedData, seriesOptions } = args;
|
|
9
10
|
const ref = React.useRef(null);
|
|
10
11
|
React.useEffect(() => {
|
|
11
12
|
if (!ref.current) {
|
|
@@ -13,6 +14,8 @@ export const FunnelSeriesShapes = (args) => {
|
|
|
13
14
|
}
|
|
14
15
|
return renderFunnel({ plot: ref.current }, preparedData, seriesOptions, dispatcher);
|
|
15
16
|
}, [dispatcher, preparedData, seriesOptions]);
|
|
17
|
+
const htmlLayerData = React.useMemo(() => ({ htmlElements: preparedData.htmlLabels }), [preparedData.htmlLabels]);
|
|
16
18
|
return (React.createElement(React.Fragment, null,
|
|
17
|
-
React.createElement("g", { ref: ref, className: b() })
|
|
19
|
+
React.createElement("g", { ref: ref, className: b() }),
|
|
20
|
+
React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
|
|
18
21
|
};
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
import type { ZoomState } from '../../core/zoom/types';
|
|
2
|
-
import type { PreparedYAxis } from '../useAxis/types';
|
|
3
1
|
export * from '../../core/shapes/utils';
|
|
4
|
-
export declare function getSeriesClipPathId(args: {
|
|
5
|
-
clipPathId: string;
|
|
6
|
-
yAxis: PreparedYAxis[];
|
|
7
|
-
zoomState?: Partial<ZoomState>;
|
|
8
|
-
}): string;
|
|
@@ -1,12 +1 @@
|
|
|
1
1
|
export * from '../../core/shapes/utils';
|
|
2
|
-
export function getSeriesClipPathId(args) {
|
|
3
|
-
const { clipPathId, yAxis, zoomState } = args;
|
|
4
|
-
const hasMinOrMax = yAxis.some((axis) => {
|
|
5
|
-
return typeof (axis === null || axis === void 0 ? void 0 : axis.min) === 'number' || typeof (axis === null || axis === void 0 ? void 0 : axis.max) === 'number';
|
|
6
|
-
});
|
|
7
|
-
const hasZoom = zoomState && Object.keys(zoomState).length > 0;
|
|
8
|
-
if (!hasZoom && !hasMinOrMax) {
|
|
9
|
-
return `${clipPathId}-horizontal`;
|
|
10
|
-
}
|
|
11
|
-
return clipPathId;
|
|
12
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BaseTextStyle } from '../core/types/chart/base';
|
|
2
|
+
import type { CSSProperties } from '../core/types/css';
|
|
2
3
|
export interface LabelData {
|
|
3
4
|
text: string;
|
|
4
5
|
x: number;
|
|
@@ -23,7 +24,7 @@ export interface HtmlItem {
|
|
|
23
24
|
width: number;
|
|
24
25
|
height: number;
|
|
25
26
|
};
|
|
26
|
-
style?: BaseTextStyle &
|
|
27
|
+
style?: BaseTextStyle & CSSProperties;
|
|
27
28
|
/** Coordinate space for positioning: 'plot' uses the plot area origin, 'chart' uses the full chart origin. Defaults to 'plot'. */
|
|
28
29
|
scope?: 'plot' | 'chart';
|
|
29
30
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { DashStyle } from 'src/core/constants';
|
|
2
2
|
import type { AxisPlotShape } from '../../core/types/chart/axis';
|
|
3
|
+
import type { CSSProperties } from '../../core/types/css';
|
|
3
4
|
import type { BaseTextStyle, HtmlItem, PlotLayerPlacement, PointPosition } from '../../types';
|
|
4
5
|
import type { TextRowData } from '../types';
|
|
5
6
|
export type AxisSvgLabelData = {
|
|
@@ -42,7 +43,7 @@ export type SvgAxisTitleData = {
|
|
|
42
43
|
export type HtmlAxisTitleData = {
|
|
43
44
|
html: true;
|
|
44
45
|
content: string;
|
|
45
|
-
style: BaseTextStyle &
|
|
46
|
+
style: BaseTextStyle & CSSProperties;
|
|
46
47
|
size: {
|
|
47
48
|
width: number;
|
|
48
49
|
height: number;
|
|
@@ -63,6 +63,7 @@ export function prepareArea(args) {
|
|
|
63
63
|
data: prepareSeriesData(series),
|
|
64
64
|
stacking: series.stacking,
|
|
65
65
|
stackId: getSeriesStackId(series),
|
|
66
|
+
valueAxis: 'y',
|
|
66
67
|
dataLabels: {
|
|
67
68
|
enabled: ((_e = series.dataLabels) === null || _e === void 0 ? void 0 : _e.enabled) || false,
|
|
68
69
|
style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_f = series.dataLabels) === null || _f === void 0 ? void 0 : _f.style),
|
|
@@ -37,6 +37,7 @@ export function prepareBarXSeries(args) {
|
|
|
37
37
|
data: prepareSeriesData(series),
|
|
38
38
|
stacking: series.stacking,
|
|
39
39
|
stackId: getSeriesStackId(series),
|
|
40
|
+
valueAxis: 'y',
|
|
40
41
|
dataLabels: {
|
|
41
42
|
enabled: ((_e = series.dataLabels) === null || _e === void 0 ? void 0 : _e.enabled) || false,
|
|
42
43
|
inside: dataLabelsInside,
|
|
@@ -64,6 +64,7 @@ export function prepareBarYSeries(args) {
|
|
|
64
64
|
data: prepareSeriesData(series),
|
|
65
65
|
stacking: series.stacking,
|
|
66
66
|
stackId: getSeriesStackId(series),
|
|
67
|
+
valueAxis: 'x',
|
|
67
68
|
dataLabels: await prepareDataLabels(series),
|
|
68
69
|
cursor: get(series, 'cursor', null),
|
|
69
70
|
borderRadius: (_g = (_e = series.borderRadius) !== null && _e !== void 0 ? _e : (_f = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions['bar-y']) === null || _f === void 0 ? void 0 : _f.borderRadius) !== null && _g !== void 0 ? _g : 0,
|
|
@@ -134,6 +134,7 @@ export type PreparedBarXSeries = {
|
|
|
134
134
|
data: BarXSeriesData[];
|
|
135
135
|
stackId: string;
|
|
136
136
|
stacking: BarXSeries['stacking'];
|
|
137
|
+
valueAxis: 'y';
|
|
137
138
|
dataLabels: {
|
|
138
139
|
enabled: boolean;
|
|
139
140
|
inside: boolean;
|
|
@@ -151,6 +152,7 @@ export type PreparedBarYSeries = {
|
|
|
151
152
|
data: BarYSeriesData[];
|
|
152
153
|
stackId: string;
|
|
153
154
|
stacking: BarYSeries['stacking'];
|
|
155
|
+
valueAxis: 'x';
|
|
154
156
|
dataLabels: {
|
|
155
157
|
padding: number;
|
|
156
158
|
enabled: boolean;
|
|
@@ -252,6 +254,7 @@ export type PreparedAreaSeries = {
|
|
|
252
254
|
data: AreaSeriesData[];
|
|
253
255
|
stacking: AreaSeries['stacking'];
|
|
254
256
|
stackId: string;
|
|
257
|
+
valueAxis: 'y';
|
|
255
258
|
lineWidth: number;
|
|
256
259
|
opacity: number;
|
|
257
260
|
nullMode: AreaSeries['nullMode'];
|
|
@@ -9,7 +9,6 @@ export async function prepareBarYData(args) {
|
|
|
9
9
|
const stackGap = seriesOptions['bar-y'].stackGap;
|
|
10
10
|
const xLinearScale = xScale;
|
|
11
11
|
const yLinearScale = yScale;
|
|
12
|
-
const [baseRangeValue] = xLinearScale.range();
|
|
13
12
|
if (!yLinearScale) {
|
|
14
13
|
return {
|
|
15
14
|
shapes: [],
|
|
@@ -92,19 +91,22 @@ export async function prepareBarYData(args) {
|
|
|
92
91
|
const borderWidth = barSize > s.borderWidth * 2 ? s.borderWidth : 0;
|
|
93
92
|
const isFirstInStack = xValueIndex === 0;
|
|
94
93
|
const isLastStackItem = xValueIndex === sortedData.length - 1;
|
|
94
|
+
const extendsRight = xLinearScale(xValue) > baseValue;
|
|
95
95
|
// Calculate position with border compensation
|
|
96
96
|
// Border extends halfBorder outward from the shape, so we need to adjust position
|
|
97
|
-
let itemX =
|
|
97
|
+
let itemX = extendsRight ? positiveStack : negativeStack - width;
|
|
98
98
|
itemX += itemStackGap;
|
|
99
99
|
const halfBorder = borderWidth / 2;
|
|
100
|
-
if (isFirstInStack &&
|
|
101
|
-
//
|
|
102
|
-
//
|
|
100
|
+
if (isFirstInStack && extendsRight) {
|
|
101
|
+
// Bar extends right from base, border extends outward to the
|
|
102
|
+
// left → shift left by halfBorder to keep the visual left
|
|
103
|
+
// edge at the zero line.
|
|
103
104
|
itemX -= halfBorder;
|
|
104
105
|
}
|
|
105
|
-
else if (isFirstInStack && xValue
|
|
106
|
-
//
|
|
107
|
-
//
|
|
106
|
+
else if (isFirstInStack && !extendsRight && xValue !== 0) {
|
|
107
|
+
// Bar extends left from base, border extends outward to the
|
|
108
|
+
// right → shift right by halfBorder to keep the visual
|
|
109
|
+
// right edge at the zero line.
|
|
108
110
|
itemX += halfBorder;
|
|
109
111
|
}
|
|
110
112
|
const item = {
|
|
@@ -121,7 +123,7 @@ export async function prepareBarYData(args) {
|
|
|
121
123
|
isLastStackItem,
|
|
122
124
|
};
|
|
123
125
|
stackItems.push(item);
|
|
124
|
-
if (
|
|
126
|
+
if (extendsRight) {
|
|
125
127
|
positiveStack += width;
|
|
126
128
|
}
|
|
127
129
|
else {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { path } from 'd3-path';
|
|
2
|
-
import { calculateNumericProperty, getFormattedValue, getTextSizeFn } from '../../utils';
|
|
2
|
+
import { calculateNumericProperty, getFormattedValue, getLabelsSize, getTextSizeFn, } from '../../utils';
|
|
3
3
|
function getLineConnectorPaths(args) {
|
|
4
4
|
const { points } = args;
|
|
5
5
|
const leftPath = path();
|
|
@@ -23,6 +23,7 @@ export async function prepareFunnelData(args) {
|
|
|
23
23
|
const { series, boundsWidth, boundsHeight } = args;
|
|
24
24
|
const items = [];
|
|
25
25
|
const svgLabels = [];
|
|
26
|
+
const htmlLabels = [];
|
|
26
27
|
const connectors = [];
|
|
27
28
|
const maxValue = Math.max(...series.map((s) => s.data.value));
|
|
28
29
|
const itemBandSpace = boundsHeight / series.length;
|
|
@@ -42,36 +43,55 @@ export async function prepareFunnelData(args) {
|
|
|
42
43
|
if (s.dataLabels.enabled) {
|
|
43
44
|
const d = s.data;
|
|
44
45
|
const labelContent = (_c = d.label) !== null && _c !== void 0 ? _c : getFormattedValue({ value: d.value, format: s.dataLabels.format });
|
|
45
|
-
const
|
|
46
|
+
const { width, height, hangingOffset } = s.dataLabels.html
|
|
47
|
+
? await getLabelsSize({
|
|
48
|
+
labels: [labelContent],
|
|
49
|
+
style: s.dataLabels.style,
|
|
50
|
+
html: true,
|
|
51
|
+
}).then((size) => ({
|
|
52
|
+
width: size.maxWidth,
|
|
53
|
+
height: size.maxHeight,
|
|
54
|
+
hangingOffset: 0,
|
|
55
|
+
}))
|
|
56
|
+
: await getTextSize(labelContent);
|
|
46
57
|
let x;
|
|
47
58
|
switch (s.dataLabels.align) {
|
|
48
59
|
case 'left': {
|
|
49
60
|
x = 0;
|
|
50
|
-
segmentLeftOffset = Math.max(segmentLeftOffset,
|
|
61
|
+
segmentLeftOffset = Math.max(segmentLeftOffset, width);
|
|
51
62
|
break;
|
|
52
63
|
}
|
|
53
64
|
case 'right': {
|
|
54
|
-
x = boundsWidth -
|
|
55
|
-
segmentRightOffset = Math.max(segmentRightOffset,
|
|
65
|
+
x = boundsWidth - width;
|
|
66
|
+
segmentRightOffset = Math.max(segmentRightOffset, width);
|
|
56
67
|
break;
|
|
57
68
|
}
|
|
58
69
|
case 'center': {
|
|
59
|
-
x = boundsWidth / 2 -
|
|
70
|
+
x = boundsWidth / 2 - width / 2;
|
|
60
71
|
break;
|
|
61
72
|
}
|
|
62
73
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
const y = getSegmentY(index) + itemHeight / 2 - height / 2 + hangingOffset;
|
|
75
|
+
if (s.dataLabels.html) {
|
|
76
|
+
htmlLabels.push({
|
|
77
|
+
x,
|
|
78
|
+
y,
|
|
79
|
+
content: labelContent,
|
|
80
|
+
size: { width, height },
|
|
81
|
+
style: s.dataLabels.style,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
svgLabels.push({
|
|
86
|
+
x,
|
|
87
|
+
y,
|
|
88
|
+
text: labelContent,
|
|
89
|
+
style: s.dataLabels.style,
|
|
90
|
+
size: { width, height, hangingOffset },
|
|
91
|
+
textAnchor: 'start',
|
|
92
|
+
series: s,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
75
95
|
}
|
|
76
96
|
}
|
|
77
97
|
const segmentMaxWidth = boundsWidth - segmentLeftOffset - segmentRightOffset;
|
|
@@ -117,6 +137,7 @@ export async function prepareFunnelData(args) {
|
|
|
117
137
|
type: 'funnel',
|
|
118
138
|
items,
|
|
119
139
|
svgLabels,
|
|
140
|
+
htmlLabels,
|
|
120
141
|
connectors,
|
|
121
142
|
};
|
|
122
143
|
return data;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Path } from 'd3-path';
|
|
2
|
-
import type { FunnelSeriesData, LabelData } from '../../../types';
|
|
2
|
+
import type { FunnelSeriesData, HtmlItem, LabelData } from '../../../types';
|
|
3
3
|
import type { DashStyle } from '../../constants';
|
|
4
4
|
import type { PreparedFunnelSeries } from '../../series/types';
|
|
5
5
|
export type FunnelItemData = {
|
|
@@ -29,4 +29,5 @@ export type PreparedFunnelData = {
|
|
|
29
29
|
items: FunnelItemData[];
|
|
30
30
|
connectors: FunnelConnectorData[];
|
|
31
31
|
svgLabels: LabelData[];
|
|
32
|
+
htmlLabels: HtmlItem[];
|
|
32
33
|
};
|
|
@@ -2,6 +2,7 @@ import type { BaseType } from 'd3-selection';
|
|
|
2
2
|
import type { PreparedXAxis, PreparedYAxis } from '../axes/types';
|
|
3
3
|
import type { ChartScale } from '../scales/types';
|
|
4
4
|
import type { BasicInactiveState } from '../types';
|
|
5
|
+
import type { ZoomState } from '../zoom/types';
|
|
5
6
|
export declare function getXValue(args: {
|
|
6
7
|
point: {
|
|
7
8
|
x?: number | string | null;
|
|
@@ -74,3 +75,8 @@ export declare function getClipPathIdByBounds(args: {
|
|
|
74
75
|
clipPathId: string;
|
|
75
76
|
bounds?: 'horizontal';
|
|
76
77
|
}): string;
|
|
78
|
+
export declare function getSeriesClipPathId(args: {
|
|
79
|
+
clipPathId: string;
|
|
80
|
+
yAxis: PreparedYAxis[];
|
|
81
|
+
zoomState?: Partial<ZoomState>;
|
|
82
|
+
}): string;
|
|
@@ -184,3 +184,12 @@ export function getClipPathIdByBounds(args) {
|
|
|
184
184
|
const { bounds, clipPathId } = args;
|
|
185
185
|
return bounds ? `${clipPathId}-${bounds}` : clipPathId;
|
|
186
186
|
}
|
|
187
|
+
export function getSeriesClipPathId(args) {
|
|
188
|
+
const { clipPathId, yAxis, zoomState } = args;
|
|
189
|
+
const hasMinOrMax = yAxis.some((axis) => typeof (axis === null || axis === void 0 ? void 0 : axis.min) === 'number' || typeof (axis === null || axis === void 0 ? void 0 : axis.max) === 'number');
|
|
190
|
+
const hasZoom = zoomState && Object.keys(zoomState).length > 0;
|
|
191
|
+
if (!hasZoom && !hasMinOrMax) {
|
|
192
|
+
return `${clipPathId}-horizontal`;
|
|
193
|
+
}
|
|
194
|
+
return clipPathId;
|
|
195
|
+
}
|
|
@@ -39,7 +39,7 @@ export interface FunnelSeries<T = MeaningfulAny> extends Omit<BaseSeries, 'dataL
|
|
|
39
39
|
/** Opacity for the connector area. */
|
|
40
40
|
areaOpacity?: number;
|
|
41
41
|
};
|
|
42
|
-
dataLabels?: Omit<BaseDataLabels, '
|
|
42
|
+
dataLabels?: Omit<BaseDataLabels, 'allowOverlap'> & {
|
|
43
43
|
/** Horizontal alignment of the data labels. */
|
|
44
44
|
align?: 'left' | 'center' | 'right';
|
|
45
45
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type React from 'react';
|
|
2
1
|
import type { DashStyle, LineCap, LineJoin } from '../../constants';
|
|
3
2
|
import type { MeaningfulAny } from '../misc';
|
|
3
|
+
import type { SVGTextAttributes } from '../renderer';
|
|
4
4
|
import type { ChartAnnotationSeriesOptions } from './annotation';
|
|
5
5
|
import type { AreaSeries, AreaSeriesData } from './area';
|
|
6
6
|
import type { BarXSeries, BarXSeriesData } from './bar-x';
|
|
@@ -55,7 +55,7 @@ export interface ChartSeriesOptions {
|
|
|
55
55
|
/** Enable or disable the data labels */
|
|
56
56
|
enabled?: boolean;
|
|
57
57
|
/** Callback function to render the data label */
|
|
58
|
-
renderer?: (args: DataLabelRendererData) =>
|
|
58
|
+
renderer?: (args: DataLabelRendererData) => SVGTextAttributes;
|
|
59
59
|
};
|
|
60
60
|
'bar-x'?: {
|
|
61
61
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TOOLTIP_TOTALS_BUILT_IN_AGGREGATION } from '../../constants';
|
|
2
2
|
import type { DateTimeLabelFormats } from '../../utils/time';
|
|
3
3
|
import type { MeaningfulAny } from '../misc';
|
|
4
|
+
import type { RendererElement } from '../renderer';
|
|
4
5
|
import type { AreaSeries, AreaSeriesData } from './area';
|
|
5
6
|
import type { AxisPlotBand, AxisPlotLine, AxisPlotShape, ChartXAxis, ChartYAxis } from './axis';
|
|
6
7
|
import type { BarXSeries, BarXSeriesData } from './bar-x';
|
|
@@ -132,7 +133,7 @@ export type ChartTooltipSortComparator<T = MeaningfulAny> = (a: TooltipDataChunk
|
|
|
132
133
|
export interface ChartTooltip<T = MeaningfulAny> {
|
|
133
134
|
enabled?: boolean;
|
|
134
135
|
/** Specifies the renderer for the tooltip. If returned null default tooltip renderer will be used. */
|
|
135
|
-
renderer?: (args: ChartTooltipRendererArgs<T>) =>
|
|
136
|
+
renderer?: (args: ChartTooltipRendererArgs<T>) => RendererElement | null;
|
|
136
137
|
/**
|
|
137
138
|
* Defines the way a single data/series is displayed (corresponding to a separate selected point/ruler/shape on the chart).
|
|
138
139
|
* It is useful in cases where you need to display additional information, but keep the general format of the tooltip.
|
|
@@ -157,7 +158,7 @@ export interface ChartTooltip<T = MeaningfulAny> {
|
|
|
157
158
|
* `<tr class="${className}"><td>${name}</td><td>${value}</td></tr>`
|
|
158
159
|
* ```
|
|
159
160
|
*/
|
|
160
|
-
rowRenderer?: ((args: ChartTooltipRowRendererArgs) =>
|
|
161
|
+
rowRenderer?: ((args: ChartTooltipRowRendererArgs) => RendererElement | string) | null;
|
|
161
162
|
pin?: {
|
|
162
163
|
enabled?: boolean;
|
|
163
164
|
modifierKey?: 'altKey' | 'metaKey';
|
|
@@ -19,8 +19,8 @@ export interface ChartZoom {
|
|
|
19
19
|
* Supported zoom types by series type:
|
|
20
20
|
* - `Area`, `Line`, `Scatter`: `x`, `y`, `xy`
|
|
21
21
|
* - `BarX`: `x`, `xy`
|
|
22
|
+
* - `BarY`: `y`, `xy`
|
|
22
23
|
* - `XRange`: `x`
|
|
23
|
-
* - `BarY`: `y`
|
|
24
24
|
*
|
|
25
25
|
* Default zoom type by series type:
|
|
26
26
|
* - `BarY`: `y`
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Framework-agnostic renderer return type.
|
|
3
|
+
* Structurally compatible with React.ReactElement — callers in React contexts
|
|
4
|
+
* may narrow to it without changes.
|
|
5
|
+
*/
|
|
6
|
+
export interface RendererElement {
|
|
7
|
+
type: unknown;
|
|
8
|
+
props: unknown;
|
|
9
|
+
key: unknown;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* SVG text element attributes map.
|
|
13
|
+
* Replaces React.SVGTextElementAttributes<SVGTextElement> in core types.
|
|
14
|
+
*/
|
|
15
|
+
export type SVGTextAttributes = object;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Selection } from 'd3-selection';
|
|
2
2
|
import type { BaseTextStyle, MeaningfulAny } from '../../types';
|
|
3
|
+
import type { CSSProperties } from '../types/css';
|
|
3
4
|
/**
|
|
4
5
|
* Approximate ratio of descenders relative to the full font em height.
|
|
5
6
|
* Based on the Chromium hanging baseline algorithm where hanging offset ≈ ascent × 0.2.
|
|
@@ -12,7 +13,7 @@ export declare function setEllipsisForOverflowText<T>(selection: Selection<SVGTe
|
|
|
12
13
|
export declare function setEllipsisForOverflowTexts<T>(selection: Selection<SVGTextElement, T, MeaningfulAny, unknown>, maxWidth: ((datum: T) => number) | number, currentWidth?: (datum: T) => number): void;
|
|
13
14
|
export declare function getLabelsSize({ labels, style, rotation, html, }: {
|
|
14
15
|
labels: string[];
|
|
15
|
-
style?: BaseTextStyle &
|
|
16
|
+
style?: BaseTextStyle & CSSProperties;
|
|
16
17
|
rotation?: number;
|
|
17
18
|
html?: boolean;
|
|
18
19
|
}): Promise<{
|
|
@@ -60,6 +60,14 @@ export function getZoomedSeriesData(args) {
|
|
|
60
60
|
if (!isPreparedZoomableSeries(seriesItem)) {
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
|
+
// For stacked series the chart-space position of each point is the
|
|
64
|
+
// cumulative stack sum, not its individual value, so the value-axis
|
|
65
|
+
// filter would drop segments that still need to participate in the
|
|
66
|
+
// stack. Skip it and let the axis range / clip handle visibility.
|
|
67
|
+
const isStacked = 'stacking' in seriesItem && Boolean(seriesItem.stacking);
|
|
68
|
+
const stackedValueAxis = isStacked && 'valueAxis' in seriesItem ? seriesItem.valueAxis : undefined;
|
|
69
|
+
const skipXFilter = stackedValueAxis === 'x';
|
|
70
|
+
const skipYFilter = stackedValueAxis === 'y';
|
|
63
71
|
seriesItem.data.forEach((point, i) => {
|
|
64
72
|
var _a, _b;
|
|
65
73
|
const prevPoint = seriesItem.data[i - 1];
|
|
@@ -67,7 +75,7 @@ export function getZoomedSeriesData(args) {
|
|
|
67
75
|
let inXRange = true;
|
|
68
76
|
let inYRange = true;
|
|
69
77
|
prevPointInRange = currentPointInRange;
|
|
70
|
-
if (zoomState.x) {
|
|
78
|
+
if (zoomState.x && !skipXFilter) {
|
|
71
79
|
const [xMin, xMax] = zoomState.x;
|
|
72
80
|
if ('x0' in point && 'x1' in point) {
|
|
73
81
|
const isStartInRange = isValueInRange({
|
|
@@ -94,7 +102,7 @@ export function getZoomedSeriesData(args) {
|
|
|
94
102
|
});
|
|
95
103
|
}
|
|
96
104
|
}
|
|
97
|
-
if (zoomState.y) {
|
|
105
|
+
if (zoomState.y && !skipYFilter) {
|
|
98
106
|
const yAxisIndex = 'yAxis' in seriesItem && typeof seriesItem.yAxis === 'number'
|
|
99
107
|
? seriesItem.yAxis
|
|
100
108
|
: 0;
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { renderFunnel } from '../../../core/shapes/funnel/renderer';
|
|
3
3
|
import { block } from '../../../utils';
|
|
4
|
+
import { HtmlLayer } from '../HtmlLayer';
|
|
4
5
|
export { prepareFunnelData } from '../../../core/shapes/funnel/prepare-data';
|
|
5
6
|
export * from '../../../core/shapes/funnel/types';
|
|
6
7
|
const b = block('funnel');
|
|
7
8
|
export const FunnelSeriesShapes = (args) => {
|
|
8
|
-
const { dispatcher, preparedData, seriesOptions } = args;
|
|
9
|
+
const { dispatcher, htmlLayout, preparedData, seriesOptions } = args;
|
|
9
10
|
const ref = React.useRef(null);
|
|
10
11
|
React.useEffect(() => {
|
|
11
12
|
if (!ref.current) {
|
|
@@ -13,6 +14,8 @@ export const FunnelSeriesShapes = (args) => {
|
|
|
13
14
|
}
|
|
14
15
|
return renderFunnel({ plot: ref.current }, preparedData, seriesOptions, dispatcher);
|
|
15
16
|
}, [dispatcher, preparedData, seriesOptions]);
|
|
17
|
+
const htmlLayerData = React.useMemo(() => ({ htmlElements: preparedData.htmlLabels }), [preparedData.htmlLabels]);
|
|
16
18
|
return (React.createElement(React.Fragment, null,
|
|
17
|
-
React.createElement("g", { ref: ref, className: b() })
|
|
19
|
+
React.createElement("g", { ref: ref, className: b() }),
|
|
20
|
+
React.createElement(HtmlLayer, { preparedData: htmlLayerData, htmlLayout: htmlLayout })));
|
|
18
21
|
};
|
|
@@ -1,8 +1 @@
|
|
|
1
|
-
import type { ZoomState } from '../../core/zoom/types';
|
|
2
|
-
import type { PreparedYAxis } from '../useAxis/types';
|
|
3
1
|
export * from '../../core/shapes/utils';
|
|
4
|
-
export declare function getSeriesClipPathId(args: {
|
|
5
|
-
clipPathId: string;
|
|
6
|
-
yAxis: PreparedYAxis[];
|
|
7
|
-
zoomState?: Partial<ZoomState>;
|
|
8
|
-
}): string;
|
|
@@ -1,12 +1 @@
|
|
|
1
1
|
export * from '../../core/shapes/utils';
|
|
2
|
-
export function getSeriesClipPathId(args) {
|
|
3
|
-
const { clipPathId, yAxis, zoomState } = args;
|
|
4
|
-
const hasMinOrMax = yAxis.some((axis) => {
|
|
5
|
-
return typeof (axis === null || axis === void 0 ? void 0 : axis.min) === 'number' || typeof (axis === null || axis === void 0 ? void 0 : axis.max) === 'number';
|
|
6
|
-
});
|
|
7
|
-
const hasZoom = zoomState && Object.keys(zoomState).length > 0;
|
|
8
|
-
if (!hasZoom && !hasMinOrMax) {
|
|
9
|
-
return `${clipPathId}-horizontal`;
|
|
10
|
-
}
|
|
11
|
-
return clipPathId;
|
|
12
|
-
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BaseTextStyle } from '../core/types/chart/base';
|
|
2
|
+
import type { CSSProperties } from '../core/types/css';
|
|
2
3
|
export interface LabelData {
|
|
3
4
|
text: string;
|
|
4
5
|
x: number;
|
|
@@ -23,7 +24,7 @@ export interface HtmlItem {
|
|
|
23
24
|
width: number;
|
|
24
25
|
height: number;
|
|
25
26
|
};
|
|
26
|
-
style?: BaseTextStyle &
|
|
27
|
+
style?: BaseTextStyle & CSSProperties;
|
|
27
28
|
/** Coordinate space for positioning: 'plot' uses the plot area origin, 'chart' uses the full chart origin. Defaults to 'plot'. */
|
|
28
29
|
scope?: 'plot' | 'chart';
|
|
29
30
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gravity-ui/charts",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.50.0",
|
|
4
4
|
"description": "A flexible JavaScript library for data visualization and chart rendering using React",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/cjs/index.js",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"@gravity-ui/date-utils": "^2.5.4",
|
|
72
72
|
"@gravity-ui/i18n": "^1.6.0",
|
|
73
73
|
"@gravity-ui/icons": "^2.15.0",
|
|
74
|
+
"csstype": "^3.2.3",
|
|
74
75
|
"d3-array": "^3.2.4",
|
|
75
76
|
"d3-axis": "^3.0.0",
|
|
76
77
|
"d3-brush": "^3.0.0",
|