@gravity-ui/chartkit 4.4.1 → 4.5.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.
@@ -5,6 +5,7 @@ type Props = {
5
5
  width: number;
6
6
  height: number;
7
7
  scale: ChartScale;
8
+ chartWidth: number;
8
9
  };
9
- export declare const AxisX: ({ axis, width, height, scale }: Props) => React.JSX.Element;
10
+ export declare const AxisX: ({ axis, width, height, scale, chartWidth }: Props) => React.JSX.Element;
10
11
  export {};
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
2
  import { axisBottom, select } from 'd3';
3
3
  import { block } from '../../../../utils/cn';
4
- import { formatAxisTickLabel, parseTransformStyle } from '../utils';
4
+ import { formatAxisTickLabel, parseTransformStyle, setEllipsisForOverflowText } from '../utils';
5
5
  const b = block('d3-axis');
6
6
  const EMPTY_SPACE_BETWEEN_LABELS = 10;
7
7
  // FIXME: add overflow ellipsis for the labels that out of boundaries
8
- export const AxisX = ({ axis, width, height, scale }) => {
8
+ export const AxisX = ({ axis, width, height, scale, chartWidth }) => {
9
9
  const ref = React.useRef(null);
10
10
  React.useEffect(() => {
11
11
  if (!ref.current) {
@@ -46,17 +46,7 @@ export const AxisX = ({ axis, width, height, scale }) => {
46
46
  // Remove tick that has the same x coordinate like domain
47
47
  svgElement.select('.tick').remove();
48
48
  }
49
- if (axis.title.text) {
50
- const textY = axis.title.height + parseInt(axis.labels.style.fontSize) + axis.labels.padding;
51
- svgElement
52
- .append('text')
53
- .attr('class', b('title'))
54
- .attr('text-anchor', 'middle')
55
- .attr('x', width / 2)
56
- .attr('y', textY)
57
- .attr('font-size', axis.title.style.fontSize)
58
- .text(axis.title.text);
59
- }
49
+ // remove overlapping labels
60
50
  let elementX = 0;
61
51
  svgElement
62
52
  .selectAll('.tick')
@@ -70,6 +60,28 @@ export const AxisX = ({ axis, width, height, scale }) => {
70
60
  return false;
71
61
  })
72
62
  .remove();
63
+ // add an ellipsis to the labels on the right that go beyond the boundaries of the chart
64
+ svgElement.selectAll('.tick text').each(function () {
65
+ const node = this;
66
+ const textRect = node.getBoundingClientRect();
67
+ if (textRect.right > chartWidth) {
68
+ const maxWidth = textRect.width - (textRect.right - chartWidth) * 2;
69
+ select(node).call(setEllipsisForOverflowText, maxWidth);
70
+ }
71
+ });
72
+ // add an axis header if necessary
73
+ if (axis.title.text) {
74
+ const textY = axis.title.height + parseInt(axis.labels.style.fontSize) + axis.labels.padding;
75
+ svgElement
76
+ .append('text')
77
+ .attr('class', b('title'))
78
+ .attr('text-anchor', 'middle')
79
+ .attr('x', width / 2)
80
+ .attr('y', textY)
81
+ .attr('font-size', axis.title.style.fontSize)
82
+ .text(axis.title.text)
83
+ .call(setEllipsisForOverflowText, width);
84
+ }
73
85
  }, [axis, width, height, scale]);
74
86
  return React.createElement("g", { ref: ref });
75
87
  };
@@ -53,7 +53,7 @@ export const Chart = (props) => {
53
53
  xScale && yScale && (React.createElement(React.Fragment, null,
54
54
  React.createElement(AxisY, { axises: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale }),
55
55
  React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
56
- React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale })))),
56
+ React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, chartWidth: width })))),
57
57
  shapes),
58
58
  legend.enabled && (React.createElement(Legend, { width: boundsWidth, offsetWidth: chart.margin.left, height: legend.height, legend: legend, offsetHeight: height - legend.height / 2, chartSeries: preparedSeries, onItemClick: handleLegendItemClick }))),
59
59
  React.createElement(Tooltip, { hovered: hovered, pointerPosition: pointerPosition, tooltip: tooltip, xAxis: xAxis, yAxis: yAxis[0] })));
@@ -33,14 +33,15 @@ const createScales = (args) => {
33
33
  visibleSeries = visibleSeries.length === 0 ? series : visibleSeries;
34
34
  let xScale;
35
35
  let yScale;
36
+ const xAxisMinPadding = boundsWidth * xAxis.maxPadding;
37
+ const xRange = [0, boundsWidth - xAxisMinPadding];
36
38
  switch (xType) {
37
39
  case 'linear': {
38
40
  const domain = getDomainDataXBySeries(visibleSeries);
39
- const range = [0, boundsWidth - boundsWidth * xAxis.maxPadding];
40
41
  if (isNumericalArrayData(domain)) {
41
42
  const [domainXMin, xMax] = extent(domain);
42
43
  const xMinValue = typeof xMin === 'number' ? xMin : domainXMin;
43
- xScale = scaleLinear().domain([xMinValue, xMax]).range(range).nice();
44
+ xScale = scaleLinear().domain([xMinValue, xMax]).range(xRange).nice();
44
45
  }
45
46
  break;
46
47
  }
@@ -52,20 +53,22 @@ const createScales = (args) => {
52
53
  series: visibleSeries,
53
54
  });
54
55
  xScale = scaleBand().domain(filteredCategories).range([0, boundsWidth]);
56
+ if (xScale.step() / 2 < xAxisMinPadding) {
57
+ xScale.range(xRange);
58
+ }
55
59
  }
56
60
  break;
57
61
  }
58
62
  case 'datetime': {
59
- const range = [0, boundsWidth - boundsWidth * xAxis.maxPadding];
60
63
  if (xTimestamps) {
61
64
  const [xMin, xMax] = extent(xTimestamps);
62
- xScale = scaleUtc().domain([xMin, xMax]).range(range).nice();
65
+ xScale = scaleUtc().domain([xMin, xMax]).range(xRange).nice();
63
66
  }
64
67
  else {
65
68
  const domain = getDomainDataXBySeries(visibleSeries);
66
69
  if (isNumericalArrayData(domain)) {
67
70
  const [xMin, xMax] = extent(domain);
68
- xScale = scaleUtc().domain([xMin, xMax]).range(range).nice();
71
+ xScale = scaleUtc().domain([xMin, xMax]).range(xRange).nice();
69
72
  }
70
73
  }
71
74
  break;
@@ -80,12 +80,8 @@ const getMarginLeft = (args) => {
80
80
  return marginLeft;
81
81
  };
82
82
  const getMarginRight = (args) => {
83
- const { chart, hasAxisRelatedSeries, series, preparedXAxis } = args;
84
- let marginRight = get(chart, 'margin.right', 0);
85
- if (hasAxisRelatedSeries) {
86
- marginRight += getAxisLabelMaxWidth({ axis: preparedXAxis, series: series.data }) / 2;
87
- }
88
- return marginRight;
83
+ const { chart } = args;
84
+ return get(chart, 'margin.right', 0);
89
85
  };
90
86
  export const getPreparedChart = (args) => {
91
87
  const { chart, series, preparedLegend, preparedXAxis, preparedY1Axis, preparedTitle } = args;
@@ -98,7 +94,7 @@ export const getPreparedChart = (args) => {
98
94
  preparedXAxis,
99
95
  });
100
96
  const marginLeft = getMarginLeft({ chart, hasAxisRelatedSeries, series, preparedY1Axis });
101
- const marginRight = getMarginRight({ chart, hasAxisRelatedSeries, series, preparedXAxis });
97
+ const marginRight = getMarginRight({ chart });
102
98
  return {
103
99
  margin: {
104
100
  top: marginTop,
@@ -1,6 +1,7 @@
1
1
  import { AxisDomain } from 'd3';
2
2
  import type { BaseTextStyle, ChartKitWidgetSeries, ChartKitWidgetSeriesData, ChartKitWidgetAxisType, ChartKitWidgetAxisLabels } from '../../../../types/widget-data';
3
3
  export * from './math';
4
+ export * from './text';
4
5
  export type AxisDirection = 'x' | 'y';
5
6
  type UnknownSeries = {
6
7
  type: ChartKitWidgetSeries['type'];
@@ -5,6 +5,7 @@ import { dateTime } from '@gravity-ui/date-utils';
5
5
  import { formatNumber } from '../../../shared';
6
6
  import { DEFAULT_AXIS_LABEL_FONT_SIZE } from '../constants';
7
7
  export * from './math';
8
+ export * from './text';
8
9
  const CHARTS_WITHOUT_AXIS = ['pie'];
9
10
  /**
10
11
  * Checks whether the series should be drawn with axes.
@@ -0,0 +1,2 @@
1
+ import { Selection } from 'd3-selection';
2
+ export declare function setEllipsisForOverflowText(selection: Selection<SVGTextElement, any, null, undefined>, maxWidth: number): void;
@@ -0,0 +1,12 @@
1
+ export function setEllipsisForOverflowText(selection, maxWidth) {
2
+ var _a, _b;
3
+ let text = selection.text();
4
+ selection.text(null).attr('text-anchor', 'left').append('title').text(text);
5
+ const tSpan = selection.append('tspan').text(text);
6
+ let textLength = ((_a = tSpan.node()) === null || _a === void 0 ? void 0 : _a.getComputedTextLength()) || 0;
7
+ while (textLength > maxWidth && text.length > 1) {
8
+ text = text.slice(0, -1);
9
+ tSpan.text(text + '…');
10
+ textLength = ((_b = tSpan.node()) === null || _b === void 0 ? void 0 : _b.getComputedTextLength()) || 0;
11
+ }
12
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/chartkit",
3
- "version": "4.4.1",
3
+ "version": "4.5.0",
4
4
  "description": "React component used to render charts based on any sources you need",
5
5
  "license": "MIT",
6
6
  "repository": "git@github.com:gravity-ui/ChartKit.git",
@@ -48,7 +48,7 @@
48
48
  "dependencies": {
49
49
  "@bem-react/classname": "^1.6.0",
50
50
  "@gravity-ui/date-utils": "^1.4.1",
51
- "@gravity-ui/yagr": "^3.7.13",
51
+ "@gravity-ui/yagr": "^3.8.0",
52
52
  "d3": "^7.8.5",
53
53
  "lodash": "^4.17.21",
54
54
  "react-split-pane": "^0.1.92"