@gravity-ui/charts 1.49.1 → 1.51.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/scales/y-scale.js +6 -0
- 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/prepare-funnel.js +5 -2
- package/dist/cjs/core/series/types.d.ts +4 -0
- package/dist/cjs/core/shapes/bar-y/prepare-data.js +11 -9
- package/dist/cjs/core/shapes/funnel/prepare-data.js +57 -23
- package/dist/cjs/core/shapes/funnel/renderer.js +3 -6
- package/dist/cjs/core/shapes/funnel/types.d.ts +3 -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 +18 -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/scales/y-scale.js +6 -0
- 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/prepare-funnel.js +5 -2
- package/dist/esm/core/series/types.d.ts +4 -0
- package/dist/esm/core/shapes/bar-y/prepare-data.js +11 -9
- package/dist/esm/core/shapes/funnel/prepare-data.js +57 -23
- package/dist/esm/core/shapes/funnel/renderer.js +3 -6
- package/dist/esm/core/shapes/funnel/types.d.ts +3 -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 +18 -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;
|
|
@@ -127,6 +127,9 @@ function getDomainMinAlignedToStartTick(args) {
|
|
|
127
127
|
else {
|
|
128
128
|
step = tickStep(dMin, dMax, 1);
|
|
129
129
|
}
|
|
130
|
+
if (step === 0) {
|
|
131
|
+
return dMin;
|
|
132
|
+
}
|
|
130
133
|
dNewMin = tickValues[0].value - step;
|
|
131
134
|
}
|
|
132
135
|
}
|
|
@@ -161,6 +164,9 @@ function getDomainMaxAlignedToEndTick(args) {
|
|
|
161
164
|
else {
|
|
162
165
|
step = tickStep(dMin, dMax, 1);
|
|
163
166
|
}
|
|
167
|
+
if (step === 0) {
|
|
168
|
+
return dMax;
|
|
169
|
+
}
|
|
164
170
|
dNewMax = Math.floor(dMax / step + 1) * step;
|
|
165
171
|
}
|
|
166
172
|
}
|
|
@@ -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,
|
|
@@ -4,17 +4,20 @@ import { DEFAULT_DATALABELS_STYLE } from '../constants';
|
|
|
4
4
|
import { getUniqId } from '../utils';
|
|
5
5
|
import { prepareLegendSymbol } from './utils';
|
|
6
6
|
export function prepareFunnelSeries(args) {
|
|
7
|
-
var _a, _b;
|
|
7
|
+
var _a, _b, _c;
|
|
8
8
|
const { series, legend, colors } = args;
|
|
9
9
|
const dataNames = series.data.map((d) => d.name);
|
|
10
10
|
const colorScale = scaleOrdinal(dataNames, colors);
|
|
11
|
-
const
|
|
11
|
+
const shape = (_a = series.shape) !== null && _a !== void 0 ? _a : 'rectangle';
|
|
12
|
+
const isTrapezoid = shape === 'trapezoid';
|
|
13
|
+
const isConnectorsEnabled = (_c = (_b = series.connectors) === null || _b === void 0 ? void 0 : _b.enabled) !== null && _c !== void 0 ? _c : !isTrapezoid;
|
|
12
14
|
const preparedSeries = series.data.map((dataItem) => {
|
|
13
15
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2;
|
|
14
16
|
const id = getUniqId();
|
|
15
17
|
const color = dataItem.color || colorScale(dataItem.name);
|
|
16
18
|
const result = {
|
|
17
19
|
type: 'funnel',
|
|
20
|
+
shape,
|
|
18
21
|
data: dataItem,
|
|
19
22
|
dataLabels: {
|
|
20
23
|
enabled: get(series, 'dataLabels.enabled', true),
|
|
@@ -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'];
|
|
@@ -359,6 +362,7 @@ export type PreparedRadarSeries = {
|
|
|
359
362
|
export type PreparedFunnelSeries = {
|
|
360
363
|
type: FunnelSeries['type'];
|
|
361
364
|
data: FunnelSeriesData;
|
|
365
|
+
shape: Required<FunnelSeries>['shape'];
|
|
362
366
|
dataLabels: {
|
|
363
367
|
enabled: boolean;
|
|
364
368
|
style: BaseTextStyle;
|
|
@@ -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();
|
|
@@ -19,10 +19,11 @@ function getAreaConnectorPath(args) {
|
|
|
19
19
|
return p;
|
|
20
20
|
}
|
|
21
21
|
export async function prepareFunnelData(args) {
|
|
22
|
-
var _a, _b, _c, _d;
|
|
22
|
+
var _a, _b, _c, _d, _e;
|
|
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,48 +43,80 @@ 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;
|
|
98
|
+
const isTrapezoid = ((_d = series[0]) === null || _d === void 0 ? void 0 : _d.shape) === 'trapezoid';
|
|
99
|
+
const getItemWidth = (index) => (segmentMaxWidth * series[index].data.value) / maxValue;
|
|
78
100
|
for (let index = 0; index < series.length; index++) {
|
|
79
101
|
const s = series[index];
|
|
80
102
|
const d = s.data;
|
|
81
|
-
const itemWidth = (
|
|
103
|
+
const itemWidth = getItemWidth(index);
|
|
104
|
+
const centerX = segmentLeftOffset + segmentMaxWidth / 2;
|
|
105
|
+
const segmentY = getSegmentY(index);
|
|
106
|
+
const isLastSegment = index === series.length - 1;
|
|
107
|
+
const bottomWidth = isTrapezoid && !isLastSegment ? getItemWidth(index + 1) : itemWidth;
|
|
108
|
+
const points = [
|
|
109
|
+
[centerX - itemWidth / 2, segmentY],
|
|
110
|
+
[centerX + itemWidth / 2, segmentY],
|
|
111
|
+
[centerX + bottomWidth / 2, segmentY + itemHeight],
|
|
112
|
+
[centerX - bottomWidth / 2, segmentY + itemHeight],
|
|
113
|
+
];
|
|
82
114
|
const funnelSegment = {
|
|
83
|
-
x:
|
|
84
|
-
y:
|
|
115
|
+
x: centerX - itemWidth / 2,
|
|
116
|
+
y: segmentY,
|
|
85
117
|
width: itemWidth,
|
|
86
118
|
height: itemHeight,
|
|
119
|
+
points,
|
|
87
120
|
color: s.color,
|
|
88
121
|
series: s,
|
|
89
122
|
data: d,
|
|
@@ -94,7 +127,7 @@ export async function prepareFunnelData(args) {
|
|
|
94
127
|
items.push(funnelSegment);
|
|
95
128
|
const prevSeries = series[index - 1];
|
|
96
129
|
const prevItem = items[index - 1];
|
|
97
|
-
if (prevSeries && prevItem && ((
|
|
130
|
+
if (prevSeries && prevItem && ((_e = prevSeries.connectors) === null || _e === void 0 ? void 0 : _e.enabled)) {
|
|
98
131
|
const connectorPoints = [
|
|
99
132
|
[prevItem.x, prevItem.y + prevItem.height],
|
|
100
133
|
[prevItem.x + prevItem.width, prevItem.y + prevItem.height],
|
|
@@ -117,6 +150,7 @@ export async function prepareFunnelData(args) {
|
|
|
117
150
|
type: 'funnel',
|
|
118
151
|
items,
|
|
119
152
|
svgLabels,
|
|
153
|
+
htmlLabels,
|
|
120
154
|
connectors,
|
|
121
155
|
};
|
|
122
156
|
return data;
|
|
@@ -11,13 +11,10 @@ export function renderFunnel(elements, preparedData, seriesOptions, dispatcher)
|
|
|
11
11
|
svgElement.selectAll('*').remove();
|
|
12
12
|
// funnel levels
|
|
13
13
|
const cellsSelection = svgElement
|
|
14
|
-
.selectAll('
|
|
14
|
+
.selectAll('polygon')
|
|
15
15
|
.data(preparedData.items)
|
|
16
|
-
.join('
|
|
17
|
-
.attr('
|
|
18
|
-
.attr('y', (d) => d.y)
|
|
19
|
-
.attr('height', (d) => d.height)
|
|
20
|
-
.attr('width', (d) => d.width)
|
|
16
|
+
.join('polygon')
|
|
17
|
+
.attr('points', (d) => d.points.map((p) => p.join(',')).join(' '))
|
|
21
18
|
.attr('fill', (d) => d.color)
|
|
22
19
|
.attr('stroke', (d) => d.borderColor)
|
|
23
20
|
.attr('stroke-width', (d) => d.borderWidth);
|
|
@@ -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 = {
|
|
@@ -7,6 +7,7 @@ export type FunnelItemData = {
|
|
|
7
7
|
y: number;
|
|
8
8
|
width: number;
|
|
9
9
|
height: number;
|
|
10
|
+
points: [number, number][];
|
|
10
11
|
color: string;
|
|
11
12
|
series: PreparedFunnelSeries;
|
|
12
13
|
data: FunnelSeriesData;
|
|
@@ -29,4 +30,5 @@ export type PreparedFunnelData = {
|
|
|
29
30
|
items: FunnelItemData[];
|
|
30
31
|
connectors: FunnelConnectorData[];
|
|
31
32
|
svgLabels: LabelData[];
|
|
33
|
+
htmlLabels: HtmlItem[];
|
|
32
34
|
};
|
|
@@ -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
|
+
}
|
|
@@ -21,6 +21,23 @@ export interface FunnelSeries<T = MeaningfulAny> extends Omit<BaseSeries, 'dataL
|
|
|
21
21
|
name?: string;
|
|
22
22
|
/** The color of the funnel series. */
|
|
23
23
|
color?: string;
|
|
24
|
+
/**
|
|
25
|
+
* The visual shape of funnel segments.
|
|
26
|
+
*
|
|
27
|
+
* - `'rectangle'` (**recommended**): each segment is an independent rectangle whose
|
|
28
|
+
* width is directly proportional to its value. The human eye reads width as a linear
|
|
29
|
+
* scale, making comparisons between segments accurate and effortless.
|
|
30
|
+
*
|
|
31
|
+
* - `'trapezoid'`: adjacent segments are drawn as connected trapezoids, giving the chart
|
|
32
|
+
* a classic "funnel" silhouette. However, this shape distorts perception: the slanted
|
|
33
|
+
* sides cause viewers to judge area (which grows as the square of width) rather than
|
|
34
|
+
* width alone, exaggerating differences between large and small values. Use only for
|
|
35
|
+
* decorative purposes or when visual familiarity with the funnel metaphor is more
|
|
36
|
+
* important than analytical precision.
|
|
37
|
+
*
|
|
38
|
+
* @default 'rectangle'
|
|
39
|
+
*/
|
|
40
|
+
shape?: 'rectangle' | 'trapezoid';
|
|
24
41
|
/** Lines or areas connecting the funnel segments. */
|
|
25
42
|
connectors?: {
|
|
26
43
|
enabled?: boolean;
|
|
@@ -39,7 +56,7 @@ export interface FunnelSeries<T = MeaningfulAny> extends Omit<BaseSeries, 'dataL
|
|
|
39
56
|
/** Opacity for the connector area. */
|
|
40
57
|
areaOpacity?: number;
|
|
41
58
|
};
|
|
42
|
-
dataLabels?: Omit<BaseDataLabels, '
|
|
59
|
+
dataLabels?: Omit<BaseDataLabels, 'allowOverlap'> & {
|
|
43
60
|
/** Horizontal alignment of the data labels. */
|
|
44
61
|
align?: 'left' | 'center' | 'right';
|
|
45
62
|
};
|
|
@@ -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;
|