@gravity-ui/chartkit 5.8.0 → 5.10.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/build/plugins/d3/examples/line/LogarithmicAxis.d.ts +2 -0
- package/build/plugins/d3/examples/line/LogarithmicAxis.js +38 -0
- package/build/plugins/d3/renderer/components/AxisX.d.ts +8 -0
- package/build/plugins/d3/renderer/components/AxisX.js +41 -7
- package/build/plugins/d3/renderer/components/AxisY.js +49 -7
- package/build/plugins/d3/renderer/components/Chart.js +2 -1
- package/build/plugins/d3/renderer/components/styles.css +4 -0
- package/build/plugins/d3/renderer/constants/defaults/axis.d.ts +6 -8
- package/build/plugins/d3/renderer/constants/defaults/axis.js +7 -1
- package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +9 -5
- package/build/plugins/d3/renderer/hooks/useChartOptions/types.d.ts +4 -1
- package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +16 -7
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.d.ts +2 -1
- package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +16 -10
- package/build/plugins/d3/renderer/utils/axis.d.ts +5 -0
- package/build/plugins/d3/renderer/utils/axis.js +21 -0
- package/build/plugins/d3/renderer/utils/text.d.ts +10 -0
- package/build/plugins/d3/renderer/utils/text.js +61 -8
- package/build/plugins/highcharts/renderer/HighchartsWidget.d.ts +2 -0
- package/build/plugins/highcharts/renderer/components/HighchartsComponent.js +1 -1
- package/build/plugins/highcharts/renderer/components/StyledSplitPane/StyledSplitPane.d.ts +1 -2
- package/build/plugins/highcharts/renderer/components/withSplitPane/withSplitPane.d.ts +7 -0
- package/build/plugins/highcharts/renderer/components/withSplitPane/withSplitPane.js +8 -6
- package/build/plugins/highcharts/renderer/helpers/config/config.js +11 -1
- package/build/types/widget-data/axis.d.ts +9 -1
- package/build/types/widget.d.ts +3 -0
- package/package.json +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Col, Container, Row, Text } from '@gravity-ui/uikit';
|
|
3
|
+
import { randomNormal } from 'd3';
|
|
4
|
+
import { ChartKit } from '../../../../components/ChartKit';
|
|
5
|
+
import { ExampleWrapper } from '../ExampleWrapper';
|
|
6
|
+
export const LineWithLogarithmicAxis = () => {
|
|
7
|
+
const randomY = randomNormal(0, 100);
|
|
8
|
+
const widgetData = {
|
|
9
|
+
series: {
|
|
10
|
+
data: [
|
|
11
|
+
{
|
|
12
|
+
type: 'line',
|
|
13
|
+
name: 'Line series',
|
|
14
|
+
data: new Array(25).fill(null).map((_, index) => ({
|
|
15
|
+
x: index,
|
|
16
|
+
y: Math.abs(randomY()),
|
|
17
|
+
})),
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
const lineWidgetData = Object.assign(Object.assign({}, widgetData), { title: { text: 'line' } });
|
|
23
|
+
const logarithmicWidgetData = Object.assign(Object.assign({}, widgetData), { title: { text: 'logarithmic' }, yAxis: [
|
|
24
|
+
{
|
|
25
|
+
type: 'logarithmic',
|
|
26
|
+
},
|
|
27
|
+
] });
|
|
28
|
+
return (React.createElement(Container, { spaceRow: 5 },
|
|
29
|
+
React.createElement(Row, { space: 1 },
|
|
30
|
+
React.createElement(Text, { variant: "header-2" }, "logarithmic VS line")),
|
|
31
|
+
React.createElement(Row, { space: 3 },
|
|
32
|
+
React.createElement(Col, { s: 12, m: 6 },
|
|
33
|
+
React.createElement(ExampleWrapper, null,
|
|
34
|
+
React.createElement(ChartKit, { type: "d3", data: lineWidgetData }))),
|
|
35
|
+
React.createElement(Col, { s: 12, m: 6 },
|
|
36
|
+
React.createElement(ExampleWrapper, null,
|
|
37
|
+
React.createElement(ChartKit, { type: "d3", data: logarithmicWidgetData }))))));
|
|
38
|
+
};
|
|
@@ -7,5 +7,13 @@ type Props = {
|
|
|
7
7
|
scale: ChartScale;
|
|
8
8
|
split: PreparedSplit;
|
|
9
9
|
};
|
|
10
|
+
export declare function getTitlePosition(args: {
|
|
11
|
+
axis: PreparedAxis;
|
|
12
|
+
width: number;
|
|
13
|
+
rowCount: number;
|
|
14
|
+
}): {
|
|
15
|
+
x: number;
|
|
16
|
+
y: number;
|
|
17
|
+
};
|
|
10
18
|
export declare const AxisX: React.NamedExoticComponent<Props>;
|
|
11
19
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { select } from 'd3';
|
|
3
3
|
import { block } from '../../../../utils/cn';
|
|
4
|
-
import { formatAxisTickLabel, getClosestPointsRange, getMaxTickCount, getScaleTicks, getTicksCount,
|
|
4
|
+
import { formatAxisTickLabel, getAxisTitleRows, getClosestPointsRange, getMaxTickCount, getScaleTicks, getTicksCount, handleOverflowingText, } from '../utils';
|
|
5
5
|
import { axisBottom } from '../utils/axis-generators';
|
|
6
6
|
const b = block('d3-axis');
|
|
7
7
|
function getLabelFormatter({ axis, scale }) {
|
|
@@ -18,6 +18,29 @@ function getLabelFormatter({ axis, scale }) {
|
|
|
18
18
|
});
|
|
19
19
|
};
|
|
20
20
|
}
|
|
21
|
+
export function getTitlePosition(args) {
|
|
22
|
+
const { axis, width, rowCount } = args;
|
|
23
|
+
if (rowCount < 1) {
|
|
24
|
+
return { x: 0, y: 0 };
|
|
25
|
+
}
|
|
26
|
+
let x;
|
|
27
|
+
const y = axis.title.height / rowCount + axis.title.margin + axis.labels.height + axis.labels.margin;
|
|
28
|
+
switch (axis.title.align) {
|
|
29
|
+
case 'left': {
|
|
30
|
+
x = axis.title.width / 2;
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
case 'right': {
|
|
34
|
+
x = width - axis.title.width / 2;
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
case 'center': {
|
|
38
|
+
x = width / 2;
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return { x, y };
|
|
43
|
+
}
|
|
21
44
|
export const AxisX = React.memo(function AxisX(props) {
|
|
22
45
|
const { axis, width, height: totalHeight, scale, split } = props;
|
|
23
46
|
const ref = React.useRef(null);
|
|
@@ -58,16 +81,27 @@ export const AxisX = React.memo(function AxisX(props) {
|
|
|
58
81
|
svgElement.call(xAxisGenerator).attr('class', b());
|
|
59
82
|
// add an axis header if necessary
|
|
60
83
|
if (axis.title.text) {
|
|
61
|
-
const
|
|
84
|
+
const titleRows = getAxisTitleRows({ axis, textMaxWidth: width });
|
|
62
85
|
svgElement
|
|
63
86
|
.append('text')
|
|
64
87
|
.attr('class', b('title'))
|
|
65
|
-
.attr('
|
|
66
|
-
|
|
67
|
-
|
|
88
|
+
.attr('transform', () => {
|
|
89
|
+
const { x, y } = getTitlePosition({ axis, width, rowCount: titleRows.length });
|
|
90
|
+
return `translate(${x}, ${y})`;
|
|
91
|
+
})
|
|
68
92
|
.attr('font-size', axis.title.style.fontSize)
|
|
69
|
-
.
|
|
70
|
-
.
|
|
93
|
+
.attr('text-anchor', 'middle')
|
|
94
|
+
.selectAll('tspan')
|
|
95
|
+
.data(titleRows)
|
|
96
|
+
.join('tspan')
|
|
97
|
+
.attr('x', 0)
|
|
98
|
+
.attr('y', (d) => d.y)
|
|
99
|
+
.text((d) => d.text)
|
|
100
|
+
.each((_d, index, nodes) => {
|
|
101
|
+
if (index === axis.title.maxRowCount - 1) {
|
|
102
|
+
handleOverflowingText(nodes[index], width);
|
|
103
|
+
}
|
|
104
|
+
});
|
|
71
105
|
}
|
|
72
106
|
}, [axis, width, totalHeight, scale, split]);
|
|
73
107
|
return React.createElement("g", { ref: ref });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { axisLeft, axisRight, line, select } from 'd3';
|
|
3
3
|
import { block } from '../../../../utils/cn';
|
|
4
|
-
import { calculateCos, calculateSin, formatAxisTickLabel, getAxisHeight, getClosestPointsRange, getScaleTicks, getTicksCount,
|
|
4
|
+
import { calculateCos, calculateSin, formatAxisTickLabel, getAxisHeight, getAxisTitleRows, getClosestPointsRange, getScaleTicks, getTicksCount, handleOverflowingText, parseTransformStyle, setEllipsisForOverflowTexts, wrapText, } from '../utils';
|
|
5
5
|
const b = block('d3-axis');
|
|
6
6
|
function transformLabel(args) {
|
|
7
7
|
const { node, axis } = args;
|
|
@@ -51,6 +51,33 @@ function getAxisGenerator(args) {
|
|
|
51
51
|
}
|
|
52
52
|
return axisGenerator;
|
|
53
53
|
}
|
|
54
|
+
function getTitlePosition(args) {
|
|
55
|
+
const { axis, axisHeight, rowCount } = args;
|
|
56
|
+
if (rowCount < 1) {
|
|
57
|
+
return { x: 0, y: 0 };
|
|
58
|
+
}
|
|
59
|
+
const x = -(axis.title.height -
|
|
60
|
+
axis.title.height / rowCount +
|
|
61
|
+
axis.title.margin +
|
|
62
|
+
axis.labels.margin +
|
|
63
|
+
axis.labels.width);
|
|
64
|
+
let y;
|
|
65
|
+
switch (axis.title.align) {
|
|
66
|
+
case 'left': {
|
|
67
|
+
y = axisHeight - axis.title.width / 2;
|
|
68
|
+
break;
|
|
69
|
+
}
|
|
70
|
+
case 'right': {
|
|
71
|
+
y = axis.title.width / 2;
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'center': {
|
|
75
|
+
y = axisHeight / 2;
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { x, y };
|
|
80
|
+
}
|
|
54
81
|
export const AxisY = (props) => {
|
|
55
82
|
const { axes, width, height: totalHeight, scale, split } = props;
|
|
56
83
|
const height = getAxisHeight({ split, boundsHeight: totalHeight });
|
|
@@ -144,13 +171,28 @@ export const AxisY = (props) => {
|
|
|
144
171
|
.append('text')
|
|
145
172
|
.attr('class', b('title'))
|
|
146
173
|
.attr('text-anchor', 'middle')
|
|
147
|
-
.attr('dy', (d) => -(d.title.margin + d.labels.margin + d.labels.width))
|
|
148
|
-
.attr('dx', (d) => (d.position === 'left' ? -height / 2 : height / 2))
|
|
149
174
|
.attr('font-size', (d) => d.title.style.fontSize)
|
|
150
|
-
.attr('transform', (d) =>
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
175
|
+
.attr('transform', (d) => {
|
|
176
|
+
const titleRows = wrapText({
|
|
177
|
+
text: d.title.text,
|
|
178
|
+
style: d.title.style,
|
|
179
|
+
width: height,
|
|
180
|
+
});
|
|
181
|
+
const rowCount = Math.min(titleRows.length, d.title.maxRowCount);
|
|
182
|
+
const { x, y } = getTitlePosition({ axis: d, axisHeight: height, rowCount });
|
|
183
|
+
const angle = d.position === 'left' ? -90 : 90;
|
|
184
|
+
return `translate(${x}, ${y}) rotate(${angle})`;
|
|
185
|
+
})
|
|
186
|
+
.selectAll('tspan')
|
|
187
|
+
.data((d) => getAxisTitleRows({ axis: d, textMaxWidth: height }))
|
|
188
|
+
.join('tspan')
|
|
189
|
+
.attr('x', 0)
|
|
190
|
+
.attr('y', (d) => d.y)
|
|
191
|
+
.text((d) => d.text)
|
|
192
|
+
.each((_d, index, nodes) => {
|
|
193
|
+
if (index === nodes.length - 1) {
|
|
194
|
+
handleOverflowingText(nodes[index], height);
|
|
195
|
+
}
|
|
154
196
|
});
|
|
155
197
|
}, [axes, width, height, scale, split]);
|
|
156
198
|
return React.createElement("g", { ref: ref, className: b('container') });
|
|
@@ -32,7 +32,8 @@ export const Chart = (props) => {
|
|
|
32
32
|
const yAxis = React.useMemo(() => getPreparedYAxis({
|
|
33
33
|
series: data.series.data,
|
|
34
34
|
yAxis: data.yAxis,
|
|
35
|
-
|
|
35
|
+
height,
|
|
36
|
+
}), [data, height]);
|
|
36
37
|
const { legendItems, legendConfig, preparedSeries, preparedSeriesOptions, preparedLegend, handleLegendItemClick, } = useSeries({
|
|
37
38
|
chartWidth: width,
|
|
38
39
|
chartHeight: height,
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
import { ChartKitWidgetAxisType } from '../../../../../types';
|
|
1
|
+
import type { BaseTextStyle, ChartKitWidgetAxis, ChartKitWidgetAxisType } from '../../../../../types';
|
|
2
2
|
export declare const axisLabelsDefaults: {
|
|
3
3
|
margin: number;
|
|
4
4
|
padding: number;
|
|
5
5
|
fontSize: number;
|
|
6
6
|
maxWidth: number;
|
|
7
7
|
};
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
fontSize: string;
|
|
11
|
-
};
|
|
12
|
-
export declare const yAxisTitleDefaults: {
|
|
13
|
-
margin: number;
|
|
14
|
-
fontSize: string;
|
|
8
|
+
type AxisTitleDefaults = Required<ChartKitWidgetAxis['title']> & {
|
|
9
|
+
style: BaseTextStyle;
|
|
15
10
|
};
|
|
11
|
+
export declare const xAxisTitleDefaults: AxisTitleDefaults;
|
|
12
|
+
export declare const yAxisTitleDefaults: AxisTitleDefaults;
|
|
16
13
|
export declare const DEFAULT_AXIS_TYPE: ChartKitWidgetAxisType;
|
|
14
|
+
export {};
|
|
@@ -5,7 +5,13 @@ export const axisLabelsDefaults = {
|
|
|
5
5
|
maxWidth: 80,
|
|
6
6
|
};
|
|
7
7
|
const axisTitleDefaults = {
|
|
8
|
-
|
|
8
|
+
text: '',
|
|
9
|
+
margin: 0,
|
|
10
|
+
style: {
|
|
11
|
+
fontSize: '14px',
|
|
12
|
+
},
|
|
13
|
+
align: 'center',
|
|
14
|
+
maxRowCount: 1,
|
|
9
15
|
};
|
|
10
16
|
export const xAxisTitleDefaults = Object.assign(Object.assign({}, axisTitleDefaults), { margin: 4 });
|
|
11
17
|
export const yAxisTitleDefaults = Object.assign(Object.assign({}, axisTitleDefaults), { margin: 8 });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { extent, scaleBand, scaleLinear, scaleUtc } from 'd3';
|
|
2
|
+
import { extent, scaleBand, scaleLinear, scaleLog, scaleUtc } from 'd3';
|
|
3
3
|
import get from 'lodash/get';
|
|
4
4
|
import { DEFAULT_AXIS_TYPE } from '../../constants';
|
|
5
5
|
import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
|
|
@@ -24,7 +24,8 @@ export function createYScale(axis, series, boundsHeight) {
|
|
|
24
24
|
const yCategories = get(axis, 'categories');
|
|
25
25
|
const yTimestamps = get(axis, 'timestamps');
|
|
26
26
|
switch (yType) {
|
|
27
|
-
case 'linear':
|
|
27
|
+
case 'linear':
|
|
28
|
+
case 'logarithmic': {
|
|
28
29
|
const domain = getDomainDataYBySeries(series);
|
|
29
30
|
const range = [boundsHeight, boundsHeight * axis.maxPadding];
|
|
30
31
|
if (isNumericalArrayData(domain)) {
|
|
@@ -34,7 +35,8 @@ export function createYScale(axis, series, boundsHeight) {
|
|
|
34
35
|
if (series.some((s) => CHART_SERIES_WITH_VOLUME_ON_Y_AXIS.includes(s.type))) {
|
|
35
36
|
yMaxValue = Math.max(yMaxValue, 0);
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
+
const scaleFn = yType === 'logarithmic' ? scaleLog : scaleLinear;
|
|
39
|
+
return scaleFn().domain([yMinValue, yMaxValue]).range(range).nice();
|
|
38
40
|
}
|
|
39
41
|
break;
|
|
40
42
|
}
|
|
@@ -94,13 +96,15 @@ export function createXScale(axis, series, boundsWidth) {
|
|
|
94
96
|
const xAxisMinPadding = boundsWidth * maxPadding + calculateXAxisPadding(series);
|
|
95
97
|
const xRange = [0, boundsWidth - xAxisMinPadding];
|
|
96
98
|
switch (xType) {
|
|
97
|
-
case 'linear':
|
|
99
|
+
case 'linear':
|
|
100
|
+
case 'logarithmic': {
|
|
98
101
|
const domain = getDomainDataXBySeries(series);
|
|
99
102
|
if (isNumericalArrayData(domain)) {
|
|
100
103
|
const [domainXMin, domainXMax] = extent(domain);
|
|
101
104
|
const xMinValue = typeof xMin === 'number' ? xMin : domainXMin;
|
|
102
105
|
const xMaxValue = typeof xMax === 'number' ? Math.max(xMax, domainXMax) : domainXMax;
|
|
103
|
-
|
|
106
|
+
const scaleFn = xType === 'logarithmic' ? scaleLog : scaleLinear;
|
|
107
|
+
return scaleFn().domain([xMinValue, xMaxValue]).range(xRange).nice();
|
|
104
108
|
}
|
|
105
109
|
break;
|
|
106
110
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BaseTextStyle, ChartKitWidgetAxis, ChartKitWidgetAxisLabels, ChartKitWidgetAxisType, ChartKitWidgetData, ChartMargin } from '../../../../../types';
|
|
1
|
+
import type { BaseTextStyle, ChartKitWidgetAxis, ChartKitWidgetAxisLabels, ChartKitWidgetAxisTitleAlignment, ChartKitWidgetAxisType, ChartKitWidgetData, ChartMargin } from '../../../../../types';
|
|
2
2
|
type PreparedAxisLabels = Omit<ChartKitWidgetAxisLabels, 'enabled' | 'padding' | 'style' | 'autoRotation'> & Required<Pick<ChartKitWidgetAxisLabels, 'enabled' | 'padding' | 'margin' | 'rotation'>> & {
|
|
3
3
|
style: BaseTextStyle;
|
|
4
4
|
rotation: number;
|
|
@@ -15,9 +15,12 @@ export type PreparedAxis = Omit<ChartKitWidgetAxis, 'type' | 'labels'> & {
|
|
|
15
15
|
labels: PreparedAxisLabels;
|
|
16
16
|
title: {
|
|
17
17
|
height: number;
|
|
18
|
+
width: number;
|
|
18
19
|
text: string;
|
|
19
20
|
margin: number;
|
|
20
21
|
style: BaseTextStyle;
|
|
22
|
+
align: ChartKitWidgetAxisTitleAlignment;
|
|
23
|
+
maxRowCount: number;
|
|
21
24
|
};
|
|
22
25
|
min?: number;
|
|
23
26
|
grid: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
2
|
import { DEFAULT_AXIS_LABEL_FONT_SIZE, axisLabelsDefaults, xAxisTitleDefaults, } from '../../constants';
|
|
3
|
-
import { CHART_SERIES_WITH_VOLUME_ON_X_AXIS, calculateCos, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, getXAxisItems, hasOverlappingLabels, } from '../../utils';
|
|
3
|
+
import { CHART_SERIES_WITH_VOLUME_ON_X_AXIS, calculateCos, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, getXAxisItems, hasOverlappingLabels, wrapText, } from '../../utils';
|
|
4
4
|
import { createXScale } from '../useAxisScales';
|
|
5
5
|
function getLabelSettings({ axis, series, width, autoRotation = true, }) {
|
|
6
6
|
const scale = createXScale(axis, series, width);
|
|
@@ -50,9 +50,17 @@ function getAxisMin(axis, series) {
|
|
|
50
50
|
export const getPreparedXAxis = ({ xAxis, series, width, }) => {
|
|
51
51
|
var _a;
|
|
52
52
|
const titleText = get(xAxis, 'title.text', '');
|
|
53
|
-
const titleStyle = {
|
|
54
|
-
|
|
55
|
-
|
|
53
|
+
const titleStyle = Object.assign(Object.assign({}, xAxisTitleDefaults.style), get(xAxis, 'title.style'));
|
|
54
|
+
const titleMaxRowsCount = get(xAxis, 'title.maxRowCount', xAxisTitleDefaults.maxRowCount);
|
|
55
|
+
const estimatedTitleRows = wrapText({
|
|
56
|
+
text: titleText,
|
|
57
|
+
style: titleStyle,
|
|
58
|
+
width,
|
|
59
|
+
}).slice(0, titleMaxRowsCount);
|
|
60
|
+
const titleSize = getLabelsSize({
|
|
61
|
+
labels: [titleText],
|
|
62
|
+
style: titleStyle,
|
|
63
|
+
});
|
|
56
64
|
const labelsStyle = {
|
|
57
65
|
fontSize: get(xAxis, 'labels.style.fontSize', DEFAULT_AXIS_LABEL_FONT_SIZE),
|
|
58
66
|
};
|
|
@@ -78,9 +86,10 @@ export const getPreparedXAxis = ({ xAxis, series, width, }) => {
|
|
|
78
86
|
text: titleText,
|
|
79
87
|
style: titleStyle,
|
|
80
88
|
margin: get(xAxis, 'title.margin', xAxisTitleDefaults.margin),
|
|
81
|
-
height:
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
height: titleSize.maxHeight * estimatedTitleRows.length,
|
|
90
|
+
width: titleSize.maxWidth,
|
|
91
|
+
align: get(xAxis, 'title.align', xAxisTitleDefaults.align),
|
|
92
|
+
maxRowCount: get(xAxis, 'title.maxRowCount', xAxisTitleDefaults.maxRowCount),
|
|
84
93
|
},
|
|
85
94
|
min: getAxisMin(xAxis, series),
|
|
86
95
|
maxPadding: get(xAxis, 'maxPadding', 0.01),
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ChartKitWidgetSeries, ChartKitWidgetYAxis } from '../../../../../types';
|
|
2
2
|
import type { PreparedAxis } from './types';
|
|
3
|
-
export declare const getPreparedYAxis: ({ series, yAxis, }: {
|
|
3
|
+
export declare const getPreparedYAxis: ({ series, yAxis, height, }: {
|
|
4
4
|
series: ChartKitWidgetSeries[];
|
|
5
5
|
yAxis: ChartKitWidgetYAxis[] | undefined;
|
|
6
|
+
height: number;
|
|
6
7
|
}) => PreparedAxis[];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import get from 'lodash/get';
|
|
2
|
-
import { DEFAULT_AXIS_LABEL_FONT_SIZE, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
|
|
3
|
-
import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, } from '../../utils';
|
|
2
|
+
import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
|
|
3
|
+
import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, wrapText, } from '../../utils';
|
|
4
4
|
import { createYScale } from '../useAxisScales';
|
|
5
5
|
const getAxisLabelMaxWidth = (args) => {
|
|
6
6
|
const { axis, series } = args;
|
|
@@ -41,7 +41,7 @@ function getAxisMin(axis, series) {
|
|
|
41
41
|
}
|
|
42
42
|
return min;
|
|
43
43
|
}
|
|
44
|
-
export const getPreparedYAxis = ({ series, yAxis, }) => {
|
|
44
|
+
export const getPreparedYAxis = ({ series, yAxis, height, }) => {
|
|
45
45
|
const axisByPlot = [];
|
|
46
46
|
const axisItems = yAxis || [{}];
|
|
47
47
|
return axisItems.map((axisItem) => {
|
|
@@ -57,10 +57,15 @@ export const getPreparedYAxis = ({ series, yAxis, }) => {
|
|
|
57
57
|
fontSize: get(axisItem, 'labels.style.fontSize', DEFAULT_AXIS_LABEL_FONT_SIZE),
|
|
58
58
|
};
|
|
59
59
|
const titleText = get(axisItem, 'title.text', '');
|
|
60
|
-
const titleStyle = {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
const titleStyle = Object.assign(Object.assign({}, yAxisTitleDefaults.style), get(axisItem, 'title.style'));
|
|
61
|
+
const titleMaxRowsCount = get(axisItem, 'title.maxRowCount', yAxisTitleDefaults.maxRowCount);
|
|
62
|
+
const estimatedTitleRows = wrapText({
|
|
63
|
+
text: titleText,
|
|
64
|
+
style: titleStyle,
|
|
65
|
+
width: height,
|
|
66
|
+
}).slice(0, titleMaxRowsCount);
|
|
67
|
+
const titleSize = getLabelsSize({ labels: [titleText], style: titleStyle });
|
|
68
|
+
const axisType = get(axisItem, 'type', DEFAULT_AXIS_TYPE);
|
|
64
69
|
const preparedAxis = {
|
|
65
70
|
type: axisType,
|
|
66
71
|
labels: {
|
|
@@ -87,9 +92,10 @@ export const getPreparedYAxis = ({ series, yAxis, }) => {
|
|
|
87
92
|
text: titleText,
|
|
88
93
|
margin: get(axisItem, 'title.margin', yAxisTitleDefaults.margin),
|
|
89
94
|
style: titleStyle,
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
95
|
+
width: titleSize.maxWidth,
|
|
96
|
+
height: titleSize.maxHeight * estimatedTitleRows.length,
|
|
97
|
+
align: get(axisItem, 'title.align', yAxisTitleDefaults.align),
|
|
98
|
+
maxRowCount: titleMaxRowsCount,
|
|
93
99
|
},
|
|
94
100
|
min: getAxisMin(axisItem, series),
|
|
95
101
|
maxPadding: get(axisItem, 'maxPadding', 0.05),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AxisDomain, AxisScale, ScaleBand } from 'd3';
|
|
2
2
|
import type { PreparedAxis, PreparedSplit } from '../hooks';
|
|
3
|
+
import type { TextRow } from './text';
|
|
3
4
|
export declare function getTicksCount({ axis, range }: {
|
|
4
5
|
axis: PreparedAxis;
|
|
5
6
|
range: number;
|
|
@@ -24,3 +25,7 @@ export declare function getAxisHeight(args: {
|
|
|
24
25
|
split: PreparedSplit;
|
|
25
26
|
boundsHeight: number;
|
|
26
27
|
}): number;
|
|
28
|
+
export declare function getAxisTitleRows(args: {
|
|
29
|
+
axis: PreparedAxis;
|
|
30
|
+
textMaxWidth: number;
|
|
31
|
+
}): TextRow[];
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { wrapText } from './text';
|
|
1
2
|
export function getTicksCount({ axis, range }) {
|
|
2
3
|
let ticksCount;
|
|
3
4
|
if (axis.ticks.pixelInterval) {
|
|
@@ -48,3 +49,23 @@ export function getAxisHeight(args) {
|
|
|
48
49
|
}
|
|
49
50
|
return boundsHeight;
|
|
50
51
|
}
|
|
52
|
+
export function getAxisTitleRows(args) {
|
|
53
|
+
const { axis, textMaxWidth } = args;
|
|
54
|
+
if (axis.title.maxRowCount < 1) {
|
|
55
|
+
return [];
|
|
56
|
+
}
|
|
57
|
+
const textRows = wrapText({
|
|
58
|
+
text: axis.title.text,
|
|
59
|
+
style: axis.title.style,
|
|
60
|
+
width: textMaxWidth,
|
|
61
|
+
});
|
|
62
|
+
return textRows.reduce((acc, row, index) => {
|
|
63
|
+
if (index < axis.title.maxRowCount) {
|
|
64
|
+
acc.push(row);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
acc[axis.title.maxRowCount - 1].text += row.text;
|
|
68
|
+
}
|
|
69
|
+
return acc;
|
|
70
|
+
}, []);
|
|
71
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Selection } from 'd3';
|
|
2
2
|
import { BaseTextStyle } from '../../../../types';
|
|
3
|
+
export declare function handleOverflowingText(tSpan: SVGTSpanElement | null, maxWidth: number): void;
|
|
3
4
|
export declare function setEllipsisForOverflowText<T>(selection: Selection<SVGTextElement, T, null, unknown>, maxWidth: number): void;
|
|
4
5
|
export declare function setEllipsisForOverflowTexts<T>(selection: Selection<SVGTextElement, T, any, unknown>, maxWidth: ((datum: T) => number) | number): void;
|
|
5
6
|
export declare function hasOverlappingLabels({ width, labels, padding, style, }: {
|
|
@@ -16,3 +17,12 @@ export declare function getLabelsSize({ labels, style, rotation, }: {
|
|
|
16
17
|
maxHeight: number;
|
|
17
18
|
maxWidth: number;
|
|
18
19
|
};
|
|
20
|
+
export type TextRow = {
|
|
21
|
+
text: string;
|
|
22
|
+
y: number;
|
|
23
|
+
};
|
|
24
|
+
export declare function wrapText(args: {
|
|
25
|
+
text: string;
|
|
26
|
+
style?: BaseTextStyle;
|
|
27
|
+
width: number;
|
|
28
|
+
}): TextRow[];
|
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
import { select } from 'd3-selection';
|
|
2
|
-
export function
|
|
3
|
-
var _a, _b, _c
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
2
|
+
export function handleOverflowingText(tSpan, maxWidth) {
|
|
3
|
+
var _a, _b, _c;
|
|
4
|
+
if (!tSpan) {
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
const svg = tSpan.closest('svg');
|
|
8
|
+
if (!svg) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const textNode = tSpan.closest('text');
|
|
12
|
+
const angle = ((_a = Array.from((textNode === null || textNode === void 0 ? void 0 : textNode.transform.baseVal) || []).find((item) => item.angle)) === null || _a === void 0 ? void 0 : _a.angle) || 0;
|
|
13
|
+
const revertRotation = svg.createSVGTransform();
|
|
14
|
+
revertRotation.setRotate(-angle, 0, 0);
|
|
15
|
+
textNode === null || textNode === void 0 ? void 0 : textNode.transform.baseVal.appendItem(revertRotation);
|
|
16
|
+
let text = tSpan.textContent || '';
|
|
17
|
+
let textLength = ((_b = tSpan.getBoundingClientRect()) === null || _b === void 0 ? void 0 : _b.width) || 0;
|
|
8
18
|
while (textLength > maxWidth && text.length > 1) {
|
|
9
19
|
text = text.slice(0, -1);
|
|
10
|
-
tSpan.text
|
|
11
|
-
textLength = ((
|
|
20
|
+
tSpan.textContent = text + '…';
|
|
21
|
+
textLength = ((_c = tSpan.getBoundingClientRect()) === null || _c === void 0 ? void 0 : _c.width) || 0;
|
|
12
22
|
}
|
|
23
|
+
textNode === null || textNode === void 0 ? void 0 : textNode.transform.baseVal.removeItem((textNode === null || textNode === void 0 ? void 0 : textNode.transform.baseVal.length) - 1);
|
|
24
|
+
}
|
|
25
|
+
export function setEllipsisForOverflowText(selection, maxWidth) {
|
|
26
|
+
const text = selection.text();
|
|
27
|
+
selection.text(null).append('title').text(text);
|
|
28
|
+
const tSpan = selection.append('tspan').text(text).style('alignment-baseline', 'inherit');
|
|
29
|
+
handleOverflowingText(tSpan.node(), maxWidth);
|
|
13
30
|
}
|
|
14
31
|
export function setEllipsisForOverflowTexts(selection, maxWidth) {
|
|
15
32
|
selection.each(function (datum) {
|
|
@@ -48,6 +65,9 @@ function renderLabels(selection, { labels, style = {}, attrs = {}, }) {
|
|
|
48
65
|
}
|
|
49
66
|
export function getLabelsSize({ labels, style, rotation, }) {
|
|
50
67
|
var _a;
|
|
68
|
+
if (!labels.filter(Boolean).length) {
|
|
69
|
+
return { maxHeight: 0, maxWidth: 0 };
|
|
70
|
+
}
|
|
51
71
|
const container = select(document.body)
|
|
52
72
|
.append('div')
|
|
53
73
|
.attr('class', 'chartkit chartkit-theme_common');
|
|
@@ -62,3 +82,36 @@ export function getLabelsSize({ labels, style, rotation, }) {
|
|
|
62
82
|
container.remove();
|
|
63
83
|
return { maxHeight: height, maxWidth: width };
|
|
64
84
|
}
|
|
85
|
+
export function wrapText(args) {
|
|
86
|
+
const { text, style, width } = args;
|
|
87
|
+
const height = getLabelsSize({
|
|
88
|
+
labels: [text],
|
|
89
|
+
style: style,
|
|
90
|
+
}).maxHeight;
|
|
91
|
+
// @ts-ignore
|
|
92
|
+
const segmenter = new Intl.Segmenter([], { granularity: 'word' });
|
|
93
|
+
const segments = Array.from(segmenter.segment(text));
|
|
94
|
+
return segments.reduce((acc, s) => {
|
|
95
|
+
const item = s;
|
|
96
|
+
if (!acc.length) {
|
|
97
|
+
acc.push({
|
|
98
|
+
text: '',
|
|
99
|
+
y: acc.length * height,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
let lastRow = acc[acc.length - 1];
|
|
103
|
+
if (item.isWordLike &&
|
|
104
|
+
getLabelsSize({
|
|
105
|
+
labels: [lastRow.text + item.segment],
|
|
106
|
+
style,
|
|
107
|
+
}).maxWidth > width) {
|
|
108
|
+
lastRow = {
|
|
109
|
+
text: '',
|
|
110
|
+
y: acc.length * height,
|
|
111
|
+
};
|
|
112
|
+
acc.push(lastRow);
|
|
113
|
+
}
|
|
114
|
+
lastRow.text += item.segment;
|
|
115
|
+
return acc;
|
|
116
|
+
}, []);
|
|
117
|
+
}
|
|
@@ -15,6 +15,8 @@ declare const HighchartsWidget: React.ForwardRefExoticComponent<{
|
|
|
15
15
|
hoistConfigError?: boolean | undefined;
|
|
16
16
|
nonBodyScroll?: boolean | undefined;
|
|
17
17
|
splitTooltip?: boolean | undefined;
|
|
18
|
+
paneSplitOrientation?: import("react-split-pane").Split | undefined;
|
|
19
|
+
onSplitPaneOrientationChange?: ((orientation: import("react-split-pane").Split) => void) | undefined;
|
|
18
20
|
onChange?: ((data: {
|
|
19
21
|
type: "PARAMS_CHANGED";
|
|
20
22
|
data: {
|
|
@@ -124,7 +124,7 @@ export class HighchartsComponent extends React.PureComponent {
|
|
|
124
124
|
return null;
|
|
125
125
|
}
|
|
126
126
|
markChartPerformance(this.getId(true));
|
|
127
|
-
return (React.createElement(Component, { key: Math.random(), options: options, highcharts: Highcharts, onSplitPaneMountCallback: this.state.callback || undefined, callback: this.extendChartInstance, constructorType: (options === null || options === void 0 ? void 0 : options.useHighStock) ? 'stockChart' : 'chart', containerProps: { className: 'chartkit-graph' }, ref: this.chartComponent }));
|
|
127
|
+
return (React.createElement(Component, { key: Math.random(), options: options, highcharts: Highcharts, onSplitPaneMountCallback: this.state.callback || undefined, onSplitPaneOrientationChange: this.props.onSplitPaneOrientationChange, paneSplitOrientation: this.props.paneSplitOrientation, callback: this.extendChartInstance, constructorType: (options === null || options === void 0 ? void 0 : options.useHighStock) ? 'stockChart' : 'chart', containerProps: { className: 'chartkit-graph' }, ref: this.chartComponent }));
|
|
128
128
|
}
|
|
129
129
|
getId(refresh = false) {
|
|
130
130
|
if (refresh) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { SplitPaneProps } from 'react-split-pane';
|
|
3
3
|
import './StyledSplitPane.css';
|
|
4
|
-
export type PaneSplit = Split;
|
|
5
4
|
type Props = SplitPaneProps & {
|
|
6
5
|
paneOneRender: () => React.ReactNode;
|
|
7
6
|
paneTwoRender: () => React.ReactNode;
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { Highcharts } from '../../../types';
|
|
3
3
|
import './WithSplitPane.css';
|
|
4
|
+
declare enum PaneSplits {
|
|
5
|
+
VERTICAL = "vertical",
|
|
6
|
+
HORIZONTAL = "horizontal"
|
|
7
|
+
}
|
|
4
8
|
export declare const withSplitPane: <ComposedComponentProps extends {}>(ComposedComponent: React.ComponentType<ComposedComponentProps>) => React.ForwardRefExoticComponent<React.PropsWithoutRef<ComposedComponentProps & {
|
|
5
9
|
onPaneChange?: (() => void) | undefined;
|
|
6
10
|
onSplitPaneMountCallback?: ((chart: Highcharts.Chart) => void) | undefined;
|
|
11
|
+
paneSplitOrientation?: PaneSplits | undefined;
|
|
12
|
+
onSplitPaneOrientationChange?: ((orientation?: PaneSplits) => void) | undefined;
|
|
7
13
|
} & {
|
|
8
14
|
current: any;
|
|
9
15
|
forwardedRef: React.Ref<ComposedComponentProps>;
|
|
10
16
|
callback?: Highcharts.ChartCallbackFunction | undefined;
|
|
11
17
|
}> & React.RefAttributes<ComposedComponentProps>>;
|
|
18
|
+
export {};
|
|
@@ -79,9 +79,10 @@ export const withSplitPane = (ComposedComponent) => {
|
|
|
79
79
|
this.state = {
|
|
80
80
|
paneSize: undefined,
|
|
81
81
|
maxPaneSize: undefined,
|
|
82
|
-
paneSplit:
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
paneSplit: this.props.paneSplitOrientation ||
|
|
83
|
+
(window.innerWidth > window.innerHeight
|
|
84
|
+
? PaneSplits.VERTICAL
|
|
85
|
+
: PaneSplits.HORIZONTAL),
|
|
85
86
|
componentKey: getRandomCKId(),
|
|
86
87
|
};
|
|
87
88
|
this.tooltipContainerRef = React.createRef();
|
|
@@ -136,15 +137,16 @@ export const withSplitPane = (ComposedComponent) => {
|
|
|
136
137
|
};
|
|
137
138
|
this.handleOrientationChange = () => {
|
|
138
139
|
const handleResizeAfterOrientationChange = () => {
|
|
140
|
+
var _a, _b;
|
|
139
141
|
const deviceWidth = window.innerWidth;
|
|
140
142
|
const deviceHeight = window.innerHeight;
|
|
143
|
+
const aspectRatioOrientation = deviceWidth > deviceHeight ? PaneSplits.VERTICAL : PaneSplits.HORIZONTAL;
|
|
141
144
|
this.setState({
|
|
142
|
-
paneSplit:
|
|
143
|
-
? PaneSplits.VERTICAL
|
|
144
|
-
: PaneSplits.HORIZONTAL,
|
|
145
|
+
paneSplit: this.props.paneSplitOrientation || aspectRatioOrientation,
|
|
145
146
|
}, () => {
|
|
146
147
|
this.setInitialState(true);
|
|
147
148
|
});
|
|
149
|
+
(_b = (_a = this.props).onSplitPaneOrientationChange) === null || _b === void 0 ? void 0 : _b.call(_a, aspectRatioOrientation);
|
|
148
150
|
window.removeEventListener('resize', handleResizeAfterOrientationChange);
|
|
149
151
|
};
|
|
150
152
|
window.addEventListener('resize', handleResizeAfterOrientationChange);
|
|
@@ -379,9 +379,19 @@ function validateCellManipulationConfig(tooltipOptions, property, item) {
|
|
|
379
379
|
item[property] = tooltipOptions[property];
|
|
380
380
|
}
|
|
381
381
|
}
|
|
382
|
+
function getSeriesTypeFromTooltipContext() {
|
|
383
|
+
var _a, _b;
|
|
384
|
+
if (this.series) {
|
|
385
|
+
return this.series.type;
|
|
386
|
+
}
|
|
387
|
+
if (Array.isArray(this.points)) {
|
|
388
|
+
return (_b = (_a = this.points[0]) === null || _a === void 0 ? void 0 : _a.series) === null || _b === void 0 ? void 0 : _b.type;
|
|
389
|
+
}
|
|
390
|
+
return '';
|
|
391
|
+
}
|
|
382
392
|
function getTooltip(tooltip, options, comments, holidays) {
|
|
383
393
|
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
384
|
-
const serieType = (this
|
|
394
|
+
const serieType = getSeriesTypeFromTooltipContext.call(this) || tooltip.chart.options.chart.type;
|
|
385
395
|
const chart = tooltip.chart;
|
|
386
396
|
const xAxis = chart.xAxis[0];
|
|
387
397
|
const isDatetimeXAxis = xAxis.options.type === 'datetime';
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { FormatNumberOptions } from '../../plugins/shared';
|
|
2
2
|
import type { BaseTextStyle } from './base';
|
|
3
|
-
export type ChartKitWidgetAxisType = 'category' | 'datetime' | 'linear';
|
|
3
|
+
export type ChartKitWidgetAxisType = 'category' | 'datetime' | 'linear' | 'logarithmic';
|
|
4
|
+
export type ChartKitWidgetAxisTitleAlignment = 'left' | 'center' | 'right';
|
|
4
5
|
export type ChartKitWidgetAxisLabels = {
|
|
5
6
|
/** Enable or disable the axis labels. */
|
|
6
7
|
enabled?: boolean;
|
|
@@ -37,11 +38,18 @@ export type ChartKitWidgetAxis = {
|
|
|
37
38
|
lineColor?: string;
|
|
38
39
|
title?: {
|
|
39
40
|
text?: string;
|
|
41
|
+
/** CSS styles for the title */
|
|
42
|
+
style?: Partial<BaseTextStyle>;
|
|
40
43
|
/** The pixel distance between the axis labels or line and the title.
|
|
41
44
|
*
|
|
42
45
|
* Defaults to 4 for horizontal axes, 8 for vertical.
|
|
43
46
|
* */
|
|
44
47
|
margin?: number;
|
|
48
|
+
/** Alignment of the title. */
|
|
49
|
+
align?: ChartKitWidgetAxisTitleAlignment;
|
|
50
|
+
/** Allows limiting of the contents of a title block to the specified number of lines.
|
|
51
|
+
* Defaults to 1. */
|
|
52
|
+
maxRowCount?: number;
|
|
45
53
|
};
|
|
46
54
|
/** The minimum value of the axis. If undefined the min value is automatically calculate. */
|
|
47
55
|
min?: number;
|
package/build/types/widget.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
|
+
import type { Split } from 'react-split-pane';
|
|
2
3
|
import type { Highcharts, HighchartsWidgetData, StringParams } from '../plugins/highcharts/types';
|
|
3
4
|
import type { IndicatorWidgetData } from '../plugins/indicator/types';
|
|
4
5
|
import type { CustomTooltipProps, Yagr, YagrWidgetData } from '../plugins/yagr/types';
|
|
@@ -20,6 +21,8 @@ export interface ChartKitWidget {
|
|
|
20
21
|
hoistConfigError?: boolean;
|
|
21
22
|
nonBodyScroll?: boolean;
|
|
22
23
|
splitTooltip?: boolean;
|
|
24
|
+
paneSplitOrientation?: Split;
|
|
25
|
+
onSplitPaneOrientationChange?: (orientation: Split) => void;
|
|
23
26
|
onChange?: (data: {
|
|
24
27
|
type: 'PARAMS_CHANGED';
|
|
25
28
|
data: {
|
package/package.json
CHANGED