@gravity-ui/charts 1.31.0 → 1.32.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/ChartInner/index.js +7 -1
- package/dist/cjs/components/ChartInner/types.d.ts +6 -0
- package/dist/cjs/components/index.d.ts +1 -0
- package/dist/cjs/components/index.js +4 -4
- package/dist/cjs/constants/line-styles.d.ts +6 -0
- package/dist/cjs/constants/line-styles.js +7 -0
- package/dist/cjs/hooks/useSeries/prepare-line.js +8 -1
- package/dist/cjs/hooks/useSeries/types.d.ts +2 -1
- package/dist/cjs/hooks/useShapes/area/prepare-data.d.ts +1 -0
- package/dist/cjs/hooks/useShapes/area/prepare-data.js +2 -2
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.d.ts +1 -0
- package/dist/cjs/hooks/useShapes/bar-x/prepare-data.js +15 -6
- package/dist/cjs/hooks/useShapes/bar-y/prepare-data.js +10 -3
- package/dist/cjs/hooks/useShapes/index.js +2 -0
- package/dist/cjs/hooks/useShapes/line/index.js +1 -1
- package/dist/cjs/hooks/useShapes/line/prepare-data.js +2 -1
- package/dist/cjs/hooks/useShapes/line/types.d.ts +2 -1
- package/dist/cjs/types/chart/line.d.ts +3 -1
- package/dist/cjs/types/chart/series.d.ts +6 -1
- package/dist/cjs/utils/chart/index.d.ts +1 -1
- package/dist/cjs/utils/chart/index.js +26 -24
- package/dist/esm/components/ChartInner/index.js +7 -1
- package/dist/esm/components/ChartInner/types.d.ts +6 -0
- package/dist/esm/components/index.d.ts +1 -0
- package/dist/esm/components/index.js +4 -4
- package/dist/esm/constants/line-styles.d.ts +6 -0
- package/dist/esm/constants/line-styles.js +7 -0
- package/dist/esm/hooks/useSeries/prepare-line.js +8 -1
- package/dist/esm/hooks/useSeries/types.d.ts +2 -1
- package/dist/esm/hooks/useShapes/area/prepare-data.d.ts +1 -0
- package/dist/esm/hooks/useShapes/area/prepare-data.js +2 -2
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.d.ts +1 -0
- package/dist/esm/hooks/useShapes/bar-x/prepare-data.js +15 -6
- package/dist/esm/hooks/useShapes/bar-y/prepare-data.js +10 -3
- package/dist/esm/hooks/useShapes/index.js +2 -0
- package/dist/esm/hooks/useShapes/line/index.js +1 -1
- package/dist/esm/hooks/useShapes/line/prepare-data.js +2 -1
- package/dist/esm/hooks/useShapes/line/types.d.ts +2 -1
- package/dist/esm/types/chart/line.d.ts +3 -1
- package/dist/esm/types/chart/series.d.ts +6 -1
- package/dist/esm/utils/chart/index.d.ts +1 -1
- package/dist/esm/utils/chart/index.js +26 -24
- package/package.json +1 -1
|
@@ -26,7 +26,7 @@ const b = block('chart');
|
|
|
26
26
|
const DEBOUNCED_VALUE_DELAY = 10;
|
|
27
27
|
export const ChartInner = (props) => {
|
|
28
28
|
var _a, _b, _c, _d, _e, _f;
|
|
29
|
-
const { width, height, data } = props;
|
|
29
|
+
const { width, height, data, onReady } = props;
|
|
30
30
|
const svgRef = React.useRef(null);
|
|
31
31
|
const resetZoomButtonRef = React.useRef(null);
|
|
32
32
|
const [htmlLayout, setHtmlLayout] = React.useState(null);
|
|
@@ -206,6 +206,12 @@ export const ChartInner = (props) => {
|
|
|
206
206
|
updateRangeSliderState,
|
|
207
207
|
xScale,
|
|
208
208
|
]);
|
|
209
|
+
const areShapesReady = shapes.length > 0;
|
|
210
|
+
React.useEffect(() => {
|
|
211
|
+
if (areShapesReady) {
|
|
212
|
+
onReady === null || onReady === void 0 ? void 0 : onReady({ dimensions: { width, height } });
|
|
213
|
+
}
|
|
214
|
+
}, [height, areShapesReady, onReady, width]);
|
|
209
215
|
const chartContent = (React.createElement(React.Fragment, null,
|
|
210
216
|
React.createElement("defs", null,
|
|
211
217
|
React.createElement("clipPath", { id: clipPathId },
|
|
@@ -7,7 +7,7 @@ import { validateData } from '../validation';
|
|
|
7
7
|
import { ChartInner } from './ChartInner';
|
|
8
8
|
export * from './Tooltip/ChartTooltipContent';
|
|
9
9
|
export const Chart = React.forwardRef(function Chart(props, forwardedRef) {
|
|
10
|
-
const { data, lang, onResize } = props;
|
|
10
|
+
const { data, lang, onResize, onReady } = props;
|
|
11
11
|
const validatedData = React.useRef();
|
|
12
12
|
const ref = React.useRef(null);
|
|
13
13
|
const debounced = React.useRef();
|
|
@@ -36,8 +36,8 @@ export const Chart = React.forwardRef(function Chart(props, forwardedRef) {
|
|
|
36
36
|
}), [debuncedHandleResize]);
|
|
37
37
|
React.useEffect(() => {
|
|
38
38
|
// dimensions initialize
|
|
39
|
-
|
|
40
|
-
}, [
|
|
39
|
+
handleResize();
|
|
40
|
+
}, [handleResize]);
|
|
41
41
|
React.useEffect(() => {
|
|
42
42
|
const selection = select(window);
|
|
43
43
|
// https://github.com/d3/d3-selection/blob/main/README.md#handling-events
|
|
@@ -62,5 +62,5 @@ export const Chart = React.forwardRef(function Chart(props, forwardedRef) {
|
|
|
62
62
|
width: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) || '100%',
|
|
63
63
|
height: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) || '100%',
|
|
64
64
|
position: 'relative',
|
|
65
|
-
} }, (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) && (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) && (React.createElement(ChartInner, { height: dimensions === null || dimensions === void 0 ? void 0 : dimensions.height, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, data: data }))));
|
|
65
|
+
} }, (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) && (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) && (React.createElement(ChartInner, { height: dimensions === null || dimensions === void 0 ? void 0 : dimensions.height, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, data: data, onReady: onReady }))));
|
|
66
66
|
});
|
|
@@ -18,3 +18,10 @@ export var LineCap;
|
|
|
18
18
|
LineCap["Square"] = "square";
|
|
19
19
|
LineCap["None"] = "none";
|
|
20
20
|
})(LineCap || (LineCap = {}));
|
|
21
|
+
export var LineJoin;
|
|
22
|
+
(function (LineJoin) {
|
|
23
|
+
LineJoin["Round"] = "round";
|
|
24
|
+
LineJoin["Bevel"] = "bevel";
|
|
25
|
+
LineJoin["Miter"] = "miter";
|
|
26
|
+
LineJoin["None"] = "unset";
|
|
27
|
+
})(LineJoin || (LineJoin = {}));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
2
|
import merge from 'lodash/merge';
|
|
3
|
-
import { DASH_STYLE, DEFAULT_DATALABELS_STYLE, LineCap, seriesRangeSliderOptionsDefaults, } from '../../constants';
|
|
3
|
+
import { DASH_STYLE, DEFAULT_DATALABELS_STYLE, LineCap, LineJoin, seriesRangeSliderOptionsDefaults, } from '../../constants';
|
|
4
4
|
import { getUniqId } from '../../utils';
|
|
5
5
|
import { DEFAULT_DATALABELS_PADDING, DEFAULT_HALO_OPTIONS, DEFAULT_LEGEND_SYMBOL_PADDING, DEFAULT_POINT_MARKER_OPTIONS, } from './constants';
|
|
6
6
|
export const DEFAULT_LEGEND_SYMBOL_SIZE = 16;
|
|
@@ -12,6 +12,12 @@ function prepareLinecap(dashStyle, series, seriesOptions) {
|
|
|
12
12
|
const lineCapFromSeriesOptions = get(seriesOptions, 'line.linecap', defaultLineCap);
|
|
13
13
|
return get(series, 'linecap', lineCapFromSeriesOptions);
|
|
14
14
|
}
|
|
15
|
+
function prepareLinejoin(dashStyle, series, seriesOptions) {
|
|
16
|
+
var _a, _b, _c;
|
|
17
|
+
const defaultLinejoin = dashStyle === DASH_STYLE.Solid ? LineJoin.Round : LineJoin.None;
|
|
18
|
+
const linejoinFromSeriesOptions = (_b = (_a = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions.line) === null || _a === void 0 ? void 0 : _a.linejoin) !== null && _b !== void 0 ? _b : defaultLinejoin;
|
|
19
|
+
return ((_c = series === null || series === void 0 ? void 0 : series.linejoin) !== null && _c !== void 0 ? _c : linejoinFromSeriesOptions);
|
|
20
|
+
}
|
|
15
21
|
function prepareLineLegendSymbol(series, seriesOptions) {
|
|
16
22
|
var _a;
|
|
17
23
|
const symbolOptions = ((_a = series.legend) === null || _a === void 0 ? void 0 : _a.symbol) || {};
|
|
@@ -90,6 +96,7 @@ export function prepareLineSeries(args) {
|
|
|
90
96
|
marker: prepareMarker(series, seriesOptions),
|
|
91
97
|
dashStyle: dashStyle,
|
|
92
98
|
linecap: prepareLinecap(dashStyle, series, seriesOptions),
|
|
99
|
+
linejoin: prepareLinejoin(dashStyle, series, seriesOptions),
|
|
93
100
|
opacity: get(series, 'opacity', null),
|
|
94
101
|
cursor: get(series, 'cursor', null),
|
|
95
102
|
yAxis: get(series, 'yAxis', 0),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DashStyle, LayoutAlgorithm, LineCap, SeriesOptionsDefaults, SymbolType } from '../../constants';
|
|
1
|
+
import type { DashStyle, LayoutAlgorithm, LineCap, LineJoin, SeriesOptionsDefaults, SymbolType } from '../../constants';
|
|
2
2
|
import type { AreaSeries, AreaSeriesData, BarXSeries, BarXSeriesData, BarYSeries, BarYSeriesData, BaseTextStyle, ChartLegend, ChartSeries, ChartSeriesRangeSliderOptions, ConnectorCurve, ConnectorShape, FunnelSeries, FunnelSeriesData, HeatmapSeries, HeatmapSeriesData, LineSeries, LineSeriesData, LineSeriesLineBaseStyle, PathLegendSymbolOptions, PieSeries, PieSeriesData, RadarSeries, RadarSeriesCategory, RadarSeriesData, RectLegendSymbolOptions, SankeySeries, SankeySeriesData, ScatterSeries, ScatterSeriesData, SymbolLegendSymbolOptions, TreemapSeries, TreemapSeriesData, ValueFormat, WaterfallSeries, WaterfallSeriesData } from '../../types';
|
|
3
3
|
export type RectLegendSymbol = {
|
|
4
4
|
shape: 'rect';
|
|
@@ -222,6 +222,7 @@ export type PreparedLineSeries = {
|
|
|
222
222
|
};
|
|
223
223
|
dashStyle: DashStyle;
|
|
224
224
|
linecap: LineCap;
|
|
225
|
+
linejoin: LineJoin;
|
|
225
226
|
opacity: number | null;
|
|
226
227
|
yAxis: number;
|
|
227
228
|
} & BasePreparedSeries & BasePreparedAxisRelatedSeries;
|
|
@@ -74,7 +74,7 @@ async function prepareDataLabels({ series, points, xMax, yAxisTop, }) {
|
|
|
74
74
|
}
|
|
75
75
|
export const prepareAreaData = async (args) => {
|
|
76
76
|
var _a, _b;
|
|
77
|
-
const { series, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, isOutsideBounds, } = args;
|
|
77
|
+
const { series, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, isOutsideBounds, isRangeSlider, } = args;
|
|
78
78
|
const [_xMin, xRangeMax] = xScale.range();
|
|
79
79
|
const xMax = xRangeMax;
|
|
80
80
|
const result = [];
|
|
@@ -147,7 +147,7 @@ export const prepareAreaData = async (args) => {
|
|
|
147
147
|
}, []);
|
|
148
148
|
const labels = [];
|
|
149
149
|
const htmlElements = [];
|
|
150
|
-
if (s.dataLabels.enabled) {
|
|
150
|
+
if (s.dataLabels.enabled && !isRangeSlider) {
|
|
151
151
|
const labelsData = await prepareDataLabels({ series: s, points, xMax, yAxisTop });
|
|
152
152
|
labels.push(...labelsData.svgLabels);
|
|
153
153
|
htmlElements.push(...labelsData.htmlLabels);
|
|
@@ -35,7 +35,7 @@ async function getLabelData(d) {
|
|
|
35
35
|
// eslint-disable-next-line complexity
|
|
36
36
|
export const prepareBarXData = async (args) => {
|
|
37
37
|
var _a, _b, _c, _d;
|
|
38
|
-
const { series, seriesOptions, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, } = args;
|
|
38
|
+
const { series, seriesOptions, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, isRangeSlider, } = args;
|
|
39
39
|
const stackGap = seriesOptions['bar-x'].stackGap;
|
|
40
40
|
const categories = (_a = xAxis === null || xAxis === void 0 ? void 0 : xAxis.categories) !== null && _a !== void 0 ? _a : [];
|
|
41
41
|
const sortingOptions = get(seriesOptions, 'bar-x.dataSorting');
|
|
@@ -99,7 +99,8 @@ export const prepareBarXData = async (args) => {
|
|
|
99
99
|
const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
|
|
100
100
|
for (let groupItemIndex = 0; groupItemIndex < stacks.length; groupItemIndex++) {
|
|
101
101
|
const yValues = stacks[groupItemIndex];
|
|
102
|
-
let
|
|
102
|
+
let positiveStackHeight = 0;
|
|
103
|
+
let negativeStackHeight = 0;
|
|
103
104
|
const stackItems = [];
|
|
104
105
|
const sortedData = sortKey
|
|
105
106
|
? sort(yValues, (a, b) => comparator(get(a, sortKey), get(b, sortKey)))
|
|
@@ -117,6 +118,7 @@ export const prepareBarXData = async (args) => {
|
|
|
117
118
|
if (xAxis.type === 'category') {
|
|
118
119
|
const xBandScale = xScale;
|
|
119
120
|
const xBandScaleDomain = xBandScale.domain();
|
|
121
|
+
// eslint-disable-next-line max-depth
|
|
120
122
|
if (xBandScaleDomain.indexOf(xValue) === -1) {
|
|
121
123
|
continue;
|
|
122
124
|
}
|
|
@@ -141,7 +143,9 @@ export const prepareBarXData = async (args) => {
|
|
|
141
143
|
}
|
|
142
144
|
const barData = {
|
|
143
145
|
x,
|
|
144
|
-
y:
|
|
146
|
+
y: yDataValue > 0
|
|
147
|
+
? yAxisTop + y - positiveStackHeight
|
|
148
|
+
: yAxisTop + base + negativeStackHeight,
|
|
145
149
|
width: rectWidth,
|
|
146
150
|
height: shapeHeight,
|
|
147
151
|
opacity: get(yValue.data, 'opacity', null),
|
|
@@ -151,11 +155,16 @@ export const prepareBarXData = async (args) => {
|
|
|
151
155
|
isLastStackItem,
|
|
152
156
|
};
|
|
153
157
|
stackItems.push(barData);
|
|
154
|
-
|
|
158
|
+
if (yDataValue > 0) {
|
|
159
|
+
positiveStackHeight += height;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
negativeStackHeight += height;
|
|
163
|
+
}
|
|
155
164
|
}
|
|
156
165
|
if (series.some((s) => s.stacking === 'percent')) {
|
|
157
166
|
let acc = 0;
|
|
158
|
-
const ratio = plotHeight / (
|
|
167
|
+
const ratio = plotHeight / (positiveStackHeight - stackItems.length);
|
|
159
168
|
stackItems.forEach((item) => {
|
|
160
169
|
item.height = item.height * ratio;
|
|
161
170
|
item.y = plotHeight - item.height - acc;
|
|
@@ -168,7 +177,7 @@ export const prepareBarXData = async (args) => {
|
|
|
168
177
|
}
|
|
169
178
|
for (let i = 0; i < result.length; i++) {
|
|
170
179
|
const barData = result[i];
|
|
171
|
-
if (barData.series.dataLabels.enabled) {
|
|
180
|
+
if (barData.series.dataLabels.enabled && !isRangeSlider) {
|
|
172
181
|
const label = await getLabelData(barData);
|
|
173
182
|
if (barData.series.dataLabels.html && label) {
|
|
174
183
|
barData.htmlElements.push({
|
|
@@ -45,7 +45,8 @@ export async function prepareBarYData(args) {
|
|
|
45
45
|
stacks.forEach((measureValues, groupItemIndex) => {
|
|
46
46
|
const baseValue = xAxis.type === 'logarithmic' ? 0 : xLinearScale(0);
|
|
47
47
|
const base = baseValue - measureValues[0].series.borderWidth;
|
|
48
|
-
let
|
|
48
|
+
let positiveStack = base;
|
|
49
|
+
let negativeStack = base;
|
|
49
50
|
const stackItems = [];
|
|
50
51
|
const sortedData = sortKey
|
|
51
52
|
? sort(measureValues, (a, b) => comparator(get(a, sortKey), get(b, sortKey)))
|
|
@@ -93,7 +94,8 @@ export async function prepareBarYData(args) {
|
|
|
93
94
|
const isLastStackItem = xValueIndex === sortedData.length - 1;
|
|
94
95
|
// Calculate position with border compensation
|
|
95
96
|
// Border extends halfBorder outward from the shape, so we need to adjust position
|
|
96
|
-
let itemX =
|
|
97
|
+
let itemX = xValue > baseRangeValue ? positiveStack : negativeStack - width;
|
|
98
|
+
itemX += itemStackGap;
|
|
97
99
|
const halfBorder = borderWidth / 2;
|
|
98
100
|
if (isFirstInStack && xValue > 0) {
|
|
99
101
|
// Positive bar: border extends left, so shift position left by halfBorder
|
|
@@ -119,7 +121,12 @@ export async function prepareBarYData(args) {
|
|
|
119
121
|
isLastStackItem,
|
|
120
122
|
};
|
|
121
123
|
stackItems.push(item);
|
|
122
|
-
|
|
124
|
+
if (xValue > baseRangeValue) {
|
|
125
|
+
positiveStack += width;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
negativeStack -= width;
|
|
129
|
+
}
|
|
123
130
|
});
|
|
124
131
|
result.push(...stackItems);
|
|
125
132
|
});
|
|
@@ -58,6 +58,7 @@ export const useShapes = (args) => {
|
|
|
58
58
|
yScale,
|
|
59
59
|
boundsHeight,
|
|
60
60
|
split,
|
|
61
|
+
isRangeSlider,
|
|
61
62
|
});
|
|
62
63
|
shapes.push(React.createElement(BarXSeriesShapes, { key: SERIES_TYPE.BarX, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
|
|
63
64
|
shapesData.push(...preparedData);
|
|
@@ -124,6 +125,7 @@ export const useShapes = (args) => {
|
|
|
124
125
|
boundsHeight,
|
|
125
126
|
split,
|
|
126
127
|
isOutsideBounds,
|
|
128
|
+
isRangeSlider,
|
|
127
129
|
});
|
|
128
130
|
shapes.push(React.createElement(AreaSeriesShapes, { key: SERIES_TYPE.Area, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
|
|
129
131
|
shapesData.push(...preparedData);
|
|
@@ -34,7 +34,7 @@ export const LineSeriesShapes = (args) => {
|
|
|
34
34
|
.attr('fill', 'none')
|
|
35
35
|
.attr('stroke', (d) => d.color)
|
|
36
36
|
.attr('stroke-width', (d) => d.lineWidth)
|
|
37
|
-
.attr('stroke-linejoin', (d) => d.
|
|
37
|
+
.attr('stroke-linejoin', (d) => d.linejoin)
|
|
38
38
|
.attr('stroke-linecap', (d) => d.linecap)
|
|
39
39
|
.attr('stroke-dasharray', (d) => getLineDashArray(d.dashStyle, d.lineWidth))
|
|
40
40
|
.attr('opacity', (d) => d.opacity)
|
|
@@ -44,7 +44,7 @@ export const prepareLineData = async (args) => {
|
|
|
44
44
|
});
|
|
45
45
|
const htmlElements = [];
|
|
46
46
|
const labels = [];
|
|
47
|
-
if (s.dataLabels.enabled) {
|
|
47
|
+
if (s.dataLabels.enabled && !isRangeSlider) {
|
|
48
48
|
if (s.dataLabels.html) {
|
|
49
49
|
const list = await Promise.all(points.reduce((result, p) => {
|
|
50
50
|
if (p.y === null) {
|
|
@@ -108,6 +108,7 @@ export const prepareLineData = async (args) => {
|
|
|
108
108
|
lineWidth: (_b = (isRangeSlider ? s.rangeSlider.lineWidth : undefined)) !== null && _b !== void 0 ? _b : s.lineWidth,
|
|
109
109
|
dashStyle: s.dashStyle,
|
|
110
110
|
linecap: s.linecap,
|
|
111
|
+
linejoin: s.linejoin,
|
|
111
112
|
opacity: (_c = (isRangeSlider ? s.rangeSlider.opacity : undefined)) !== null && _c !== void 0 ? _c : s.opacity,
|
|
112
113
|
};
|
|
113
114
|
acc.push(result);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DashStyle, LineCap } from '../../../constants';
|
|
1
|
+
import type { DashStyle, LineCap, LineJoin } from '../../../constants';
|
|
2
2
|
import type { HtmlItem, LabelData, LineSeriesData, LineSeriesLineBaseStyle } from '../../../types';
|
|
3
3
|
import type { PreparedLineSeries } from '../../useSeries/types';
|
|
4
4
|
export type PointData = {
|
|
@@ -30,4 +30,5 @@ export type PreparedLineData = {
|
|
|
30
30
|
color: string;
|
|
31
31
|
dashStyle: DashStyle;
|
|
32
32
|
linecap: LineCap;
|
|
33
|
+
linejoin: LineJoin;
|
|
33
34
|
} & Required<LineSeriesLineBaseStyle>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DashStyle, LineCap, SERIES_TYPE } from '../../constants';
|
|
1
|
+
import type { DashStyle, LineCap, LineJoin, SERIES_TYPE } from '../../constants';
|
|
2
2
|
import type { MeaningfulAny } from '../misc';
|
|
3
3
|
import type { BaseSeries, BaseSeriesData, BaseSeriesLegend } from './base';
|
|
4
4
|
import type { RectLegendSymbolOptions } from './legend';
|
|
@@ -53,6 +53,8 @@ export interface LineSeries<T = MeaningfulAny> extends BaseSeries, LineSeriesLin
|
|
|
53
53
|
dashStyle?: DashStyle;
|
|
54
54
|
/** Option for line cap style */
|
|
55
55
|
linecap?: `${LineCap}`;
|
|
56
|
+
/** Defines the shape to be used at the corners of the line */
|
|
57
|
+
linejoin?: `${LineJoin}`;
|
|
56
58
|
/** Individual series legend options. Has higher priority than legend options in widget data */
|
|
57
59
|
legend?: BaseSeriesLegend & {
|
|
58
60
|
symbol?: RectLegendSymbolOptions;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import type { DashStyle, LineCap } from '../../constants';
|
|
2
|
+
import type { DashStyle, LineCap, LineJoin } from '../../constants';
|
|
3
3
|
import type { MeaningfulAny } from '../misc';
|
|
4
4
|
import type { AreaSeries, AreaSeriesData } from './area';
|
|
5
5
|
import type { BarXSeries, BarXSeriesData } from './bar-x';
|
|
@@ -211,6 +211,11 @@ export interface ChartSeriesOptions {
|
|
|
211
211
|
* @default 'round' when dashStyle is not 'solid', 'none' when dashStyle is not 'solid'
|
|
212
212
|
* */
|
|
213
213
|
linecap?: `${LineCap}`;
|
|
214
|
+
/** Defines the shape to be used at the corners of the line
|
|
215
|
+
*
|
|
216
|
+
* @default 'round' when dashStyle is not 'solid', 'unset' when dashStyle is not 'solid'
|
|
217
|
+
* */
|
|
218
|
+
linejoin?: `${LineJoin}`;
|
|
214
219
|
};
|
|
215
220
|
area?: {
|
|
216
221
|
/** Pixel width of the graph line.
|
|
@@ -46,8 +46,8 @@ export declare function isSeriesWithCategoryValues(series: UnknownSeries): serie
|
|
|
46
46
|
export declare const getDomainDataXBySeries: (series: UnknownSeries[]) => ({} | undefined)[];
|
|
47
47
|
export declare function getDefaultMaxXAxisValue(series: UnknownSeries[]): 0 | undefined;
|
|
48
48
|
export declare function getDefaultMinXAxisValue(series: UnknownSeries[]): number | undefined;
|
|
49
|
-
export declare function getDefaultMinYAxisValue(series?: UnknownSeries[]): number | undefined;
|
|
50
49
|
export declare const getDomainDataYBySeries: (series: UnknownSeries[]) => unknown[];
|
|
50
|
+
export declare function getDefaultMinYAxisValue(series?: UnknownSeries[]): number | undefined;
|
|
51
51
|
export declare const getSeriesNames: (series: ChartSeries[]) => string[];
|
|
52
52
|
export declare const getOnlyVisibleSeries: <T extends {
|
|
53
53
|
visible: boolean;
|
|
@@ -46,7 +46,8 @@ function getDomainDataForStackedSeries(seriesList, keyAttr = 'x', valueAttr = 'y
|
|
|
46
46
|
const acc = [];
|
|
47
47
|
const stackedSeries = group(seriesList, getSeriesStackId);
|
|
48
48
|
Array.from(stackedSeries).forEach(([_stackId, seriesStack]) => {
|
|
49
|
-
const
|
|
49
|
+
const positiveValues = {};
|
|
50
|
+
const negativeValues = {};
|
|
50
51
|
seriesStack.forEach((singleSeries) => {
|
|
51
52
|
const data = new Map();
|
|
52
53
|
singleSeries.data.forEach((point) => {
|
|
@@ -65,10 +66,15 @@ function getDomainDataForStackedSeries(seriesList, keyAttr = 'x', valueAttr = 'y
|
|
|
65
66
|
data.set(key, value);
|
|
66
67
|
});
|
|
67
68
|
Array.from(data).forEach(([key, value]) => {
|
|
68
|
-
|
|
69
|
+
if (value >= 0) {
|
|
70
|
+
positiveValues[key] = (positiveValues[key] || 0) + value;
|
|
71
|
+
}
|
|
72
|
+
if (value < 0) {
|
|
73
|
+
negativeValues[key] = (negativeValues[key] || 0) + value;
|
|
74
|
+
}
|
|
69
75
|
});
|
|
70
76
|
});
|
|
71
|
-
acc.push(...Object.values(values));
|
|
77
|
+
acc.push(...Object.values(negativeValues), ...Object.values(positiveValues));
|
|
72
78
|
});
|
|
73
79
|
return acc;
|
|
74
80
|
}
|
|
@@ -98,27 +104,9 @@ export function getDefaultMaxXAxisValue(series) {
|
|
|
98
104
|
}
|
|
99
105
|
export function getDefaultMinXAxisValue(series) {
|
|
100
106
|
if (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME_ON_X_AXIS.includes(s.type))) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const minXValue = s.data.reduce((res, d) => Math.min(res, get(d, 'x', 0)), 0);
|
|
105
|
-
return Math.min(minValue, minXValue);
|
|
106
|
-
}, 0);
|
|
107
|
-
}
|
|
108
|
-
return undefined;
|
|
109
|
-
}
|
|
110
|
-
export function getDefaultMinYAxisValue(series) {
|
|
111
|
-
if (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type))) {
|
|
112
|
-
if (series.some((s) => s.type === SERIES_TYPE.Waterfall)) {
|
|
113
|
-
const seriesData = series.map((s) => s.data).flat();
|
|
114
|
-
const minSubTotal = seriesData.reduce((res, d) => Math.min(res, getWaterfallPointSubtotal(d, seriesData) || 0), 0);
|
|
115
|
-
return Math.min(0, minSubTotal);
|
|
116
|
-
}
|
|
117
|
-
return series.reduce((minValue, s) => {
|
|
118
|
-
// https://github.com/gravity-ui/charts/issues/160
|
|
119
|
-
// @ts-expect-error
|
|
120
|
-
const minYValue = s.data.reduce((res, d) => Math.min(res, get(d, 'y', 0)), 0);
|
|
121
|
-
return Math.min(minValue, minYValue);
|
|
107
|
+
const domainData = getDomainDataXBySeries(series);
|
|
108
|
+
return domainData.reduce((minValue, d) => {
|
|
109
|
+
return Math.min(minValue, d);
|
|
122
110
|
}, 0);
|
|
123
111
|
}
|
|
124
112
|
return undefined;
|
|
@@ -151,6 +139,20 @@ export const getDomainDataYBySeries = (series) => {
|
|
|
151
139
|
}, []);
|
|
152
140
|
return Array.from(new Set(items));
|
|
153
141
|
};
|
|
142
|
+
export function getDefaultMinYAxisValue(series) {
|
|
143
|
+
if (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type))) {
|
|
144
|
+
if (series.some((s) => s.type === SERIES_TYPE.Waterfall)) {
|
|
145
|
+
const seriesData = series.map((s) => s.data).flat();
|
|
146
|
+
const minSubTotal = seriesData.reduce((res, d) => Math.min(res, getWaterfallPointSubtotal(d, seriesData) || 0), 0);
|
|
147
|
+
return Math.min(0, minSubTotal);
|
|
148
|
+
}
|
|
149
|
+
const domainData = getDomainDataYBySeries(series);
|
|
150
|
+
return domainData.reduce((minValue, d) => {
|
|
151
|
+
return Math.min(minValue, d);
|
|
152
|
+
}, 0);
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
154
156
|
// Uses to get all series names array (except `pie` charts)
|
|
155
157
|
export const getSeriesNames = (series) => {
|
|
156
158
|
return series.reduce((acc, s) => {
|
|
@@ -26,7 +26,7 @@ const b = block('chart');
|
|
|
26
26
|
const DEBOUNCED_VALUE_DELAY = 10;
|
|
27
27
|
export const ChartInner = (props) => {
|
|
28
28
|
var _a, _b, _c, _d, _e, _f;
|
|
29
|
-
const { width, height, data } = props;
|
|
29
|
+
const { width, height, data, onReady } = props;
|
|
30
30
|
const svgRef = React.useRef(null);
|
|
31
31
|
const resetZoomButtonRef = React.useRef(null);
|
|
32
32
|
const [htmlLayout, setHtmlLayout] = React.useState(null);
|
|
@@ -206,6 +206,12 @@ export const ChartInner = (props) => {
|
|
|
206
206
|
updateRangeSliderState,
|
|
207
207
|
xScale,
|
|
208
208
|
]);
|
|
209
|
+
const areShapesReady = shapes.length > 0;
|
|
210
|
+
React.useEffect(() => {
|
|
211
|
+
if (areShapesReady) {
|
|
212
|
+
onReady === null || onReady === void 0 ? void 0 : onReady({ dimensions: { width, height } });
|
|
213
|
+
}
|
|
214
|
+
}, [height, areShapesReady, onReady, width]);
|
|
209
215
|
const chartContent = (React.createElement(React.Fragment, null,
|
|
210
216
|
React.createElement("defs", null,
|
|
211
217
|
React.createElement("clipPath", { id: clipPathId },
|
|
@@ -7,7 +7,7 @@ import { validateData } from '../validation';
|
|
|
7
7
|
import { ChartInner } from './ChartInner';
|
|
8
8
|
export * from './Tooltip/ChartTooltipContent';
|
|
9
9
|
export const Chart = React.forwardRef(function Chart(props, forwardedRef) {
|
|
10
|
-
const { data, lang, onResize } = props;
|
|
10
|
+
const { data, lang, onResize, onReady } = props;
|
|
11
11
|
const validatedData = React.useRef();
|
|
12
12
|
const ref = React.useRef(null);
|
|
13
13
|
const debounced = React.useRef();
|
|
@@ -36,8 +36,8 @@ export const Chart = React.forwardRef(function Chart(props, forwardedRef) {
|
|
|
36
36
|
}), [debuncedHandleResize]);
|
|
37
37
|
React.useEffect(() => {
|
|
38
38
|
// dimensions initialize
|
|
39
|
-
|
|
40
|
-
}, [
|
|
39
|
+
handleResize();
|
|
40
|
+
}, [handleResize]);
|
|
41
41
|
React.useEffect(() => {
|
|
42
42
|
const selection = select(window);
|
|
43
43
|
// https://github.com/d3/d3-selection/blob/main/README.md#handling-events
|
|
@@ -62,5 +62,5 @@ export const Chart = React.forwardRef(function Chart(props, forwardedRef) {
|
|
|
62
62
|
width: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) || '100%',
|
|
63
63
|
height: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) || '100%',
|
|
64
64
|
position: 'relative',
|
|
65
|
-
} }, (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) && (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) && (React.createElement(ChartInner, { height: dimensions === null || dimensions === void 0 ? void 0 : dimensions.height, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, data: data }))));
|
|
65
|
+
} }, (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) && (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) && (React.createElement(ChartInner, { height: dimensions === null || dimensions === void 0 ? void 0 : dimensions.height, width: dimensions === null || dimensions === void 0 ? void 0 : dimensions.width, data: data, onReady: onReady }))));
|
|
66
66
|
});
|
|
@@ -18,3 +18,10 @@ export var LineCap;
|
|
|
18
18
|
LineCap["Square"] = "square";
|
|
19
19
|
LineCap["None"] = "none";
|
|
20
20
|
})(LineCap || (LineCap = {}));
|
|
21
|
+
export var LineJoin;
|
|
22
|
+
(function (LineJoin) {
|
|
23
|
+
LineJoin["Round"] = "round";
|
|
24
|
+
LineJoin["Bevel"] = "bevel";
|
|
25
|
+
LineJoin["Miter"] = "miter";
|
|
26
|
+
LineJoin["None"] = "unset";
|
|
27
|
+
})(LineJoin || (LineJoin = {}));
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
2
|
import merge from 'lodash/merge';
|
|
3
|
-
import { DASH_STYLE, DEFAULT_DATALABELS_STYLE, LineCap, seriesRangeSliderOptionsDefaults, } from '../../constants';
|
|
3
|
+
import { DASH_STYLE, DEFAULT_DATALABELS_STYLE, LineCap, LineJoin, seriesRangeSliderOptionsDefaults, } from '../../constants';
|
|
4
4
|
import { getUniqId } from '../../utils';
|
|
5
5
|
import { DEFAULT_DATALABELS_PADDING, DEFAULT_HALO_OPTIONS, DEFAULT_LEGEND_SYMBOL_PADDING, DEFAULT_POINT_MARKER_OPTIONS, } from './constants';
|
|
6
6
|
export const DEFAULT_LEGEND_SYMBOL_SIZE = 16;
|
|
@@ -12,6 +12,12 @@ function prepareLinecap(dashStyle, series, seriesOptions) {
|
|
|
12
12
|
const lineCapFromSeriesOptions = get(seriesOptions, 'line.linecap', defaultLineCap);
|
|
13
13
|
return get(series, 'linecap', lineCapFromSeriesOptions);
|
|
14
14
|
}
|
|
15
|
+
function prepareLinejoin(dashStyle, series, seriesOptions) {
|
|
16
|
+
var _a, _b, _c;
|
|
17
|
+
const defaultLinejoin = dashStyle === DASH_STYLE.Solid ? LineJoin.Round : LineJoin.None;
|
|
18
|
+
const linejoinFromSeriesOptions = (_b = (_a = seriesOptions === null || seriesOptions === void 0 ? void 0 : seriesOptions.line) === null || _a === void 0 ? void 0 : _a.linejoin) !== null && _b !== void 0 ? _b : defaultLinejoin;
|
|
19
|
+
return ((_c = series === null || series === void 0 ? void 0 : series.linejoin) !== null && _c !== void 0 ? _c : linejoinFromSeriesOptions);
|
|
20
|
+
}
|
|
15
21
|
function prepareLineLegendSymbol(series, seriesOptions) {
|
|
16
22
|
var _a;
|
|
17
23
|
const symbolOptions = ((_a = series.legend) === null || _a === void 0 ? void 0 : _a.symbol) || {};
|
|
@@ -90,6 +96,7 @@ export function prepareLineSeries(args) {
|
|
|
90
96
|
marker: prepareMarker(series, seriesOptions),
|
|
91
97
|
dashStyle: dashStyle,
|
|
92
98
|
linecap: prepareLinecap(dashStyle, series, seriesOptions),
|
|
99
|
+
linejoin: prepareLinejoin(dashStyle, series, seriesOptions),
|
|
93
100
|
opacity: get(series, 'opacity', null),
|
|
94
101
|
cursor: get(series, 'cursor', null),
|
|
95
102
|
yAxis: get(series, 'yAxis', 0),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DashStyle, LayoutAlgorithm, LineCap, SeriesOptionsDefaults, SymbolType } from '../../constants';
|
|
1
|
+
import type { DashStyle, LayoutAlgorithm, LineCap, LineJoin, SeriesOptionsDefaults, SymbolType } from '../../constants';
|
|
2
2
|
import type { AreaSeries, AreaSeriesData, BarXSeries, BarXSeriesData, BarYSeries, BarYSeriesData, BaseTextStyle, ChartLegend, ChartSeries, ChartSeriesRangeSliderOptions, ConnectorCurve, ConnectorShape, FunnelSeries, FunnelSeriesData, HeatmapSeries, HeatmapSeriesData, LineSeries, LineSeriesData, LineSeriesLineBaseStyle, PathLegendSymbolOptions, PieSeries, PieSeriesData, RadarSeries, RadarSeriesCategory, RadarSeriesData, RectLegendSymbolOptions, SankeySeries, SankeySeriesData, ScatterSeries, ScatterSeriesData, SymbolLegendSymbolOptions, TreemapSeries, TreemapSeriesData, ValueFormat, WaterfallSeries, WaterfallSeriesData } from '../../types';
|
|
3
3
|
export type RectLegendSymbol = {
|
|
4
4
|
shape: 'rect';
|
|
@@ -222,6 +222,7 @@ export type PreparedLineSeries = {
|
|
|
222
222
|
};
|
|
223
223
|
dashStyle: DashStyle;
|
|
224
224
|
linecap: LineCap;
|
|
225
|
+
linejoin: LineJoin;
|
|
225
226
|
opacity: number | null;
|
|
226
227
|
yAxis: number;
|
|
227
228
|
} & BasePreparedSeries & BasePreparedAxisRelatedSeries;
|
|
@@ -74,7 +74,7 @@ async function prepareDataLabels({ series, points, xMax, yAxisTop, }) {
|
|
|
74
74
|
}
|
|
75
75
|
export const prepareAreaData = async (args) => {
|
|
76
76
|
var _a, _b;
|
|
77
|
-
const { series, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, isOutsideBounds, } = args;
|
|
77
|
+
const { series, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, isOutsideBounds, isRangeSlider, } = args;
|
|
78
78
|
const [_xMin, xRangeMax] = xScale.range();
|
|
79
79
|
const xMax = xRangeMax;
|
|
80
80
|
const result = [];
|
|
@@ -147,7 +147,7 @@ export const prepareAreaData = async (args) => {
|
|
|
147
147
|
}, []);
|
|
148
148
|
const labels = [];
|
|
149
149
|
const htmlElements = [];
|
|
150
|
-
if (s.dataLabels.enabled) {
|
|
150
|
+
if (s.dataLabels.enabled && !isRangeSlider) {
|
|
151
151
|
const labelsData = await prepareDataLabels({ series: s, points, xMax, yAxisTop });
|
|
152
152
|
labels.push(...labelsData.svgLabels);
|
|
153
153
|
htmlElements.push(...labelsData.htmlLabels);
|
|
@@ -35,7 +35,7 @@ async function getLabelData(d) {
|
|
|
35
35
|
// eslint-disable-next-line complexity
|
|
36
36
|
export const prepareBarXData = async (args) => {
|
|
37
37
|
var _a, _b, _c, _d;
|
|
38
|
-
const { series, seriesOptions, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, } = args;
|
|
38
|
+
const { series, seriesOptions, xAxis, xScale, yAxis, yScale, boundsHeight: plotHeight, split, isRangeSlider, } = args;
|
|
39
39
|
const stackGap = seriesOptions['bar-x'].stackGap;
|
|
40
40
|
const categories = (_a = xAxis === null || xAxis === void 0 ? void 0 : xAxis.categories) !== null && _a !== void 0 ? _a : [];
|
|
41
41
|
const sortingOptions = get(seriesOptions, 'bar-x.dataSorting');
|
|
@@ -99,7 +99,8 @@ export const prepareBarXData = async (args) => {
|
|
|
99
99
|
const currentGroupWidth = rectWidth * stacks.length + rectGap * (stacks.length - 1);
|
|
100
100
|
for (let groupItemIndex = 0; groupItemIndex < stacks.length; groupItemIndex++) {
|
|
101
101
|
const yValues = stacks[groupItemIndex];
|
|
102
|
-
let
|
|
102
|
+
let positiveStackHeight = 0;
|
|
103
|
+
let negativeStackHeight = 0;
|
|
103
104
|
const stackItems = [];
|
|
104
105
|
const sortedData = sortKey
|
|
105
106
|
? sort(yValues, (a, b) => comparator(get(a, sortKey), get(b, sortKey)))
|
|
@@ -117,6 +118,7 @@ export const prepareBarXData = async (args) => {
|
|
|
117
118
|
if (xAxis.type === 'category') {
|
|
118
119
|
const xBandScale = xScale;
|
|
119
120
|
const xBandScaleDomain = xBandScale.domain();
|
|
121
|
+
// eslint-disable-next-line max-depth
|
|
120
122
|
if (xBandScaleDomain.indexOf(xValue) === -1) {
|
|
121
123
|
continue;
|
|
122
124
|
}
|
|
@@ -141,7 +143,9 @@ export const prepareBarXData = async (args) => {
|
|
|
141
143
|
}
|
|
142
144
|
const barData = {
|
|
143
145
|
x,
|
|
144
|
-
y:
|
|
146
|
+
y: yDataValue > 0
|
|
147
|
+
? yAxisTop + y - positiveStackHeight
|
|
148
|
+
: yAxisTop + base + negativeStackHeight,
|
|
145
149
|
width: rectWidth,
|
|
146
150
|
height: shapeHeight,
|
|
147
151
|
opacity: get(yValue.data, 'opacity', null),
|
|
@@ -151,11 +155,16 @@ export const prepareBarXData = async (args) => {
|
|
|
151
155
|
isLastStackItem,
|
|
152
156
|
};
|
|
153
157
|
stackItems.push(barData);
|
|
154
|
-
|
|
158
|
+
if (yDataValue > 0) {
|
|
159
|
+
positiveStackHeight += height;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
negativeStackHeight += height;
|
|
163
|
+
}
|
|
155
164
|
}
|
|
156
165
|
if (series.some((s) => s.stacking === 'percent')) {
|
|
157
166
|
let acc = 0;
|
|
158
|
-
const ratio = plotHeight / (
|
|
167
|
+
const ratio = plotHeight / (positiveStackHeight - stackItems.length);
|
|
159
168
|
stackItems.forEach((item) => {
|
|
160
169
|
item.height = item.height * ratio;
|
|
161
170
|
item.y = plotHeight - item.height - acc;
|
|
@@ -168,7 +177,7 @@ export const prepareBarXData = async (args) => {
|
|
|
168
177
|
}
|
|
169
178
|
for (let i = 0; i < result.length; i++) {
|
|
170
179
|
const barData = result[i];
|
|
171
|
-
if (barData.series.dataLabels.enabled) {
|
|
180
|
+
if (barData.series.dataLabels.enabled && !isRangeSlider) {
|
|
172
181
|
const label = await getLabelData(barData);
|
|
173
182
|
if (barData.series.dataLabels.html && label) {
|
|
174
183
|
barData.htmlElements.push({
|
|
@@ -45,7 +45,8 @@ export async function prepareBarYData(args) {
|
|
|
45
45
|
stacks.forEach((measureValues, groupItemIndex) => {
|
|
46
46
|
const baseValue = xAxis.type === 'logarithmic' ? 0 : xLinearScale(0);
|
|
47
47
|
const base = baseValue - measureValues[0].series.borderWidth;
|
|
48
|
-
let
|
|
48
|
+
let positiveStack = base;
|
|
49
|
+
let negativeStack = base;
|
|
49
50
|
const stackItems = [];
|
|
50
51
|
const sortedData = sortKey
|
|
51
52
|
? sort(measureValues, (a, b) => comparator(get(a, sortKey), get(b, sortKey)))
|
|
@@ -93,7 +94,8 @@ export async function prepareBarYData(args) {
|
|
|
93
94
|
const isLastStackItem = xValueIndex === sortedData.length - 1;
|
|
94
95
|
// Calculate position with border compensation
|
|
95
96
|
// Border extends halfBorder outward from the shape, so we need to adjust position
|
|
96
|
-
let itemX =
|
|
97
|
+
let itemX = xValue > baseRangeValue ? positiveStack : negativeStack - width;
|
|
98
|
+
itemX += itemStackGap;
|
|
97
99
|
const halfBorder = borderWidth / 2;
|
|
98
100
|
if (isFirstInStack && xValue > 0) {
|
|
99
101
|
// Positive bar: border extends left, so shift position left by halfBorder
|
|
@@ -119,7 +121,12 @@ export async function prepareBarYData(args) {
|
|
|
119
121
|
isLastStackItem,
|
|
120
122
|
};
|
|
121
123
|
stackItems.push(item);
|
|
122
|
-
|
|
124
|
+
if (xValue > baseRangeValue) {
|
|
125
|
+
positiveStack += width;
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
negativeStack -= width;
|
|
129
|
+
}
|
|
123
130
|
});
|
|
124
131
|
result.push(...stackItems);
|
|
125
132
|
});
|
|
@@ -58,6 +58,7 @@ export const useShapes = (args) => {
|
|
|
58
58
|
yScale,
|
|
59
59
|
boundsHeight,
|
|
60
60
|
split,
|
|
61
|
+
isRangeSlider,
|
|
61
62
|
});
|
|
62
63
|
shapes.push(React.createElement(BarXSeriesShapes, { key: SERIES_TYPE.BarX, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
|
|
63
64
|
shapesData.push(...preparedData);
|
|
@@ -124,6 +125,7 @@ export const useShapes = (args) => {
|
|
|
124
125
|
boundsHeight,
|
|
125
126
|
split,
|
|
126
127
|
isOutsideBounds,
|
|
128
|
+
isRangeSlider,
|
|
127
129
|
});
|
|
128
130
|
shapes.push(React.createElement(AreaSeriesShapes, { key: SERIES_TYPE.Area, dispatcher: dispatcher, seriesOptions: seriesOptions, preparedData: preparedData, htmlLayout: htmlLayout, clipPathId: clipPathId }));
|
|
129
131
|
shapesData.push(...preparedData);
|
|
@@ -34,7 +34,7 @@ export const LineSeriesShapes = (args) => {
|
|
|
34
34
|
.attr('fill', 'none')
|
|
35
35
|
.attr('stroke', (d) => d.color)
|
|
36
36
|
.attr('stroke-width', (d) => d.lineWidth)
|
|
37
|
-
.attr('stroke-linejoin', (d) => d.
|
|
37
|
+
.attr('stroke-linejoin', (d) => d.linejoin)
|
|
38
38
|
.attr('stroke-linecap', (d) => d.linecap)
|
|
39
39
|
.attr('stroke-dasharray', (d) => getLineDashArray(d.dashStyle, d.lineWidth))
|
|
40
40
|
.attr('opacity', (d) => d.opacity)
|
|
@@ -44,7 +44,7 @@ export const prepareLineData = async (args) => {
|
|
|
44
44
|
});
|
|
45
45
|
const htmlElements = [];
|
|
46
46
|
const labels = [];
|
|
47
|
-
if (s.dataLabels.enabled) {
|
|
47
|
+
if (s.dataLabels.enabled && !isRangeSlider) {
|
|
48
48
|
if (s.dataLabels.html) {
|
|
49
49
|
const list = await Promise.all(points.reduce((result, p) => {
|
|
50
50
|
if (p.y === null) {
|
|
@@ -108,6 +108,7 @@ export const prepareLineData = async (args) => {
|
|
|
108
108
|
lineWidth: (_b = (isRangeSlider ? s.rangeSlider.lineWidth : undefined)) !== null && _b !== void 0 ? _b : s.lineWidth,
|
|
109
109
|
dashStyle: s.dashStyle,
|
|
110
110
|
linecap: s.linecap,
|
|
111
|
+
linejoin: s.linejoin,
|
|
111
112
|
opacity: (_c = (isRangeSlider ? s.rangeSlider.opacity : undefined)) !== null && _c !== void 0 ? _c : s.opacity,
|
|
112
113
|
};
|
|
113
114
|
acc.push(result);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DashStyle, LineCap } from '../../../constants';
|
|
1
|
+
import type { DashStyle, LineCap, LineJoin } from '../../../constants';
|
|
2
2
|
import type { HtmlItem, LabelData, LineSeriesData, LineSeriesLineBaseStyle } from '../../../types';
|
|
3
3
|
import type { PreparedLineSeries } from '../../useSeries/types';
|
|
4
4
|
export type PointData = {
|
|
@@ -30,4 +30,5 @@ export type PreparedLineData = {
|
|
|
30
30
|
color: string;
|
|
31
31
|
dashStyle: DashStyle;
|
|
32
32
|
linecap: LineCap;
|
|
33
|
+
linejoin: LineJoin;
|
|
33
34
|
} & Required<LineSeriesLineBaseStyle>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DashStyle, LineCap, SERIES_TYPE } from '../../constants';
|
|
1
|
+
import type { DashStyle, LineCap, LineJoin, SERIES_TYPE } from '../../constants';
|
|
2
2
|
import type { MeaningfulAny } from '../misc';
|
|
3
3
|
import type { BaseSeries, BaseSeriesData, BaseSeriesLegend } from './base';
|
|
4
4
|
import type { RectLegendSymbolOptions } from './legend';
|
|
@@ -53,6 +53,8 @@ export interface LineSeries<T = MeaningfulAny> extends BaseSeries, LineSeriesLin
|
|
|
53
53
|
dashStyle?: DashStyle;
|
|
54
54
|
/** Option for line cap style */
|
|
55
55
|
linecap?: `${LineCap}`;
|
|
56
|
+
/** Defines the shape to be used at the corners of the line */
|
|
57
|
+
linejoin?: `${LineJoin}`;
|
|
56
58
|
/** Individual series legend options. Has higher priority than legend options in widget data */
|
|
57
59
|
legend?: BaseSeriesLegend & {
|
|
58
60
|
symbol?: RectLegendSymbolOptions;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type React from 'react';
|
|
2
|
-
import type { DashStyle, LineCap } from '../../constants';
|
|
2
|
+
import type { DashStyle, LineCap, LineJoin } from '../../constants';
|
|
3
3
|
import type { MeaningfulAny } from '../misc';
|
|
4
4
|
import type { AreaSeries, AreaSeriesData } from './area';
|
|
5
5
|
import type { BarXSeries, BarXSeriesData } from './bar-x';
|
|
@@ -211,6 +211,11 @@ export interface ChartSeriesOptions {
|
|
|
211
211
|
* @default 'round' when dashStyle is not 'solid', 'none' when dashStyle is not 'solid'
|
|
212
212
|
* */
|
|
213
213
|
linecap?: `${LineCap}`;
|
|
214
|
+
/** Defines the shape to be used at the corners of the line
|
|
215
|
+
*
|
|
216
|
+
* @default 'round' when dashStyle is not 'solid', 'unset' when dashStyle is not 'solid'
|
|
217
|
+
* */
|
|
218
|
+
linejoin?: `${LineJoin}`;
|
|
214
219
|
};
|
|
215
220
|
area?: {
|
|
216
221
|
/** Pixel width of the graph line.
|
|
@@ -46,8 +46,8 @@ export declare function isSeriesWithCategoryValues(series: UnknownSeries): serie
|
|
|
46
46
|
export declare const getDomainDataXBySeries: (series: UnknownSeries[]) => ({} | undefined)[];
|
|
47
47
|
export declare function getDefaultMaxXAxisValue(series: UnknownSeries[]): 0 | undefined;
|
|
48
48
|
export declare function getDefaultMinXAxisValue(series: UnknownSeries[]): number | undefined;
|
|
49
|
-
export declare function getDefaultMinYAxisValue(series?: UnknownSeries[]): number | undefined;
|
|
50
49
|
export declare const getDomainDataYBySeries: (series: UnknownSeries[]) => unknown[];
|
|
50
|
+
export declare function getDefaultMinYAxisValue(series?: UnknownSeries[]): number | undefined;
|
|
51
51
|
export declare const getSeriesNames: (series: ChartSeries[]) => string[];
|
|
52
52
|
export declare const getOnlyVisibleSeries: <T extends {
|
|
53
53
|
visible: boolean;
|
|
@@ -46,7 +46,8 @@ function getDomainDataForStackedSeries(seriesList, keyAttr = 'x', valueAttr = 'y
|
|
|
46
46
|
const acc = [];
|
|
47
47
|
const stackedSeries = group(seriesList, getSeriesStackId);
|
|
48
48
|
Array.from(stackedSeries).forEach(([_stackId, seriesStack]) => {
|
|
49
|
-
const
|
|
49
|
+
const positiveValues = {};
|
|
50
|
+
const negativeValues = {};
|
|
50
51
|
seriesStack.forEach((singleSeries) => {
|
|
51
52
|
const data = new Map();
|
|
52
53
|
singleSeries.data.forEach((point) => {
|
|
@@ -65,10 +66,15 @@ function getDomainDataForStackedSeries(seriesList, keyAttr = 'x', valueAttr = 'y
|
|
|
65
66
|
data.set(key, value);
|
|
66
67
|
});
|
|
67
68
|
Array.from(data).forEach(([key, value]) => {
|
|
68
|
-
|
|
69
|
+
if (value >= 0) {
|
|
70
|
+
positiveValues[key] = (positiveValues[key] || 0) + value;
|
|
71
|
+
}
|
|
72
|
+
if (value < 0) {
|
|
73
|
+
negativeValues[key] = (negativeValues[key] || 0) + value;
|
|
74
|
+
}
|
|
69
75
|
});
|
|
70
76
|
});
|
|
71
|
-
acc.push(...Object.values(values));
|
|
77
|
+
acc.push(...Object.values(negativeValues), ...Object.values(positiveValues));
|
|
72
78
|
});
|
|
73
79
|
return acc;
|
|
74
80
|
}
|
|
@@ -98,27 +104,9 @@ export function getDefaultMaxXAxisValue(series) {
|
|
|
98
104
|
}
|
|
99
105
|
export function getDefaultMinXAxisValue(series) {
|
|
100
106
|
if (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME_ON_X_AXIS.includes(s.type))) {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
const minXValue = s.data.reduce((res, d) => Math.min(res, get(d, 'x', 0)), 0);
|
|
105
|
-
return Math.min(minValue, minXValue);
|
|
106
|
-
}, 0);
|
|
107
|
-
}
|
|
108
|
-
return undefined;
|
|
109
|
-
}
|
|
110
|
-
export function getDefaultMinYAxisValue(series) {
|
|
111
|
-
if (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type))) {
|
|
112
|
-
if (series.some((s) => s.type === SERIES_TYPE.Waterfall)) {
|
|
113
|
-
const seriesData = series.map((s) => s.data).flat();
|
|
114
|
-
const minSubTotal = seriesData.reduce((res, d) => Math.min(res, getWaterfallPointSubtotal(d, seriesData) || 0), 0);
|
|
115
|
-
return Math.min(0, minSubTotal);
|
|
116
|
-
}
|
|
117
|
-
return series.reduce((minValue, s) => {
|
|
118
|
-
// https://github.com/gravity-ui/charts/issues/160
|
|
119
|
-
// @ts-expect-error
|
|
120
|
-
const minYValue = s.data.reduce((res, d) => Math.min(res, get(d, 'y', 0)), 0);
|
|
121
|
-
return Math.min(minValue, minYValue);
|
|
107
|
+
const domainData = getDomainDataXBySeries(series);
|
|
108
|
+
return domainData.reduce((minValue, d) => {
|
|
109
|
+
return Math.min(minValue, d);
|
|
122
110
|
}, 0);
|
|
123
111
|
}
|
|
124
112
|
return undefined;
|
|
@@ -151,6 +139,20 @@ export const getDomainDataYBySeries = (series) => {
|
|
|
151
139
|
}, []);
|
|
152
140
|
return Array.from(new Set(items));
|
|
153
141
|
};
|
|
142
|
+
export function getDefaultMinYAxisValue(series) {
|
|
143
|
+
if (series === null || series === void 0 ? void 0 : series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type))) {
|
|
144
|
+
if (series.some((s) => s.type === SERIES_TYPE.Waterfall)) {
|
|
145
|
+
const seriesData = series.map((s) => s.data).flat();
|
|
146
|
+
const minSubTotal = seriesData.reduce((res, d) => Math.min(res, getWaterfallPointSubtotal(d, seriesData) || 0), 0);
|
|
147
|
+
return Math.min(0, minSubTotal);
|
|
148
|
+
}
|
|
149
|
+
const domainData = getDomainDataYBySeries(series);
|
|
150
|
+
return domainData.reduce((minValue, d) => {
|
|
151
|
+
return Math.min(minValue, d);
|
|
152
|
+
}, 0);
|
|
153
|
+
}
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
154
156
|
// Uses to get all series names array (except `pie` charts)
|
|
155
157
|
export const getSeriesNames = (series) => {
|
|
156
158
|
return series.reduce((acc, s) => {
|