@gravity-ui/charts 1.50.0 → 1.51.1
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/utils.js +16 -4
- package/dist/cjs/core/scales/y-scale.js +6 -0
- package/dist/cjs/core/series/prepare-funnel.js +5 -2
- package/dist/cjs/core/series/types.d.ts +1 -0
- package/dist/cjs/core/shapes/area/types.d.ts +3 -3
- package/dist/cjs/core/shapes/bar-x/types.d.ts +3 -3
- package/dist/cjs/core/shapes/bar-y/prepare-data.js +6 -6
- package/dist/cjs/core/shapes/bar-y/types.d.ts +3 -3
- package/dist/cjs/core/shapes/funnel/prepare-data.js +18 -5
- package/dist/cjs/core/shapes/funnel/renderer.js +3 -6
- package/dist/cjs/core/shapes/funnel/types.d.ts +4 -3
- package/dist/cjs/core/shapes/heatmap/prepare-data.js +1 -1
- package/dist/cjs/core/shapes/heatmap/types.d.ts +3 -3
- package/dist/cjs/core/shapes/line/types.d.ts +3 -3
- package/dist/cjs/core/shapes/pie/types.d.ts +3 -3
- package/dist/cjs/core/shapes/radar/types.d.ts +3 -3
- package/dist/cjs/core/shapes/sankey/prepare-data.js +2 -2
- package/dist/cjs/core/shapes/sankey/types.d.ts +3 -3
- package/dist/cjs/core/shapes/scatter/types.d.ts +2 -2
- package/dist/cjs/core/shapes/treemap/prepare-data.js +3 -3
- package/dist/cjs/core/shapes/treemap/types.d.ts +3 -3
- package/dist/cjs/core/shapes/types.d.ts +4 -0
- package/dist/cjs/core/shapes/types.js +1 -0
- package/dist/cjs/core/shapes/waterfall/prepare-data.js +1 -1
- package/dist/cjs/core/shapes/waterfall/types.d.ts +3 -3
- package/dist/cjs/core/shapes/x-range/types.d.ts +3 -3
- package/dist/cjs/core/types/chart/funnel.d.ts +17 -0
- package/dist/cjs/core/utils/axis/x-axis.js +16 -4
- package/dist/cjs/hooks/useShapes/bar-y/index.js +1 -1
- package/dist/cjs/hooks/useShapes/heatmap/index.js +1 -1
- package/dist/cjs/hooks/useShapes/sankey/index.js +1 -1
- package/dist/cjs/hooks/useShapes/treemap/index.js +1 -1
- package/dist/cjs/hooks/useShapes/waterfall/index.js +1 -1
- package/dist/esm/components/AxisY/utils.js +16 -4
- package/dist/esm/core/scales/y-scale.js +6 -0
- package/dist/esm/core/series/prepare-funnel.js +5 -2
- package/dist/esm/core/series/types.d.ts +1 -0
- package/dist/esm/core/shapes/area/types.d.ts +3 -3
- package/dist/esm/core/shapes/bar-x/types.d.ts +3 -3
- package/dist/esm/core/shapes/bar-y/prepare-data.js +6 -6
- package/dist/esm/core/shapes/bar-y/types.d.ts +3 -3
- package/dist/esm/core/shapes/funnel/prepare-data.js +18 -5
- package/dist/esm/core/shapes/funnel/renderer.js +3 -6
- package/dist/esm/core/shapes/funnel/types.d.ts +4 -3
- package/dist/esm/core/shapes/heatmap/prepare-data.js +1 -1
- package/dist/esm/core/shapes/heatmap/types.d.ts +3 -3
- package/dist/esm/core/shapes/line/types.d.ts +3 -3
- package/dist/esm/core/shapes/pie/types.d.ts +3 -3
- package/dist/esm/core/shapes/radar/types.d.ts +3 -3
- package/dist/esm/core/shapes/sankey/prepare-data.js +2 -2
- package/dist/esm/core/shapes/sankey/types.d.ts +3 -3
- package/dist/esm/core/shapes/scatter/types.d.ts +2 -2
- package/dist/esm/core/shapes/treemap/prepare-data.js +3 -3
- package/dist/esm/core/shapes/treemap/types.d.ts +3 -3
- package/dist/esm/core/shapes/types.d.ts +4 -0
- package/dist/esm/core/shapes/types.js +1 -0
- package/dist/esm/core/shapes/waterfall/prepare-data.js +1 -1
- package/dist/esm/core/shapes/waterfall/types.d.ts +3 -3
- package/dist/esm/core/shapes/x-range/types.d.ts +3 -3
- package/dist/esm/core/types/chart/funnel.d.ts +17 -0
- package/dist/esm/core/utils/axis/x-axis.js +16 -4
- package/dist/esm/hooks/useShapes/bar-y/index.js +1 -1
- package/dist/esm/hooks/useShapes/heatmap/index.js +1 -1
- package/dist/esm/hooks/useShapes/sankey/index.js +1 -1
- package/dist/esm/hooks/useShapes/treemap/index.js +1 -1
- package/dist/esm/hooks/useShapes/waterfall/index.js +1 -1
- package/package.json +1 -1
|
@@ -31,8 +31,13 @@ export function getTickValues({ scale, axis, labelLineHeight, series, }) {
|
|
|
31
31
|
let result = originalTickValues;
|
|
32
32
|
let availableSpaceForLabel = getMinSpaceBetween(result, (d) => d.y) - axis.labels.padding * 2;
|
|
33
33
|
let ticksCount = result.length - 1;
|
|
34
|
+
let lastMultiTickResult = result;
|
|
35
|
+
const triedCounts = new Set();
|
|
34
36
|
while (availableSpaceForLabel < labelLineHeight && result.length > 1) {
|
|
35
37
|
ticksCount = ticksCount ? ticksCount - 1 : result.length - 1;
|
|
38
|
+
if (triedCounts.has(ticksCount))
|
|
39
|
+
break;
|
|
40
|
+
triedCounts.add(ticksCount);
|
|
36
41
|
const newScaleTicks = scale.ticks(ticksCount);
|
|
37
42
|
result = newScaleTicks.map((t) => ({
|
|
38
43
|
y: scale(t),
|
|
@@ -40,16 +45,23 @@ export function getTickValues({ scale, axis, labelLineHeight, series, }) {
|
|
|
40
45
|
}));
|
|
41
46
|
availableSpaceForLabel =
|
|
42
47
|
getMinSpaceBetween(result, (d) => d.y) - axis.labels.padding * 2;
|
|
48
|
+
if (result.length > 1) {
|
|
49
|
+
lastMultiTickResult = result;
|
|
50
|
+
}
|
|
43
51
|
}
|
|
44
52
|
// when this is not possible (for example, such values cannot be selected for the logarithmic axis with a small range)
|
|
45
|
-
// just thin out the
|
|
46
|
-
|
|
47
|
-
|
|
53
|
+
// just thin out the last result that had multiple ticks
|
|
54
|
+
// For log scales, 1 tick is also a failure: d3 only places ticks at powers of the base,
|
|
55
|
+
// so a sub-decade range legitimately yields 1 tick despite having room for more.
|
|
56
|
+
// For linear scales, 1 tick is a valid result meaning the chart is simply too small.
|
|
57
|
+
const isLogScale = axis.type === 'logarithmic';
|
|
58
|
+
if ((isLogScale ? result.length <= 1 : !result.length) && lastMultiTickResult.length > 1) {
|
|
59
|
+
result = lastMultiTickResult;
|
|
48
60
|
availableSpaceForLabel =
|
|
49
61
|
getMinSpaceBetween(result, (d) => d.y) - axis.labels.padding * 2;
|
|
50
62
|
let delta = 2;
|
|
51
63
|
while (availableSpaceForLabel < labelLineHeight && result.length > 1) {
|
|
52
|
-
result = thinOut(
|
|
64
|
+
result = thinOut(lastMultiTickResult, delta);
|
|
53
65
|
if (result.length > 1) {
|
|
54
66
|
delta += 1;
|
|
55
67
|
availableSpaceForLabel =
|
|
@@ -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
|
}
|
|
@@ -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),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { AreaSeriesData,
|
|
1
|
+
import type { AreaSeriesData, LabelData } from '../../../types';
|
|
2
2
|
import type { AnnotationAnchor, PreparedAnnotation, PreparedAreaSeries } from '../../series/types';
|
|
3
|
+
import type { SeriesShapeData } from '../types';
|
|
3
4
|
export type PointData = {
|
|
4
5
|
annotation?: PreparedAnnotation;
|
|
5
6
|
color?: string;
|
|
@@ -31,5 +32,4 @@ export type PreparedAreaData = {
|
|
|
31
32
|
hovered: boolean;
|
|
32
33
|
active: boolean;
|
|
33
34
|
svgLabels: LabelData[];
|
|
34
|
-
|
|
35
|
-
};
|
|
35
|
+
} & SeriesShapeData;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LabelData, TooltipDataChunkBarX } from '../../../types';
|
|
2
2
|
import type { PreparedAnnotation, PreparedBarXSeries } from '../../series/types';
|
|
3
|
+
import type { SeriesShapeData } from '../types';
|
|
3
4
|
export type PreparedBarXData = Omit<TooltipDataChunkBarX, 'series'> & {
|
|
4
5
|
annotation?: PreparedAnnotation;
|
|
5
6
|
x: number;
|
|
@@ -9,11 +10,10 @@ export type PreparedBarXData = Omit<TooltipDataChunkBarX, 'series'> & {
|
|
|
9
10
|
opacity: number | null;
|
|
10
11
|
series: PreparedBarXSeries;
|
|
11
12
|
svgLabels: LabelData[];
|
|
12
|
-
htmlLabels: HtmlItem[];
|
|
13
13
|
isLastStackItem: boolean;
|
|
14
14
|
/**
|
|
15
15
|
* the utility field for storing the original height (for recalculations, etc.)
|
|
16
16
|
* should not be used for displaying
|
|
17
17
|
*/
|
|
18
18
|
_height: number;
|
|
19
|
-
};
|
|
19
|
+
} & SeriesShapeData;
|
|
@@ -13,7 +13,7 @@ export async function prepareBarYData(args) {
|
|
|
13
13
|
return {
|
|
14
14
|
shapes: [],
|
|
15
15
|
labels: [],
|
|
16
|
-
|
|
16
|
+
htmlLabels: [],
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
19
|
const sortingOptions = get(seriesOptions, 'bar-y.dataSorting');
|
|
@@ -134,7 +134,7 @@ export async function prepareBarYData(args) {
|
|
|
134
134
|
});
|
|
135
135
|
});
|
|
136
136
|
let labels = [];
|
|
137
|
-
let
|
|
137
|
+
let htmlLabels = [];
|
|
138
138
|
const map = new Map();
|
|
139
139
|
for (let i = 0; i < result.length; i++) {
|
|
140
140
|
const prepared = result[i];
|
|
@@ -160,7 +160,7 @@ export async function prepareBarYData(args) {
|
|
|
160
160
|
x,
|
|
161
161
|
y: y - height / 2,
|
|
162
162
|
});
|
|
163
|
-
|
|
163
|
+
htmlLabels.push({
|
|
164
164
|
content,
|
|
165
165
|
size: { width, height },
|
|
166
166
|
style: dataLabels.style,
|
|
@@ -202,12 +202,12 @@ export async function prepareBarYData(args) {
|
|
|
202
202
|
if (labels.length && !allowOverlap) {
|
|
203
203
|
labels = filterOverlappingLabels(labels);
|
|
204
204
|
}
|
|
205
|
-
else if (
|
|
206
|
-
|
|
205
|
+
else if (htmlLabels.length && !allowOverlap) {
|
|
206
|
+
htmlLabels = filterOverlappingLabels(htmlLabels);
|
|
207
207
|
}
|
|
208
208
|
return {
|
|
209
209
|
shapes: result,
|
|
210
210
|
labels,
|
|
211
|
-
|
|
211
|
+
htmlLabels,
|
|
212
212
|
};
|
|
213
213
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LabelData, TooltipDataChunkBarY } from '../../../types';
|
|
2
2
|
import type { PreparedBarYSeries } from '../../series/types';
|
|
3
|
+
import type { SeriesShapeData } from '../types';
|
|
3
4
|
export type PreparedBarYData = Omit<TooltipDataChunkBarY, 'series'> & {
|
|
4
5
|
x: number;
|
|
5
6
|
y: number;
|
|
@@ -15,5 +16,4 @@ export type PreparedBarYData = Omit<TooltipDataChunkBarY, 'series'> & {
|
|
|
15
16
|
export type BarYShapesArgs = {
|
|
16
17
|
shapes: PreparedBarYData[];
|
|
17
18
|
labels: LabelData[];
|
|
18
|
-
|
|
19
|
-
};
|
|
19
|
+
} & SeriesShapeData;
|
|
@@ -19,7 +19,7 @@ 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 = [];
|
|
@@ -95,15 +95,28 @@ export async function prepareFunnelData(args) {
|
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
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;
|
|
98
100
|
for (let index = 0; index < series.length; index++) {
|
|
99
101
|
const s = series[index];
|
|
100
102
|
const d = s.data;
|
|
101
|
-
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
|
+
];
|
|
102
114
|
const funnelSegment = {
|
|
103
|
-
x:
|
|
104
|
-
y:
|
|
115
|
+
x: centerX - itemWidth / 2,
|
|
116
|
+
y: segmentY,
|
|
105
117
|
width: itemWidth,
|
|
106
118
|
height: itemHeight,
|
|
119
|
+
points,
|
|
107
120
|
color: s.color,
|
|
108
121
|
series: s,
|
|
109
122
|
data: d,
|
|
@@ -114,7 +127,7 @@ export async function prepareFunnelData(args) {
|
|
|
114
127
|
items.push(funnelSegment);
|
|
115
128
|
const prevSeries = series[index - 1];
|
|
116
129
|
const prevItem = items[index - 1];
|
|
117
|
-
if (prevSeries && prevItem && ((
|
|
130
|
+
if (prevSeries && prevItem && ((_e = prevSeries.connectors) === null || _e === void 0 ? void 0 : _e.enabled)) {
|
|
118
131
|
const connectorPoints = [
|
|
119
132
|
[prevItem.x, prevItem.y + prevItem.height],
|
|
120
133
|
[prevItem.x + prevItem.width, prevItem.y + prevItem.height],
|
|
@@ -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,12 +1,14 @@
|
|
|
1
1
|
import type { Path } from 'd3-path';
|
|
2
|
-
import type { FunnelSeriesData,
|
|
2
|
+
import type { FunnelSeriesData, LabelData } from '../../../types';
|
|
3
3
|
import type { DashStyle } from '../../constants';
|
|
4
4
|
import type { PreparedFunnelSeries } from '../../series/types';
|
|
5
|
+
import type { SeriesShapeData } from '../types';
|
|
5
6
|
export type FunnelItemData = {
|
|
6
7
|
x: number;
|
|
7
8
|
y: number;
|
|
8
9
|
width: number;
|
|
9
10
|
height: number;
|
|
11
|
+
points: [number, number][];
|
|
10
12
|
color: string;
|
|
11
13
|
series: PreparedFunnelSeries;
|
|
12
14
|
data: FunnelSeriesData;
|
|
@@ -29,5 +31,4 @@ export type PreparedFunnelData = {
|
|
|
29
31
|
items: FunnelItemData[];
|
|
30
32
|
connectors: FunnelConnectorData[];
|
|
31
33
|
svgLabels: LabelData[];
|
|
32
|
-
|
|
33
|
-
};
|
|
34
|
+
} & SeriesShapeData;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { BaseTextStyle, HeatmapSeriesData
|
|
1
|
+
import type { BaseTextStyle, HeatmapSeriesData } from '../../../types';
|
|
2
2
|
import type { PreparedHeatmapSeries } from '../../series/types';
|
|
3
|
+
import type { SeriesShapeData } from '../types';
|
|
3
4
|
export type HeatmapItem = {
|
|
4
5
|
x: number;
|
|
5
6
|
y: number;
|
|
@@ -20,6 +21,5 @@ export type HeatmapLabel = {
|
|
|
20
21
|
export type PreparedHeatmapData = {
|
|
21
22
|
series: PreparedHeatmapSeries;
|
|
22
23
|
items: HeatmapItem[];
|
|
23
|
-
htmlElements: HtmlItem[];
|
|
24
24
|
labels: HeatmapLabel[];
|
|
25
|
-
};
|
|
25
|
+
} & SeriesShapeData;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LabelData, LineSeriesData, LineSeriesLineBaseStyle } from '../../../types';
|
|
2
2
|
import type { DashStyle, LineCap, LineJoin } from '../../constants';
|
|
3
3
|
import type { AnnotationAnchor, PreparedAnnotation, PreparedLineSeries } from '../../series/types';
|
|
4
|
+
import type { SeriesShapeData } from '../types';
|
|
4
5
|
export type PointData = {
|
|
5
6
|
annotation?: PreparedAnnotation;
|
|
6
7
|
color?: string;
|
|
@@ -29,9 +30,8 @@ export type PreparedLineData = {
|
|
|
29
30
|
hovered: boolean;
|
|
30
31
|
active: boolean;
|
|
31
32
|
svgLabels: LabelData[];
|
|
32
|
-
htmlLabels: HtmlItem[];
|
|
33
33
|
color: string;
|
|
34
34
|
dashStyle: DashStyle;
|
|
35
35
|
linecap: LineCap;
|
|
36
36
|
linejoin: LineJoin;
|
|
37
|
-
} & Required<LineSeriesLineBaseStyle
|
|
37
|
+
} & Required<LineSeriesLineBaseStyle> & SeriesShapeData;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { PieArcDatum } from 'd3-shape';
|
|
2
|
-
import type { ConnectorCurve,
|
|
2
|
+
import type { ConnectorCurve, LabelData } from '../../../types';
|
|
3
3
|
import type { PreparedPieSeries } from '../../series/types';
|
|
4
|
+
import type { SeriesShapeData } from '../types';
|
|
4
5
|
export type SegmentData = {
|
|
5
6
|
value: number;
|
|
6
7
|
color: string;
|
|
@@ -37,5 +38,4 @@ export type PreparedPieData = {
|
|
|
37
38
|
opacity: number;
|
|
38
39
|
size: number;
|
|
39
40
|
};
|
|
40
|
-
|
|
41
|
-
};
|
|
41
|
+
} & SeriesShapeData;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LabelData, RadarSeriesData } from '../../../types';
|
|
2
2
|
import type { PreparedRadarSeries } from '../../series/types';
|
|
3
|
+
import type { SeriesShapeData } from '../types';
|
|
3
4
|
export type RadarLabelData = LabelData & {
|
|
4
5
|
maxWidth: number;
|
|
5
6
|
};
|
|
@@ -54,6 +55,5 @@ export type PreparedRadarData = {
|
|
|
54
55
|
grid: RadarGridData[];
|
|
55
56
|
center: [number, number];
|
|
56
57
|
radius: number;
|
|
57
|
-
htmlLabels: HtmlItem[];
|
|
58
58
|
cursor: string | null;
|
|
59
|
-
};
|
|
59
|
+
} & SeriesShapeData;
|
|
@@ -2,7 +2,7 @@ import { getFormattedValue } from '../../utils/format';
|
|
|
2
2
|
import { sankey, sankeyLinkHorizontal } from './sankey-layout';
|
|
3
3
|
export function prepareSankeyData(args) {
|
|
4
4
|
const { series, width, height } = args;
|
|
5
|
-
const
|
|
5
|
+
const htmlLabels = [];
|
|
6
6
|
const sankeyGenerator = sankey()
|
|
7
7
|
.nodeId((d) => d.name)
|
|
8
8
|
.nodeSort((a, b) => { var _a, _b; return ((_a = a.index) !== null && _a !== void 0 ? _a : 0) - ((_b = b.index) !== null && _b !== void 0 ? _b : 0); })
|
|
@@ -70,5 +70,5 @@ export function prepareSankeyData(args) {
|
|
|
70
70
|
});
|
|
71
71
|
dataLabels.push(...labels);
|
|
72
72
|
}
|
|
73
|
-
return { series, nodes: sankeyNodes, links: sankeyLinks,
|
|
73
|
+
return { series, nodes: sankeyNodes, links: sankeyLinks, htmlLabels, labels: dataLabels };
|
|
74
74
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type { BaseTextStyle,
|
|
1
|
+
import type { BaseTextStyle, SankeySeriesData } from '../../../types';
|
|
2
2
|
import type { PreparedSankeySeries } from '../../series/types';
|
|
3
|
+
import type { SeriesShapeData } from '../types';
|
|
3
4
|
export type SankeyDataLabel = {
|
|
4
5
|
text: string;
|
|
5
6
|
x: number;
|
|
@@ -26,8 +27,7 @@ export type SankeyLink = {
|
|
|
26
27
|
};
|
|
27
28
|
export type PreparedSankeyData = {
|
|
28
29
|
series: PreparedSankeySeries;
|
|
29
|
-
htmlElements: HtmlItem[];
|
|
30
30
|
nodes: SankeyNode[];
|
|
31
31
|
links: SankeyLink[];
|
|
32
32
|
labels: SankeyDataLabel[];
|
|
33
|
-
};
|
|
33
|
+
} & SeriesShapeData;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { HtmlItem, LabelData, ScatterSeriesData } from '../../../types';
|
|
2
2
|
import type { PreparedScatterSeries } from '../../series/types';
|
|
3
|
+
import type { SeriesShapeData } from '../types';
|
|
3
4
|
type PointData = {
|
|
4
5
|
x: number;
|
|
5
6
|
y: number;
|
|
@@ -19,6 +20,5 @@ export type PreparedScatterData = MarkerData;
|
|
|
19
20
|
export type PreparedScatterShapeData = {
|
|
20
21
|
markers: PreparedScatterData[];
|
|
21
22
|
svgLabels: LabelData[];
|
|
22
|
-
|
|
23
|
-
};
|
|
23
|
+
} & SeriesShapeData;
|
|
24
24
|
export {};
|
|
@@ -148,17 +148,17 @@ export async function prepareTreemapData(args) {
|
|
|
148
148
|
})(hierarchy);
|
|
149
149
|
const leaves = root.leaves();
|
|
150
150
|
let labelData = [];
|
|
151
|
-
const
|
|
151
|
+
const htmlLabels = [];
|
|
152
152
|
if ((_a = series.dataLabels) === null || _a === void 0 ? void 0 : _a.enabled) {
|
|
153
153
|
const { html, style: dataLabelsStyle } = series.dataLabels;
|
|
154
154
|
const labels = await getLabels({ data: leaves, options: series.dataLabels });
|
|
155
155
|
if (html) {
|
|
156
156
|
const htmlItems = labels.map((l) => (Object.assign({ style: Object.assign(Object.assign({}, dataLabelsStyle), { maxWidth: l.size.width, maxHeight: l.size.height, overflow: 'hidden' }) }, l)));
|
|
157
|
-
|
|
157
|
+
htmlLabels.push(...htmlItems);
|
|
158
158
|
}
|
|
159
159
|
else {
|
|
160
160
|
labelData = labels;
|
|
161
161
|
}
|
|
162
162
|
}
|
|
163
|
-
return { labelData, leaves, series,
|
|
163
|
+
return { labelData, leaves, series, htmlLabels };
|
|
164
164
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { HierarchyRectangularNode } from 'd3-hierarchy';
|
|
2
|
-
import type {
|
|
2
|
+
import type { TreemapSeriesData } from '../../../types';
|
|
3
3
|
import type { PreparedTreemapSeries } from '../../series/types';
|
|
4
|
+
import type { SeriesShapeData } from '../types';
|
|
4
5
|
export type TreemapLabelData = {
|
|
5
6
|
text: string;
|
|
6
7
|
x: number;
|
|
@@ -11,5 +12,4 @@ export type PreparedTreemapData = {
|
|
|
11
12
|
labelData: TreemapLabelData[];
|
|
12
13
|
leaves: HierarchyRectangularNode<TreemapSeriesData<any>>[];
|
|
13
14
|
series: PreparedTreemapSeries;
|
|
14
|
-
|
|
15
|
-
};
|
|
15
|
+
} & SeriesShapeData;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -144,7 +144,7 @@ export const prepareWaterfallData = async (args) => {
|
|
|
144
144
|
data: item.data,
|
|
145
145
|
series: item.series,
|
|
146
146
|
subTotal: totalValue,
|
|
147
|
-
|
|
147
|
+
htmlLabels: [],
|
|
148
148
|
};
|
|
149
149
|
preparedData.label = await getLabelData(preparedData, plotHeight);
|
|
150
150
|
result.push(preparedData);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LabelData, WaterfallSeriesData } from '../../../types';
|
|
2
2
|
import type { PreparedWaterfallSeries } from '../../series/types';
|
|
3
|
+
import type { SeriesShapeData } from '../types';
|
|
3
4
|
export type PreparedWaterfallData = {
|
|
4
5
|
x: number;
|
|
5
6
|
y: number;
|
|
@@ -10,5 +11,4 @@ export type PreparedWaterfallData = {
|
|
|
10
11
|
data: WaterfallSeriesData;
|
|
11
12
|
label?: LabelData;
|
|
12
13
|
subTotal: number;
|
|
13
|
-
|
|
14
|
-
};
|
|
14
|
+
} & SeriesShapeData;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { LabelData, TooltipDataChunkXRange } from '../../../types';
|
|
2
2
|
import type { PreparedXRangeSeries } from '../../series/types';
|
|
3
|
+
import type { SeriesShapeData } from '../types';
|
|
3
4
|
export type PreparedXRangeData = Omit<TooltipDataChunkXRange, 'series'> & {
|
|
4
5
|
x: number;
|
|
5
6
|
y: number;
|
|
@@ -8,5 +9,4 @@ export type PreparedXRangeData = Omit<TooltipDataChunkXRange, 'series'> & {
|
|
|
8
9
|
color: string;
|
|
9
10
|
series: PreparedXRangeSeries;
|
|
10
11
|
svgLabels: LabelData[];
|
|
11
|
-
|
|
12
|
-
};
|
|
12
|
+
} & SeriesShapeData;
|
|
@@ -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;
|
|
@@ -55,8 +55,13 @@ export function getXAxisTickValues({ axis, labelLineHeight, scale, series, }) {
|
|
|
55
55
|
let result = originalTickValues;
|
|
56
56
|
let availableSpaceForLabel = getMinSpaceBetween(result, (d) => d.x) - axis.labels.padding * 2;
|
|
57
57
|
let ticksCount = result.length - 1;
|
|
58
|
+
let lastMultiTickResult = result;
|
|
59
|
+
const triedCounts = new Set();
|
|
58
60
|
while (availableSpaceForLabel < labelLineHeight && result.length > 1) {
|
|
59
61
|
ticksCount = ticksCount ? ticksCount - 1 : result.length - 1;
|
|
62
|
+
if (triedCounts.has(ticksCount))
|
|
63
|
+
break;
|
|
64
|
+
triedCounts.add(ticksCount);
|
|
60
65
|
const newScaleTicks = getScaleTicks({ scale, ticksCount, dateTimeLabelFormats });
|
|
61
66
|
result = newScaleTicks.map((t) => ({
|
|
62
67
|
x: scale(t),
|
|
@@ -64,16 +69,23 @@ export function getXAxisTickValues({ axis, labelLineHeight, scale, series, }) {
|
|
|
64
69
|
}));
|
|
65
70
|
availableSpaceForLabel =
|
|
66
71
|
getMinSpaceBetween(result, (d) => d.x) - axis.labels.padding * 2;
|
|
72
|
+
if (result.length > 1) {
|
|
73
|
+
lastMultiTickResult = result;
|
|
74
|
+
}
|
|
67
75
|
}
|
|
68
76
|
// when this is not possible (for example, such values cannot be selected for the logarithmic axis with a small range)
|
|
69
|
-
// just thin out the
|
|
70
|
-
|
|
71
|
-
|
|
77
|
+
// just thin out the last result that had multiple ticks
|
|
78
|
+
// For log scales, 1 tick is also a failure: d3 only places ticks at powers of the base,
|
|
79
|
+
// so a sub-decade range legitimately yields 1 tick despite having room for more.
|
|
80
|
+
// For linear scales, 1 tick is a valid result meaning the chart is simply too small.
|
|
81
|
+
const isLogScale = axis.type === 'logarithmic';
|
|
82
|
+
if ((isLogScale ? result.length <= 1 : !result.length) && lastMultiTickResult.length > 1) {
|
|
83
|
+
result = lastMultiTickResult;
|
|
72
84
|
availableSpaceForLabel =
|
|
73
85
|
getMinSpaceBetween(result, (d) => d.x) - axis.labels.padding * 2;
|
|
74
86
|
let delta = 2;
|
|
75
87
|
while (availableSpaceForLabel < labelLineHeight && result.length > 1) {
|
|
76
|
-
result = thinOut(
|
|
88
|
+
result = thinOut(lastMultiTickResult, delta);
|
|
77
89
|
if (result.length > 1) {
|
|
78
90
|
delta += 1;
|
|
79
91
|
availableSpaceForLabel =
|
|
@@ -5,7 +5,7 @@ import { HtmlLayer } from '../HtmlLayer';
|
|
|
5
5
|
export { prepareBarYData } from '../../../core/shapes/bar-y/prepare-data';
|
|
6
6
|
const b = block('bar-y');
|
|
7
7
|
export function BarYSeriesShapes(args) {
|
|
8
|
-
const { dispatcher, preparedData: { htmlElements }, seriesOptions, htmlLayout, clipPathId, } = args;
|
|
8
|
+
const { dispatcher, preparedData: { htmlLabels: htmlElements }, seriesOptions, htmlLayout, clipPathId, } = args;
|
|
9
9
|
const ref = React.useRef(null);
|
|
10
10
|
React.useEffect(() => {
|
|
11
11
|
if (!ref.current) {
|
|
@@ -16,5 +16,5 @@ export const HeatmapSeriesShapes = (args) => {
|
|
|
16
16
|
}, [dispatcher, preparedData, seriesOptions]);
|
|
17
17
|
return (React.createElement(React.Fragment, null,
|
|
18
18
|
React.createElement("g", { ref: ref, className: b() }),
|
|
19
|
-
React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
|
|
19
|
+
React.createElement(HtmlLayer, { preparedData: { htmlElements: preparedData.htmlLabels }, htmlLayout: htmlLayout })));
|
|
20
20
|
};
|
|
@@ -14,5 +14,5 @@ export const SankeySeriesShape = (props) => {
|
|
|
14
14
|
}, [dispatcher, preparedData, seriesOptions]);
|
|
15
15
|
return (React.createElement(React.Fragment, null,
|
|
16
16
|
React.createElement("g", { ref: ref, className: b() }),
|
|
17
|
-
React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
|
|
17
|
+
React.createElement(HtmlLayer, { preparedData: { htmlElements: preparedData.htmlLabels }, htmlLayout: htmlLayout })));
|
|
18
18
|
};
|
|
@@ -14,5 +14,5 @@ export const TreemapSeriesShape = (props) => {
|
|
|
14
14
|
}, [dispatcher, preparedData, seriesOptions]);
|
|
15
15
|
return (React.createElement(React.Fragment, null,
|
|
16
16
|
React.createElement("g", { ref: ref, className: b() }),
|
|
17
|
-
React.createElement(HtmlLayer, { preparedData: preparedData, htmlLayout: htmlLayout })));
|
|
17
|
+
React.createElement(HtmlLayer, { preparedData: { htmlElements: preparedData.htmlLabels }, htmlLayout: htmlLayout })));
|
|
18
18
|
};
|
|
@@ -19,7 +19,7 @@ export const WaterfallSeriesShapes = (args) => {
|
|
|
19
19
|
return renderWaterfall({ plot: ref.current }, preparedData, seriesOptions, allowOverlapDataLabels, dispatcher);
|
|
20
20
|
}, [allowOverlapDataLabels, dispatcher, preparedData, seriesOptions]);
|
|
21
21
|
const htmlLayerData = React.useMemo(() => {
|
|
22
|
-
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.
|
|
22
|
+
const items = preparedData.map((d) => d === null || d === void 0 ? void 0 : d.htmlLabels).flat();
|
|
23
23
|
if (allowOverlapDataLabels) {
|
|
24
24
|
return { htmlElements: items };
|
|
25
25
|
}
|