@gravity-ui/charts 1.47.0 → 1.48.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/AxisX/prepare-axis-data.js +9 -6
- package/dist/cjs/components/AxisY/prepare-axis-data.js +11 -4
- package/dist/cjs/core/axes/types.d.ts +4 -2
- package/dist/cjs/core/axes/x-axis.js +2 -0
- package/dist/cjs/core/axes/y-axis.js +2 -0
- package/dist/cjs/core/series/prepare-scatter.js +11 -3
- package/dist/cjs/core/series/types.d.ts +8 -0
- package/dist/cjs/core/shapes/area/prepare-data.js +2 -49
- package/dist/cjs/core/shapes/bar-x/prepare-data.js +49 -35
- package/dist/cjs/core/shapes/line/prepare-data.js +13 -58
- package/dist/cjs/core/shapes/scatter/prepare-data.d.ts +5 -2
- package/dist/cjs/core/shapes/scatter/prepare-data.js +43 -4
- package/dist/cjs/core/shapes/scatter/renderer.d.ts +2 -2
- package/dist/cjs/core/shapes/scatter/renderer.js +9 -1
- package/dist/cjs/core/shapes/scatter/types.d.ts +6 -1
- package/dist/cjs/core/types/chart/axis.d.ts +20 -0
- package/dist/cjs/core/types/chart/scatter.d.ts +2 -0
- package/dist/cjs/core/utils/data-labels.d.ts +46 -0
- package/dist/cjs/core/utils/data-labels.js +64 -0
- package/dist/cjs/core/utils/index.d.ts +1 -0
- package/dist/cjs/core/utils/index.js +1 -0
- package/dist/cjs/hooks/useShapes/index.js +5 -3
- package/dist/cjs/hooks/useShapes/scatter/index.d.ts +2 -2
- package/dist/cjs/hooks/useShapes/scatter/index.js +4 -1
- package/dist/cjs/hooks/useShapes/styles.css +8 -25
- package/dist/esm/components/AxisX/prepare-axis-data.js +9 -6
- package/dist/esm/components/AxisY/prepare-axis-data.js +11 -4
- package/dist/esm/core/axes/types.d.ts +4 -2
- package/dist/esm/core/axes/x-axis.js +2 -0
- package/dist/esm/core/axes/y-axis.js +2 -0
- package/dist/esm/core/series/prepare-scatter.js +11 -3
- package/dist/esm/core/series/types.d.ts +8 -0
- package/dist/esm/core/shapes/area/prepare-data.js +2 -49
- package/dist/esm/core/shapes/bar-x/prepare-data.js +49 -35
- package/dist/esm/core/shapes/line/prepare-data.js +13 -58
- package/dist/esm/core/shapes/scatter/prepare-data.d.ts +5 -2
- package/dist/esm/core/shapes/scatter/prepare-data.js +43 -4
- package/dist/esm/core/shapes/scatter/renderer.d.ts +2 -2
- package/dist/esm/core/shapes/scatter/renderer.js +9 -1
- package/dist/esm/core/shapes/scatter/types.d.ts +6 -1
- package/dist/esm/core/types/chart/axis.d.ts +20 -0
- package/dist/esm/core/types/chart/scatter.d.ts +2 -0
- package/dist/esm/core/utils/data-labels.d.ts +46 -0
- package/dist/esm/core/utils/data-labels.js +64 -0
- package/dist/esm/core/utils/index.d.ts +1 -0
- package/dist/esm/core/utils/index.js +1 -0
- package/dist/esm/hooks/useShapes/index.js +5 -3
- package/dist/esm/hooks/useShapes/scatter/index.d.ts +2 -2
- package/dist/esm/hooks/useShapes/scatter/index.js +4 -1
- package/dist/esm/hooks/useShapes/styles.css +8 -25
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getUniqId } from '@gravity-ui/uikit';
|
|
2
2
|
import { select } from 'd3-selection';
|
|
3
|
-
import { calculateCos, calculateSin, formatAxisTickLabel, getBandsPosition, getLabelsSize, getMinSpaceBetween, getTextSizeFn, getTextWithElipsis, } from '../../core/utils';
|
|
3
|
+
import { calculateCos, calculateNumericProperty, calculateSin, formatAxisTickLabel, getBandsPosition, getLabelsSize, getMinSpaceBetween, getTextSizeFn, getTextWithElipsis, } from '../../core/utils';
|
|
4
4
|
import { getXAxisTickValues } from '../../core/utils/axis/x-axis';
|
|
5
5
|
import { getMultilineTitleContentRows } from '../utils/axis-title';
|
|
6
6
|
async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxWidth, axisWidth, boundsOffsetLeft, boundsOffsetRight, }) {
|
|
@@ -72,7 +72,7 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxWid
|
|
|
72
72
|
return svgLabel;
|
|
73
73
|
}
|
|
74
74
|
export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRight, boundsWidth, height, scale, series, split, yAxis, }) {
|
|
75
|
-
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
75
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
76
76
|
const xAxisItems = [];
|
|
77
77
|
const splitPlots = (_a = split === null || split === void 0 ? void 0 : split.plots) !== null && _a !== void 0 ? _a : [];
|
|
78
78
|
for (let plotIndex = 0; plotIndex < splitPlots.length; plotIndex++) {
|
|
@@ -267,6 +267,9 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
267
267
|
if (plotBandWidth < 0) {
|
|
268
268
|
continue;
|
|
269
269
|
}
|
|
270
|
+
const perpExtent = (_g = calculateNumericProperty({ value: plotBand.size, base: axisHeight })) !== null && _g !== void 0 ? _g : axisHeight;
|
|
271
|
+
// X axis is positioned at the bottom of the plot area, so 'start' = bottom edge.
|
|
272
|
+
const bandY = plotBand.align === 'end' ? axisTop : axisTop + axisHeight - perpExtent;
|
|
270
273
|
const getPlotLabelSize = getTextSizeFn({ style: plotBand.label.style });
|
|
271
274
|
const labelSize = plotBand.label.text
|
|
272
275
|
? await getPlotLabelSize(plotBand.label.text)
|
|
@@ -274,17 +277,17 @@ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRig
|
|
|
274
277
|
plotBands.push({
|
|
275
278
|
layerPlacement: plotBand.layerPlacement,
|
|
276
279
|
x: Math.max(0, startPos),
|
|
277
|
-
y:
|
|
280
|
+
y: bandY,
|
|
278
281
|
width: plotBandWidth,
|
|
279
|
-
height:
|
|
282
|
+
height: perpExtent,
|
|
280
283
|
color: plotBand.color,
|
|
281
284
|
opacity: plotBand.opacity,
|
|
282
285
|
label: plotBand.label.text
|
|
283
286
|
? {
|
|
284
287
|
text: plotBand.label.text,
|
|
285
288
|
style: plotBand.label.style,
|
|
286
|
-
x: plotBand.label.padding + ((
|
|
287
|
-
y: plotBand.label.padding + ((
|
|
289
|
+
x: plotBand.label.padding + ((_h = labelSize === null || labelSize === void 0 ? void 0 : labelSize.hangingOffset) !== null && _h !== void 0 ? _h : 0),
|
|
290
|
+
y: plotBand.label.padding + ((_j = labelSize === null || labelSize === void 0 ? void 0 : labelSize.width) !== null && _j !== void 0 ? _j : 0),
|
|
288
291
|
rotate: -90,
|
|
289
292
|
qa: plotBand.label.qa,
|
|
290
293
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { getUniqId } from '@gravity-ui/uikit';
|
|
2
2
|
import { select } from 'd3-selection';
|
|
3
|
-
import { calculateCos, calculateSin, formatAxisTickLabel, getBandsPosition, getLabelsSize, getMinSpaceBetween, getTextSizeFn, getTextWithElipsis, wrapText, } from '../../core/utils';
|
|
3
|
+
import { calculateCos, calculateNumericProperty, calculateSin, formatAxisTickLabel, getBandsPosition, getLabelsSize, getMinSpaceBetween, getTextSizeFn, getTextWithElipsis, wrapText, } from '../../core/utils';
|
|
4
4
|
import { prepareHtmlYAxisTitle, prepareSvgYAxisTitle } from './prepare-axis-title';
|
|
5
5
|
import { getTickValues } from './utils';
|
|
6
6
|
async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxHeight, topOffset, }) {
|
|
@@ -113,7 +113,7 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxHei
|
|
|
113
113
|
return svgLabel;
|
|
114
114
|
}
|
|
115
115
|
export async function prepareYAxisData({ axis, split, scale, top: topOffset, width, height, series, }) {
|
|
116
|
-
var _a, _b, _c, _d, _e;
|
|
116
|
+
var _a, _b, _c, _d, _e, _f;
|
|
117
117
|
const axisPlotTopPosition = ((_a = split === null || split === void 0 ? void 0 : split.plots[axis.plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
|
|
118
118
|
const axisHeight = ((_b = split === null || split === void 0 ? void 0 : split.plots[axis.plotIndex]) === null || _b === void 0 ? void 0 : _b.height) || height;
|
|
119
119
|
const domainX = axis.position === 'left' ? 0 : width;
|
|
@@ -231,11 +231,18 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
|
|
|
231
231
|
if (plotBandHeight < 0) {
|
|
232
232
|
continue;
|
|
233
233
|
}
|
|
234
|
+
const perpExtent = (_f = calculateNumericProperty({ value: plotBand.size, base: width })) !== null && _f !== void 0 ? _f : width;
|
|
235
|
+
// 'start' = at the main Y axis line. For a left axis that's x=0;
|
|
236
|
+
// for a right axis that's x = width - perpExtent. 'end' is mirrored.
|
|
237
|
+
const isLeftAxis = axis.position === 'left';
|
|
238
|
+
const atMainAxis = isLeftAxis ? 0 : width - perpExtent;
|
|
239
|
+
const atOpposite = isLeftAxis ? width - perpExtent : 0;
|
|
240
|
+
const bandX = plotBand.align === 'end' ? atOpposite : atMainAxis;
|
|
234
241
|
const plotBandItem = {
|
|
235
242
|
layerPlacement: plotBand.layerPlacement,
|
|
236
|
-
x:
|
|
243
|
+
x: bandX,
|
|
237
244
|
y: axisPlotTopPosition + top,
|
|
238
|
-
width,
|
|
245
|
+
width: perpExtent,
|
|
239
246
|
height: plotBandHeight,
|
|
240
247
|
color: plotBand.color,
|
|
241
248
|
opacity: plotBand.opacity,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AxisCrosshair, AxisPlotBand, AxisPlotShape, BaseTextStyle, ChartAxis, ChartAxisLabels, ChartAxisRangeSlider, ChartAxisTitleAlignment, ChartAxisTitleRotation, ChartAxisType, DeepRequired, MeaningfulAny, PlotLayerPlacement } from '../../types';
|
|
1
|
+
import type { AxisCrosshair, AxisPlotBand, AxisPlotShape, BaseTextStyle, ChartAxis, ChartAxisLabels, ChartAxisRangeSlider, ChartAxisTitleAlignment, ChartAxisTitleRotation, ChartAxisType, DeepRequired, MeaningfulAny, PlotBandAlign, PlotLayerPlacement } from '../../types';
|
|
2
2
|
import type { DashStyle } from '../constants';
|
|
3
3
|
type PreparedAxisLabels = Omit<ChartAxisLabels, 'enabled' | 'padding' | 'style' | 'autoRotation'> & Required<Pick<ChartAxisLabels, 'enabled' | 'padding' | 'margin' | 'rotation' | 'html'>> & {
|
|
4
4
|
style: BaseTextStyle;
|
|
@@ -8,7 +8,8 @@ type PreparedAxisLabels = Omit<ChartAxisLabels, 'enabled' | 'padding' | 'style'
|
|
|
8
8
|
lineHeight: number;
|
|
9
9
|
maxWidth: number;
|
|
10
10
|
};
|
|
11
|
-
export type PreparedAxisPlotBand = Required<AxisPlotBand
|
|
11
|
+
export type PreparedAxisPlotBand = Required<Omit<AxisPlotBand, 'size' | 'align'>> & {
|
|
12
|
+
align: PlotBandAlign;
|
|
12
13
|
custom?: MeaningfulAny;
|
|
13
14
|
label: {
|
|
14
15
|
text: string;
|
|
@@ -16,6 +17,7 @@ export type PreparedAxisPlotBand = Required<AxisPlotBand> & {
|
|
|
16
17
|
padding: number;
|
|
17
18
|
qa?: string;
|
|
18
19
|
};
|
|
20
|
+
size?: number | string;
|
|
19
21
|
};
|
|
20
22
|
type PreparedAxisCrosshair = Required<AxisCrosshair>;
|
|
21
23
|
export type PreparedAxisPlotLine = {
|
|
@@ -166,6 +166,8 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, boundsWidth,
|
|
|
166
166
|
opacity: get(d, 'opacity', 1),
|
|
167
167
|
from: get(d, 'from', 0),
|
|
168
168
|
to: get(d, 'to', 0),
|
|
169
|
+
align: get(d, 'align', 'start'),
|
|
170
|
+
size: d.size,
|
|
169
171
|
layerPlacement: get(d, 'layerPlacement', 'before'),
|
|
170
172
|
custom: d.custom,
|
|
171
173
|
label: prepareAxisPlotLabel(d),
|
|
@@ -181,6 +181,8 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxi
|
|
|
181
181
|
opacity: get(d, 'opacity', 1),
|
|
182
182
|
from: get(d, 'from', 0),
|
|
183
183
|
to: get(d, 'to', 0),
|
|
184
|
+
align: get(d, 'align', 'start'),
|
|
185
|
+
size: d.size,
|
|
184
186
|
layerPlacement: get(d, 'layerPlacement', 'before'),
|
|
185
187
|
custom: d.custom,
|
|
186
188
|
label: prepareAxisPlotLabel(d),
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
2
|
import merge from 'lodash/merge';
|
|
3
|
-
import { seriesRangeSliderOptionsDefaults } from '../constants';
|
|
3
|
+
import { DEFAULT_DATALABELS_STYLE, seriesRangeSliderOptionsDefaults } from '../constants';
|
|
4
4
|
import { getSymbolType, getUniqId } from '../utils';
|
|
5
|
-
import { DEFAULT_HALO_OPTIONS, DEFAULT_POINT_MARKER_OPTIONS } from './constants';
|
|
5
|
+
import { DEFAULT_DATALABELS_PADDING, DEFAULT_HALO_OPTIONS, DEFAULT_POINT_MARKER_OPTIONS, } from './constants';
|
|
6
6
|
import { prepareLegendSymbol } from './utils';
|
|
7
7
|
function prepareMarker(series, seriesOptions, index) {
|
|
8
8
|
const seriesHoverState = get(seriesOptions, 'scatter.states.hover');
|
|
@@ -39,7 +39,7 @@ function prepareSeriesData(series) {
|
|
|
39
39
|
export function prepareScatterSeries(args) {
|
|
40
40
|
const { colorScale, series, seriesOptions, legend } = args;
|
|
41
41
|
return series.map((s, index) => {
|
|
42
|
-
var _a, _b, _c, _d, _e, _f;
|
|
42
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
43
43
|
const id = getUniqId();
|
|
44
44
|
const name = 'name' in s && s.name ? s.name : '';
|
|
45
45
|
const symbolType = s.symbolType || getSymbolType(index);
|
|
@@ -56,6 +56,14 @@ export function prepareScatterSeries(args) {
|
|
|
56
56
|
itemText: (_f = (_e = s.legend) === null || _e === void 0 ? void 0 : _e.itemText) !== null && _f !== void 0 ? _f : name,
|
|
57
57
|
},
|
|
58
58
|
data: prepareSeriesData(s),
|
|
59
|
+
dataLabels: {
|
|
60
|
+
enabled: ((_g = s.dataLabels) === null || _g === void 0 ? void 0 : _g.enabled) || false,
|
|
61
|
+
style: Object.assign({}, DEFAULT_DATALABELS_STYLE, (_h = s.dataLabels) === null || _h === void 0 ? void 0 : _h.style),
|
|
62
|
+
padding: get(s, 'dataLabels.padding', DEFAULT_DATALABELS_PADDING),
|
|
63
|
+
allowOverlap: get(s, 'dataLabels.allowOverlap', false),
|
|
64
|
+
html: get(s, 'dataLabels.html', false),
|
|
65
|
+
format: (_j = s.dataLabels) === null || _j === void 0 ? void 0 : _j.format,
|
|
66
|
+
},
|
|
59
67
|
marker: prepareMarker(s, seriesOptions, index),
|
|
60
68
|
cursor: get(s, 'cursor', null),
|
|
61
69
|
yAxis: get(s, 'yAxis', 0),
|
|
@@ -101,6 +101,14 @@ type BasePreparedAxisRelatedSeries = {
|
|
|
101
101
|
export type PreparedScatterSeries = {
|
|
102
102
|
type: ScatterSeries['type'];
|
|
103
103
|
data: ScatterSeriesData[];
|
|
104
|
+
dataLabels: {
|
|
105
|
+
enabled: boolean;
|
|
106
|
+
style: BaseTextStyle;
|
|
107
|
+
padding: number;
|
|
108
|
+
allowOverlap: boolean;
|
|
109
|
+
html: boolean;
|
|
110
|
+
format?: ValueFormat;
|
|
111
|
+
};
|
|
104
112
|
marker: {
|
|
105
113
|
states: {
|
|
106
114
|
normal: {
|
|
@@ -3,8 +3,7 @@ import isNil from 'lodash/isNil';
|
|
|
3
3
|
import round from 'lodash/round';
|
|
4
4
|
import { prepareAnnotation } from '../../series/prepare-annotation';
|
|
5
5
|
import { getXValue, getYValue, markHiddenPointsOutOfYRange } from '../../shapes/utils';
|
|
6
|
-
import { getDataCategoryValue,
|
|
7
|
-
import { getFormattedValue } from '../../utils/format';
|
|
6
|
+
import { getDataCategoryValue, preparePointDataLabels } from '../../utils';
|
|
8
7
|
function getXValues(series, xAxis, xScale) {
|
|
9
8
|
const categories = xAxis.categories || [];
|
|
10
9
|
const xValues = series.reduce((acc, s) => {
|
|
@@ -30,52 +29,6 @@ function getXValues(series, xAxis, xScale) {
|
|
|
30
29
|
}
|
|
31
30
|
return sort(Array.from(xValues), (d) => d[1]);
|
|
32
31
|
}
|
|
33
|
-
async function prepareDataLabels({ series, points, xMax, yAxisTop, isOutsideBounds, }) {
|
|
34
|
-
var _a;
|
|
35
|
-
const svgLabels = [];
|
|
36
|
-
const htmlLabels = [];
|
|
37
|
-
const getTextSize = getTextSizeFn({ style: series.dataLabels.style });
|
|
38
|
-
for (let pointsIndex = 0; pointsIndex < points.length; pointsIndex++) {
|
|
39
|
-
const point = points[pointsIndex];
|
|
40
|
-
if (point.y === null || isOutsideBounds(point.x, point.y)) {
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
const text = getFormattedValue(Object.assign({ value: (_a = point.data.label) !== null && _a !== void 0 ? _a : point.data.y }, series.dataLabels));
|
|
44
|
-
if (series.dataLabels.html) {
|
|
45
|
-
const size = await getLabelsSize({
|
|
46
|
-
labels: [text],
|
|
47
|
-
style: series.dataLabels.style,
|
|
48
|
-
html: series.dataLabels.html,
|
|
49
|
-
});
|
|
50
|
-
const labelSize = { width: size.maxWidth, height: size.maxHeight };
|
|
51
|
-
const x = Math.min(xMax - labelSize.width, Math.max(0, point.x - labelSize.width / 2));
|
|
52
|
-
const y = Math.max(yAxisTop, point.y - series.dataLabels.padding - labelSize.height);
|
|
53
|
-
htmlLabels.push({
|
|
54
|
-
x,
|
|
55
|
-
y,
|
|
56
|
-
content: text,
|
|
57
|
-
size: labelSize,
|
|
58
|
-
style: series.dataLabels.style,
|
|
59
|
-
});
|
|
60
|
-
}
|
|
61
|
-
else {
|
|
62
|
-
const labelSize = await getTextSize(text);
|
|
63
|
-
const x = Math.min(xMax - labelSize.width, Math.max(0, point.x - labelSize.width / 2));
|
|
64
|
-
const y = Math.max(yAxisTop, point.y - series.dataLabels.padding - labelSize.height + labelSize.hangingOffset);
|
|
65
|
-
svgLabels.push({
|
|
66
|
-
text,
|
|
67
|
-
x,
|
|
68
|
-
y,
|
|
69
|
-
style: series.dataLabels.style,
|
|
70
|
-
size: labelSize,
|
|
71
|
-
textAnchor: 'start',
|
|
72
|
-
series,
|
|
73
|
-
active: true,
|
|
74
|
-
});
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return { svgLabels, htmlLabels };
|
|
78
|
-
}
|
|
79
32
|
export const prepareAreaData = async (args) => {
|
|
80
33
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p;
|
|
81
34
|
const { series, seriesOptions, xAxis, xScale, yAxis, yScale, split, isOutsideBounds, isRangeSlider, } = args;
|
|
@@ -340,7 +293,7 @@ export const prepareAreaData = async (args) => {
|
|
|
340
293
|
const currentYAxis = yAxis[item.series.yAxis];
|
|
341
294
|
const itemYAxisTop = ((_p = split.plots[currentYAxis.plotIndex]) === null || _p === void 0 ? void 0 : _p.top) || 0;
|
|
342
295
|
if (item.series.dataLabels.enabled && !isRangeSlider) {
|
|
343
|
-
const labelsData = await
|
|
296
|
+
const labelsData = await preparePointDataLabels({
|
|
344
297
|
series: item.series,
|
|
345
298
|
points: item.points,
|
|
346
299
|
xMax,
|
|
@@ -3,37 +3,59 @@ import get from 'lodash/get';
|
|
|
3
3
|
import { prepareAnnotation } from '../../series/prepare-annotation';
|
|
4
4
|
import { getSeriesStackId } from '../../series/utils';
|
|
5
5
|
import { MIN_BAR_GAP, MIN_BAR_GROUP_GAP, MIN_BAR_WIDTH } from '../../shapes/bar-constants';
|
|
6
|
-
import { getDataCategoryValue, getLabelsSize } from '../../utils';
|
|
6
|
+
import { getDataCategoryValue, getLabelsSize, getTextSizeFn } from '../../utils';
|
|
7
7
|
import { getBandSize } from '../../utils/band-size';
|
|
8
8
|
import { getFormattedValue } from '../../utils/format';
|
|
9
9
|
const isSeriesDataValid = (d) => d.y !== null;
|
|
10
10
|
async function getLabelData(d, xMax) {
|
|
11
11
|
var _a;
|
|
12
12
|
if (!d.series.dataLabels.enabled) {
|
|
13
|
-
return
|
|
13
|
+
return {};
|
|
14
14
|
}
|
|
15
15
|
const text = getFormattedValue(Object.assign({ value: (_a = d.data.label) !== null && _a !== void 0 ? _a : d.data.y }, d.series.dataLabels));
|
|
16
16
|
const style = d.series.dataLabels.style;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
if (d.series.dataLabels.html) {
|
|
18
|
+
const { maxHeight: height, maxWidth: width } = await getLabelsSize({
|
|
19
|
+
labels: [text],
|
|
20
|
+
style,
|
|
21
|
+
html: true,
|
|
22
|
+
});
|
|
23
|
+
let y = Math.max(height, d.y - d.series.dataLabels.padding);
|
|
24
|
+
if (d.series.dataLabels.inside) {
|
|
25
|
+
y = d.y + d.height / 2;
|
|
26
|
+
}
|
|
27
|
+
const centerX = Math.min(xMax - width / 2, Math.max(width / 2, d.x + d.width / 2));
|
|
28
|
+
return {
|
|
29
|
+
htmlLabel: {
|
|
30
|
+
content: text,
|
|
31
|
+
x: centerX - width / 2,
|
|
32
|
+
y: y - height,
|
|
33
|
+
size: { width, height },
|
|
34
|
+
style,
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const getTextSize = getTextSizeFn({ style });
|
|
40
|
+
const { width, height, hangingOffset } = await getTextSize(text);
|
|
41
|
+
let y = Math.max(hangingOffset, d.y - height + hangingOffset - d.series.dataLabels.padding);
|
|
42
|
+
if (d.series.dataLabels.inside) {
|
|
43
|
+
const centerY = d.y + d.height / 2;
|
|
44
|
+
y = Math.min(d.y + d.height - height + hangingOffset, centerY - height / 2 + hangingOffset);
|
|
45
|
+
}
|
|
46
|
+
const centerX = Math.min(xMax - width / 2, Math.max(width / 2, d.x + d.width / 2));
|
|
47
|
+
return {
|
|
48
|
+
svgLabel: {
|
|
49
|
+
text,
|
|
50
|
+
x: centerX,
|
|
51
|
+
y,
|
|
52
|
+
style,
|
|
53
|
+
size: { width, height, hangingOffset },
|
|
54
|
+
textAnchor: 'middle',
|
|
55
|
+
series: d.series,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
26
58
|
}
|
|
27
|
-
const centerX = Math.min(xMax - width / 2, Math.max(width / 2, d.x + d.width / 2));
|
|
28
|
-
return {
|
|
29
|
-
text,
|
|
30
|
-
x: html ? centerX - width / 2 : centerX,
|
|
31
|
-
y: html ? y - height : y,
|
|
32
|
-
style,
|
|
33
|
-
size: { width, height },
|
|
34
|
-
textAnchor: 'middle',
|
|
35
|
-
series: d.series,
|
|
36
|
-
};
|
|
37
59
|
}
|
|
38
60
|
export const prepareBarXData = async (args) => {
|
|
39
61
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k;
|
|
@@ -232,20 +254,12 @@ export const prepareBarXData = async (args) => {
|
|
|
232
254
|
if (barData.series.dataLabels.enabled &&
|
|
233
255
|
!isRangeSlider &&
|
|
234
256
|
(!isBarOutsideBounds || isZeroValue)) {
|
|
235
|
-
const
|
|
236
|
-
if (
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
content: label.text,
|
|
242
|
-
size: label.size,
|
|
243
|
-
style: label.style,
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
barData.svgLabels.push(label);
|
|
248
|
-
}
|
|
257
|
+
const { svgLabel, htmlLabel } = await getLabelData(barData, xMax);
|
|
258
|
+
if (svgLabel) {
|
|
259
|
+
barData.svgLabels.push(svgLabel);
|
|
260
|
+
}
|
|
261
|
+
if (htmlLabel) {
|
|
262
|
+
barData.htmlLabels.push(htmlLabel);
|
|
249
263
|
}
|
|
250
264
|
}
|
|
251
265
|
}
|
|
@@ -1,22 +1,8 @@
|
|
|
1
1
|
import { prepareAnnotation } from '../../series/prepare-annotation';
|
|
2
|
-
import { filterOverlappingLabels,
|
|
3
|
-
import { getFormattedValue } from '../../utils/format';
|
|
2
|
+
import { filterOverlappingLabels, preparePointDataLabels } from '../../utils';
|
|
4
3
|
import { getXValue, getYValue, markHiddenPointsOutOfYRange } from '../utils';
|
|
5
|
-
async function getHtmlLabel(point, series, xMax) {
|
|
6
|
-
var _a;
|
|
7
|
-
const content = String((_a = point.data.label) !== null && _a !== void 0 ? _a : point.data.y);
|
|
8
|
-
const size = await getLabelsSize({ labels: [content], html: true });
|
|
9
|
-
const width = size.maxWidth;
|
|
10
|
-
return {
|
|
11
|
-
x: Math.min(xMax - size.maxWidth, Math.max(0, point.x - width / 2)),
|
|
12
|
-
y: Math.max(0, point.y - series.dataLabels.padding - size.maxHeight),
|
|
13
|
-
content,
|
|
14
|
-
size: { width, height: size.maxHeight },
|
|
15
|
-
style: series.dataLabels.style,
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
4
|
export const prepareLineData = async (args) => {
|
|
19
|
-
var _a, _b, _c, _d, _e, _f
|
|
5
|
+
var _a, _b, _c, _d, _e, _f;
|
|
20
6
|
const { series, seriesOptions, xAxis, yAxis, xScale, yScale, split, isOutsideBounds, isRangeSlider, otherLayers, } = args;
|
|
21
7
|
const [_xMin, xRangeMax] = xScale.range();
|
|
22
8
|
const xMax = xRangeMax;
|
|
@@ -58,46 +44,15 @@ export const prepareLineData = async (args) => {
|
|
|
58
44
|
let htmlElements = [];
|
|
59
45
|
let svgLabels = [];
|
|
60
46
|
if (s.dataLabels.enabled && !isRangeSlider) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
const getTextSize = getTextSizeFn({ style: s.dataLabels.style });
|
|
73
|
-
for (let index = 0; index < points.length; index++) {
|
|
74
|
-
const point = points[index];
|
|
75
|
-
if (point.y !== null &&
|
|
76
|
-
point.x !== null &&
|
|
77
|
-
!isOutsideBounds(point.x, point.y)) {
|
|
78
|
-
const labelValue = (_e = point.data.label) !== null && _e !== void 0 ? _e : point.data.y;
|
|
79
|
-
const text = getFormattedValue(Object.assign({ value: labelValue }, s.dataLabels));
|
|
80
|
-
const labelSize = await getTextSize(text);
|
|
81
|
-
const style = s.dataLabels.style;
|
|
82
|
-
const y = Math.max(yAxisTop, point.y -
|
|
83
|
-
s.dataLabels.padding -
|
|
84
|
-
labelSize.height +
|
|
85
|
-
labelSize.hangingOffset);
|
|
86
|
-
const x = Math.min(xMax - labelSize.width, Math.max(0, point.x - labelSize.width / 2));
|
|
87
|
-
const labelData = {
|
|
88
|
-
text,
|
|
89
|
-
x,
|
|
90
|
-
y,
|
|
91
|
-
style,
|
|
92
|
-
size: labelSize,
|
|
93
|
-
textAnchor: 'start',
|
|
94
|
-
series: s,
|
|
95
|
-
active: true,
|
|
96
|
-
};
|
|
97
|
-
svgLabels.push(labelData);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
47
|
+
const labelsData = await preparePointDataLabels({
|
|
48
|
+
series: s,
|
|
49
|
+
points,
|
|
50
|
+
xMax,
|
|
51
|
+
yAxisTop,
|
|
52
|
+
isOutsideBounds,
|
|
53
|
+
});
|
|
54
|
+
svgLabels = labelsData.svgLabels;
|
|
55
|
+
htmlElements = labelsData.htmlLabels;
|
|
101
56
|
}
|
|
102
57
|
if (!s.dataLabels.allowOverlap) {
|
|
103
58
|
svgLabels = filterOverlappingLabels(svgLabels, otherLayers.map((l) => l.svgLabels).flat());
|
|
@@ -148,11 +103,11 @@ export const prepareLineData = async (args) => {
|
|
|
148
103
|
id: s.id,
|
|
149
104
|
htmlLabels: htmlElements,
|
|
150
105
|
color: s.color,
|
|
151
|
-
lineWidth: (
|
|
106
|
+
lineWidth: (_e = (isRangeSlider ? s.rangeSlider.lineWidth : undefined)) !== null && _e !== void 0 ? _e : s.lineWidth,
|
|
152
107
|
dashStyle: s.dashStyle,
|
|
153
108
|
linecap: s.linecap,
|
|
154
109
|
linejoin: s.linejoin,
|
|
155
|
-
opacity: (
|
|
110
|
+
opacity: (_f = (isRangeSlider ? s.rangeSlider.opacity : undefined)) !== null && _f !== void 0 ? _f : s.opacity,
|
|
156
111
|
};
|
|
157
112
|
acc.push(result);
|
|
158
113
|
}
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import type { PreparedXAxis, PreparedYAxis } from '../../axes/types';
|
|
2
|
+
import type { PreparedSplit } from '../../layout/split-types';
|
|
2
3
|
import type { ChartScale } from '../../scales/types';
|
|
3
4
|
import type { PreparedScatterSeries } from '../../series/types';
|
|
4
|
-
import type {
|
|
5
|
+
import type { PreparedScatterShapeData } from './types';
|
|
5
6
|
export declare function prepareScatterData(args: {
|
|
6
7
|
series: PreparedScatterSeries[];
|
|
7
8
|
xAxis: PreparedXAxis;
|
|
8
9
|
xScale: ChartScale;
|
|
9
10
|
yAxis: PreparedYAxis[];
|
|
10
11
|
yScale: (ChartScale | undefined)[];
|
|
12
|
+
split: PreparedSplit;
|
|
11
13
|
isOutsideBounds: (x: number, y: number) => boolean;
|
|
12
|
-
|
|
14
|
+
isRangeSlider?: boolean;
|
|
15
|
+
}): Promise<PreparedScatterShapeData>;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
2
|
import { getXValue, getYValue } from '../../shapes/utils';
|
|
3
|
-
import { getDataCategoryValue } from '../../utils';
|
|
3
|
+
import { filterOverlappingLabels, getDataCategoryValue, preparePointDataLabels } from '../../utils';
|
|
4
4
|
function getFilteredLinearScatterData(data) {
|
|
5
5
|
return data.filter((d) => typeof d.x === 'number' && typeof d.y === 'number');
|
|
6
6
|
}
|
|
@@ -32,9 +32,12 @@ function getFilteredCategoryScatterData(args) {
|
|
|
32
32
|
return xInRange && yInRange;
|
|
33
33
|
});
|
|
34
34
|
}
|
|
35
|
-
export function prepareScatterData(args) {
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
export async function prepareScatterData(args) {
|
|
36
|
+
var _a;
|
|
37
|
+
const { series, xAxis, xScale, yAxis, yScale, split, isOutsideBounds, isRangeSlider } = args;
|
|
38
|
+
const [_xMin, xRangeMax] = xScale.range();
|
|
39
|
+
const xMax = xRangeMax;
|
|
40
|
+
const markers = series.reduce((acc, s) => {
|
|
38
41
|
const yAxisIndex = get(s, 'yAxis', 0);
|
|
39
42
|
const seriesYAxis = yAxis[yAxisIndex];
|
|
40
43
|
const seriesYScale = yScale[yAxisIndex];
|
|
@@ -74,4 +77,40 @@ export function prepareScatterData(args) {
|
|
|
74
77
|
});
|
|
75
78
|
return acc;
|
|
76
79
|
}, []);
|
|
80
|
+
const allSvgLabels = [];
|
|
81
|
+
const allHtmlLabels = [];
|
|
82
|
+
if (!isRangeSlider) {
|
|
83
|
+
for (const s of series) {
|
|
84
|
+
if (!s.dataLabels.enabled) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
const yAxisIndex = get(s, 'yAxis', 0);
|
|
88
|
+
const seriesYAxis = yAxis[yAxisIndex];
|
|
89
|
+
const seriesYScale = yScale[yAxisIndex];
|
|
90
|
+
if (!seriesYScale) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
const yAxisTop = ((_a = split.plots[seriesYAxis.plotIndex]) === null || _a === void 0 ? void 0 : _a.top) || 0;
|
|
94
|
+
const seriesPoints = markers
|
|
95
|
+
.filter((m) => m.point.series.id === s.id && !m.clipped)
|
|
96
|
+
.map((m) => m.point);
|
|
97
|
+
const { svgLabels, htmlLabels } = await preparePointDataLabels({
|
|
98
|
+
series: s,
|
|
99
|
+
points: seriesPoints,
|
|
100
|
+
xMax,
|
|
101
|
+
yAxisTop,
|
|
102
|
+
isOutsideBounds,
|
|
103
|
+
anchorYOffset: s.marker.states.normal.radius,
|
|
104
|
+
});
|
|
105
|
+
if (s.dataLabels.allowOverlap) {
|
|
106
|
+
allSvgLabels.push(...svgLabels);
|
|
107
|
+
allHtmlLabels.push(...htmlLabels);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
allSvgLabels.push(...filterOverlappingLabels(svgLabels, allSvgLabels));
|
|
111
|
+
allHtmlLabels.push(...filterOverlappingLabels(htmlLabels, allHtmlLabels));
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { markers, svgLabels: allSvgLabels, htmlLabels: allHtmlLabels };
|
|
77
116
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Dispatch } from 'd3-dispatch';
|
|
2
2
|
import type { PreparedSeriesOptions } from '../../series/types';
|
|
3
|
-
import type {
|
|
3
|
+
import type { PreparedScatterShapeData } from './types';
|
|
4
4
|
export declare function renderScatter(elements: {
|
|
5
5
|
plot: SVGGElement;
|
|
6
|
-
}, preparedData:
|
|
6
|
+
}, preparedData: PreparedScatterShapeData, seriesOptions: PreparedSeriesOptions, dispatcher?: Dispatch<object>): () => void;
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { select } from 'd3-selection';
|
|
2
2
|
import get from 'lodash/get';
|
|
3
|
+
import { block } from '../../../utils';
|
|
3
4
|
import { getMarkerHaloVisibility, renderMarker, selectMarkerHalo, selectMarkerSymbol, setMarker, } from '../../shapes/marker';
|
|
4
5
|
import { setActiveState, shapeKey } from '../../shapes/utils';
|
|
6
|
+
import { renderDataLabels } from '../data-labels';
|
|
7
|
+
const b = block('scatter');
|
|
5
8
|
export function renderScatter(elements, preparedData, seriesOptions, dispatcher) {
|
|
6
9
|
const svgElement = select(elements.plot);
|
|
7
10
|
const hoverOptions = get(seriesOptions, 'scatter.states.hover');
|
|
@@ -9,11 +12,16 @@ export function renderScatter(elements, preparedData, seriesOptions, dispatcher)
|
|
|
9
12
|
svgElement.selectAll('*').remove();
|
|
10
13
|
const selection = svgElement
|
|
11
14
|
.selectAll('path')
|
|
12
|
-
.data(preparedData, shapeKey)
|
|
15
|
+
.data(preparedData.markers, shapeKey)
|
|
13
16
|
.join('g')
|
|
14
17
|
.call(renderMarker)
|
|
15
18
|
.attr('opacity', (d) => d.point.opacity)
|
|
16
19
|
.attr('cursor', (d) => d.point.series.cursor);
|
|
20
|
+
renderDataLabels({
|
|
21
|
+
container: svgElement,
|
|
22
|
+
data: preparedData.svgLabels,
|
|
23
|
+
className: b('label'),
|
|
24
|
+
});
|
|
17
25
|
const hoverEnabled = hoverOptions === null || hoverOptions === void 0 ? void 0 : hoverOptions.enabled;
|
|
18
26
|
const inactiveEnabled = inactiveOptions === null || inactiveOptions === void 0 ? void 0 : inactiveOptions.enabled;
|
|
19
27
|
function handleShapeHover(data) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { HtmlItem, ScatterSeriesData } from '../../../types';
|
|
1
|
+
import type { HtmlItem, LabelData, ScatterSeriesData } from '../../../types';
|
|
2
2
|
import type { PreparedScatterSeries } from '../../series/types';
|
|
3
3
|
type PointData = {
|
|
4
4
|
x: number;
|
|
@@ -16,4 +16,9 @@ export type MarkerData = {
|
|
|
16
16
|
clipped: boolean;
|
|
17
17
|
};
|
|
18
18
|
export type PreparedScatterData = MarkerData;
|
|
19
|
+
export type PreparedScatterShapeData = {
|
|
20
|
+
markers: PreparedScatterData[];
|
|
21
|
+
svgLabels: LabelData[];
|
|
22
|
+
htmlLabels: HtmlItem[];
|
|
23
|
+
};
|
|
19
24
|
export {};
|
|
@@ -317,6 +317,7 @@ export interface AxisPlotShape extends AxisPlot {
|
|
|
317
317
|
plotHeight: number;
|
|
318
318
|
}) => string;
|
|
319
319
|
}
|
|
320
|
+
export type PlotBandAlign = 'start' | 'end';
|
|
320
321
|
export interface AxisPlotBand extends AxisPlot {
|
|
321
322
|
/**
|
|
322
323
|
* The start position of the plot band in axis units.
|
|
@@ -336,6 +337,25 @@ export interface AxisPlotBand extends AxisPlot {
|
|
|
336
337
|
* If the value is `Infinity` or `null`, it will be treated as the end of the axis.
|
|
337
338
|
*/
|
|
338
339
|
to: number | string | null;
|
|
340
|
+
/**
|
|
341
|
+
* Anchor side on the perpendicular axis when `size` is set.
|
|
342
|
+
*
|
|
343
|
+
* - `'start'` — the band sticks to the main axis line (bottom for an X axis,
|
|
344
|
+
* left for a left Y axis, right for a right Y axis).
|
|
345
|
+
* - `'end'` — the band sticks to the opposite side of the plot area.
|
|
346
|
+
*
|
|
347
|
+
* Has no effect without `size`.
|
|
348
|
+
* @default 'start'
|
|
349
|
+
*/
|
|
350
|
+
align?: PlotBandAlign;
|
|
351
|
+
/**
|
|
352
|
+
* Perpendicular extent of the band.
|
|
353
|
+
*
|
|
354
|
+
* Accepts a pixel number (`40`), a pixel string (`"40px"`), or a percentage of
|
|
355
|
+
* the perpendicular plot extent (`"25%"`). When omitted, the band spans the
|
|
356
|
+
* full perpendicular extent of the plot area (default behavior).
|
|
357
|
+
*/
|
|
358
|
+
size?: number | string;
|
|
339
359
|
}
|
|
340
360
|
export interface AxisCrosshair extends Pick<AxisPlotLine, 'color' | 'dashStyle' | 'opacity' | 'layerPlacement' | 'width'> {
|
|
341
361
|
/**
|
|
@@ -23,6 +23,8 @@ export interface ScatterSeriesData<T = MeaningfulAny> extends BaseSeriesData<T>
|
|
|
23
23
|
* @deprecated use `x` or `y` instead
|
|
24
24
|
*/
|
|
25
25
|
category?: string;
|
|
26
|
+
/** Data label value of the point. If not specified, the y value is used. */
|
|
27
|
+
label?: string | number;
|
|
26
28
|
/** Individual radius for the point. */
|
|
27
29
|
radius?: number;
|
|
28
30
|
/** Individual opacity for the point. */
|