@gravity-ui/charts 1.2.0 → 1.3.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.
@@ -114,6 +114,7 @@ function renderLegendSymbol(args) {
114
114
  }
115
115
  case 'symbol': {
116
116
  const y = legend.lineHeight / 2;
117
+ const translateX = x + d.symbol.width / 2;
117
118
  element
118
119
  .append('svg:path')
119
120
  .attr('d', () => {
@@ -123,7 +124,7 @@ function renderLegendSymbol(args) {
123
124
  return symbol(scatterSymbol, d.symbol.width * d.symbol.width)();
124
125
  })
125
126
  .attr('transform', () => {
126
- return 'translate(' + x + ',' + y + ')';
127
+ return 'translate(' + translateX + ',' + y + ')';
127
128
  })
128
129
  .attr('class', className)
129
130
  .style('fill', color);
@@ -190,16 +191,27 @@ export const Legend = (props) => {
190
191
  })
191
192
  .style('font-size', legend.itemStyle.fontSize);
192
193
  const contentWidth = ((_a = legendLine.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
193
- const { left } = getLegendPosition({
194
- align: legend.align,
195
- width: boundsWidth,
196
- offsetWidth: 0,
197
- contentWidth,
198
- });
194
+ let left = 0;
195
+ switch (legend.justifyContent) {
196
+ case 'center': {
197
+ const legendLinePostion = getLegendPosition({
198
+ align: legend.align,
199
+ width: boundsWidth,
200
+ offsetWidth: 0,
201
+ contentWidth,
202
+ });
203
+ left = legendLinePostion.left;
204
+ legendWidth = boundsWidth;
205
+ break;
206
+ }
207
+ case 'start': {
208
+ legendWidth = Math.max(legendWidth, contentWidth);
209
+ break;
210
+ }
211
+ }
199
212
  const top = legend.lineHeight * lineIndex;
200
213
  legendLine.attr('transform', `translate(${[left, top].join(',')})`);
201
214
  });
202
- legendWidth = boundsWidth;
203
215
  if (config.pagination) {
204
216
  const transform = `translate(${[
205
217
  0,
@@ -1,6 +1,7 @@
1
1
  import type { ChartLegend } from '../../types';
2
2
  export declare const legendDefaults: {
3
3
  align: Required<ChartLegend>["align"];
4
+ justifyContent: Required<ChartLegend>["justifyContent"];
4
5
  itemDistance: number;
5
6
  margin: number;
6
7
  itemStyle: {
@@ -1,5 +1,6 @@
1
1
  export const legendDefaults = {
2
2
  align: 'center',
3
+ justifyContent: 'center',
3
4
  itemDistance: 20,
4
5
  margin: 15,
5
6
  itemStyle: {
@@ -1,6 +1,6 @@
1
1
  import get from 'lodash/get';
2
2
  import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, DashStyle, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
3
- import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, wrapText, } from '../../utils';
3
+ import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, isAxisRelatedSeries, wrapText, } from '../../utils';
4
4
  import { createYScale } from '../useAxisScales';
5
5
  const getAxisLabelMaxWidth = (args) => {
6
6
  const { axis, series } = args;
@@ -45,6 +45,10 @@ function getAxisMin(axis, series) {
45
45
  export const getPreparedYAxis = ({ series, yAxis, height, }) => {
46
46
  const axisByPlot = [];
47
47
  const axisItems = yAxis || [{}];
48
+ const hasAxisRelatedSeries = series.some(isAxisRelatedSeries);
49
+ if (!hasAxisRelatedSeries) {
50
+ return [];
51
+ }
48
52
  return axisItems.map((axisItem) => {
49
53
  const plotIndex = get(axisItem, 'plotIndex', 0);
50
54
  const firstPlotAxis = !axisByPlot[plotIndex];
@@ -50,6 +50,7 @@ export const getPreparedLegend = (args) => {
50
50
  const legendWidth = get(legend, 'width', CONTINUOUS_LEGEND_SIZE.width);
51
51
  return {
52
52
  align: get(legend, 'align', legendDefaults.align),
53
+ justifyContent: get(legend, 'justifyContent', legendDefaults.justifyContent),
53
54
  enabled,
54
55
  height,
55
56
  itemDistance: get(legend, 'itemDistance', legendDefaults.itemDistance),
@@ -17,7 +17,10 @@ const getCenter = (boundsWidth, boundsHeight, center) => {
17
17
  };
18
18
  export function preparePieData(args) {
19
19
  const { series: preparedSeries, boundsWidth, boundsHeight } = args;
20
- const maxRadius = Math.min(boundsWidth, boundsHeight) / 2;
20
+ const haloSize = preparedSeries[0].states.hover.halo.enabled
21
+ ? preparedSeries[0].states.hover.halo.size
22
+ : 0;
23
+ const maxRadius = Math.min(boundsWidth, boundsHeight) / 2 - haloSize;
21
24
  const groupedPieSeries = group(preparedSeries, (pieSeries) => pieSeries.stackId);
22
25
  const prepareItem = (stackId, items) => {
23
26
  var _a;
@@ -210,19 +213,20 @@ export function preparePieData(args) {
210
213
  series: items,
211
214
  });
212
215
  const segmentMaxRadius = Math.max(...data.segments.map((s) => s.data.radius));
213
- const top = Math.min(data.center[1] - segmentMaxRadius, ...preparedLabels.labels.map((l) => l.y + data.center[1]), ...preparedLabels.htmlLabels.map((l) => l.y));
216
+ const topAdjustment = Math.min(data.center[1] - segmentMaxRadius, ...preparedLabels.labels.map((l) => l.y + data.center[1]), ...preparedLabels.htmlLabels.map((l) => l.y));
214
217
  const bottom = Math.max(data.center[1] + segmentMaxRadius, ...preparedLabels.labels.map((l) => l.y + data.center[1] + l.size.height), ...preparedLabels.htmlLabels.map((l) => l.y + l.size.height));
215
- const topAdjustment = Math.floor(top - data.halo.size);
216
218
  if (topAdjustment > 0) {
217
219
  data.segments.forEach((s) => {
218
- s.data.radius += topAdjustment / 2;
220
+ const nextPossibleRadius = s.data.radius + topAdjustment / 2;
221
+ s.data.radius = Math.min(nextPossibleRadius, maxRadius);
219
222
  });
220
223
  data.center[1] -= topAdjustment / 2;
221
224
  }
222
- const bottomAdjustment = Math.floor(boundsHeight - bottom - data.halo.size);
225
+ const bottomAdjustment = Math.floor(boundsHeight - bottom);
223
226
  if (bottomAdjustment > 0) {
224
227
  data.segments.forEach((s) => {
225
- s.data.radius += bottomAdjustment / 2;
228
+ const nextPossibleRadius = s.data.radius + bottomAdjustment / 2;
229
+ s.data.radius = Math.min(nextPossibleRadius, maxRadius);
226
230
  });
227
231
  data.center[1] += bottomAdjustment / 2;
228
232
  }
@@ -10,23 +10,28 @@ function getLabels(args) {
10
10
  texts.forEach((text, index) => {
11
11
  var _a;
12
12
  const label = getFormattedValue(Object.assign({ value: text }, args.options));
13
- const { maxHeight: lineHeight, maxWidth: labelWidth } = (_a = getLabelsSize({ labels: [label], html })) !== null && _a !== void 0 ? _a : {};
13
+ const { maxHeight: lineHeight, maxWidth: labelMaxWidth } = (_a = getLabelsSize({ labels: [label], html })) !== null && _a !== void 0 ? _a : {};
14
14
  const left = d.x0 + padding;
15
15
  const right = d.x1 - padding;
16
- const width = Math.max(0, right - left);
16
+ const spaceWidth = Math.max(0, right - left);
17
+ const spaceHeight = Math.max(0, d.y1 - d.y0 - padding);
17
18
  let x = left;
18
19
  const y = index * lineHeight + d.y0 + padding;
20
+ const labelWidth = Math.min(labelMaxWidth, spaceWidth);
21
+ if (!labelWidth || lineHeight > spaceHeight) {
22
+ return;
23
+ }
19
24
  switch (align) {
20
25
  case 'left': {
21
26
  x = left;
22
27
  break;
23
28
  }
24
29
  case 'center': {
25
- x = Math.max(left, left + (width - labelWidth) / 2);
30
+ x = Math.max(left, left + (spaceWidth - labelMaxWidth) / 2);
26
31
  break;
27
32
  }
28
33
  case 'right': {
29
- x = Math.max(left, right - labelWidth);
34
+ x = Math.max(left, right - labelMaxWidth);
30
35
  break;
31
36
  }
32
37
  }
@@ -35,13 +40,13 @@ function getLabels(args) {
35
40
  content: label,
36
41
  x,
37
42
  y,
38
- size: { width, height: lineHeight },
43
+ size: { width: labelWidth, height: lineHeight },
39
44
  }
40
45
  : {
41
46
  text: label,
42
47
  x,
43
48
  y,
44
- width,
49
+ width: labelWidth,
45
50
  nodeData: d.data,
46
51
  };
47
52
  acc.push(item);
@@ -15,6 +15,12 @@ export interface ChartLegend {
15
15
  * @default center
16
16
  * */
17
17
  align?: 'left' | 'center' | 'right';
18
+ /**
19
+ * Defines how items should be positioned in the legend when overflowing (moving to the next line).
20
+ *
21
+ * @default center
22
+ * */
23
+ justifyContent?: 'start' | 'center';
18
24
  /**
19
25
  * Defines the pixel distance between each legend item
20
26
  *
@@ -114,6 +114,7 @@ function renderLegendSymbol(args) {
114
114
  }
115
115
  case 'symbol': {
116
116
  const y = legend.lineHeight / 2;
117
+ const translateX = x + d.symbol.width / 2;
117
118
  element
118
119
  .append('svg:path')
119
120
  .attr('d', () => {
@@ -123,7 +124,7 @@ function renderLegendSymbol(args) {
123
124
  return symbol(scatterSymbol, d.symbol.width * d.symbol.width)();
124
125
  })
125
126
  .attr('transform', () => {
126
- return 'translate(' + x + ',' + y + ')';
127
+ return 'translate(' + translateX + ',' + y + ')';
127
128
  })
128
129
  .attr('class', className)
129
130
  .style('fill', color);
@@ -190,16 +191,27 @@ export const Legend = (props) => {
190
191
  })
191
192
  .style('font-size', legend.itemStyle.fontSize);
192
193
  const contentWidth = ((_a = legendLine.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
193
- const { left } = getLegendPosition({
194
- align: legend.align,
195
- width: boundsWidth,
196
- offsetWidth: 0,
197
- contentWidth,
198
- });
194
+ let left = 0;
195
+ switch (legend.justifyContent) {
196
+ case 'center': {
197
+ const legendLinePostion = getLegendPosition({
198
+ align: legend.align,
199
+ width: boundsWidth,
200
+ offsetWidth: 0,
201
+ contentWidth,
202
+ });
203
+ left = legendLinePostion.left;
204
+ legendWidth = boundsWidth;
205
+ break;
206
+ }
207
+ case 'start': {
208
+ legendWidth = Math.max(legendWidth, contentWidth);
209
+ break;
210
+ }
211
+ }
199
212
  const top = legend.lineHeight * lineIndex;
200
213
  legendLine.attr('transform', `translate(${[left, top].join(',')})`);
201
214
  });
202
- legendWidth = boundsWidth;
203
215
  if (config.pagination) {
204
216
  const transform = `translate(${[
205
217
  0,
@@ -1,6 +1,7 @@
1
1
  import type { ChartLegend } from '../../types';
2
2
  export declare const legendDefaults: {
3
3
  align: Required<ChartLegend>["align"];
4
+ justifyContent: Required<ChartLegend>["justifyContent"];
4
5
  itemDistance: number;
5
6
  margin: number;
6
7
  itemStyle: {
@@ -1,5 +1,6 @@
1
1
  export const legendDefaults = {
2
2
  align: 'center',
3
+ justifyContent: 'center',
3
4
  itemDistance: 20,
4
5
  margin: 15,
5
6
  itemStyle: {
@@ -1,6 +1,6 @@
1
1
  import get from 'lodash/get';
2
2
  import { DEFAULT_AXIS_LABEL_FONT_SIZE, DEFAULT_AXIS_TYPE, DashStyle, axisLabelsDefaults, yAxisTitleDefaults, } from '../../constants';
3
- import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, wrapText, } from '../../utils';
3
+ import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, formatAxisTickLabel, getClosestPointsRange, getHorisontalSvgTextHeight, getLabelsSize, getScaleTicks, getWaterfallPointSubtotal, isAxisRelatedSeries, wrapText, } from '../../utils';
4
4
  import { createYScale } from '../useAxisScales';
5
5
  const getAxisLabelMaxWidth = (args) => {
6
6
  const { axis, series } = args;
@@ -45,6 +45,10 @@ function getAxisMin(axis, series) {
45
45
  export const getPreparedYAxis = ({ series, yAxis, height, }) => {
46
46
  const axisByPlot = [];
47
47
  const axisItems = yAxis || [{}];
48
+ const hasAxisRelatedSeries = series.some(isAxisRelatedSeries);
49
+ if (!hasAxisRelatedSeries) {
50
+ return [];
51
+ }
48
52
  return axisItems.map((axisItem) => {
49
53
  const plotIndex = get(axisItem, 'plotIndex', 0);
50
54
  const firstPlotAxis = !axisByPlot[plotIndex];
@@ -50,6 +50,7 @@ export const getPreparedLegend = (args) => {
50
50
  const legendWidth = get(legend, 'width', CONTINUOUS_LEGEND_SIZE.width);
51
51
  return {
52
52
  align: get(legend, 'align', legendDefaults.align),
53
+ justifyContent: get(legend, 'justifyContent', legendDefaults.justifyContent),
53
54
  enabled,
54
55
  height,
55
56
  itemDistance: get(legend, 'itemDistance', legendDefaults.itemDistance),
@@ -17,7 +17,10 @@ const getCenter = (boundsWidth, boundsHeight, center) => {
17
17
  };
18
18
  export function preparePieData(args) {
19
19
  const { series: preparedSeries, boundsWidth, boundsHeight } = args;
20
- const maxRadius = Math.min(boundsWidth, boundsHeight) / 2;
20
+ const haloSize = preparedSeries[0].states.hover.halo.enabled
21
+ ? preparedSeries[0].states.hover.halo.size
22
+ : 0;
23
+ const maxRadius = Math.min(boundsWidth, boundsHeight) / 2 - haloSize;
21
24
  const groupedPieSeries = group(preparedSeries, (pieSeries) => pieSeries.stackId);
22
25
  const prepareItem = (stackId, items) => {
23
26
  var _a;
@@ -210,19 +213,20 @@ export function preparePieData(args) {
210
213
  series: items,
211
214
  });
212
215
  const segmentMaxRadius = Math.max(...data.segments.map((s) => s.data.radius));
213
- const top = Math.min(data.center[1] - segmentMaxRadius, ...preparedLabels.labels.map((l) => l.y + data.center[1]), ...preparedLabels.htmlLabels.map((l) => l.y));
216
+ const topAdjustment = Math.min(data.center[1] - segmentMaxRadius, ...preparedLabels.labels.map((l) => l.y + data.center[1]), ...preparedLabels.htmlLabels.map((l) => l.y));
214
217
  const bottom = Math.max(data.center[1] + segmentMaxRadius, ...preparedLabels.labels.map((l) => l.y + data.center[1] + l.size.height), ...preparedLabels.htmlLabels.map((l) => l.y + l.size.height));
215
- const topAdjustment = Math.floor(top - data.halo.size);
216
218
  if (topAdjustment > 0) {
217
219
  data.segments.forEach((s) => {
218
- s.data.radius += topAdjustment / 2;
220
+ const nextPossibleRadius = s.data.radius + topAdjustment / 2;
221
+ s.data.radius = Math.min(nextPossibleRadius, maxRadius);
219
222
  });
220
223
  data.center[1] -= topAdjustment / 2;
221
224
  }
222
- const bottomAdjustment = Math.floor(boundsHeight - bottom - data.halo.size);
225
+ const bottomAdjustment = Math.floor(boundsHeight - bottom);
223
226
  if (bottomAdjustment > 0) {
224
227
  data.segments.forEach((s) => {
225
- s.data.radius += bottomAdjustment / 2;
228
+ const nextPossibleRadius = s.data.radius + bottomAdjustment / 2;
229
+ s.data.radius = Math.min(nextPossibleRadius, maxRadius);
226
230
  });
227
231
  data.center[1] += bottomAdjustment / 2;
228
232
  }
@@ -10,23 +10,28 @@ function getLabels(args) {
10
10
  texts.forEach((text, index) => {
11
11
  var _a;
12
12
  const label = getFormattedValue(Object.assign({ value: text }, args.options));
13
- const { maxHeight: lineHeight, maxWidth: labelWidth } = (_a = getLabelsSize({ labels: [label], html })) !== null && _a !== void 0 ? _a : {};
13
+ const { maxHeight: lineHeight, maxWidth: labelMaxWidth } = (_a = getLabelsSize({ labels: [label], html })) !== null && _a !== void 0 ? _a : {};
14
14
  const left = d.x0 + padding;
15
15
  const right = d.x1 - padding;
16
- const width = Math.max(0, right - left);
16
+ const spaceWidth = Math.max(0, right - left);
17
+ const spaceHeight = Math.max(0, d.y1 - d.y0 - padding);
17
18
  let x = left;
18
19
  const y = index * lineHeight + d.y0 + padding;
20
+ const labelWidth = Math.min(labelMaxWidth, spaceWidth);
21
+ if (!labelWidth || lineHeight > spaceHeight) {
22
+ return;
23
+ }
19
24
  switch (align) {
20
25
  case 'left': {
21
26
  x = left;
22
27
  break;
23
28
  }
24
29
  case 'center': {
25
- x = Math.max(left, left + (width - labelWidth) / 2);
30
+ x = Math.max(left, left + (spaceWidth - labelMaxWidth) / 2);
26
31
  break;
27
32
  }
28
33
  case 'right': {
29
- x = Math.max(left, right - labelWidth);
34
+ x = Math.max(left, right - labelMaxWidth);
30
35
  break;
31
36
  }
32
37
  }
@@ -35,13 +40,13 @@ function getLabels(args) {
35
40
  content: label,
36
41
  x,
37
42
  y,
38
- size: { width, height: lineHeight },
43
+ size: { width: labelWidth, height: lineHeight },
39
44
  }
40
45
  : {
41
46
  text: label,
42
47
  x,
43
48
  y,
44
- width,
49
+ width: labelWidth,
45
50
  nodeData: d.data,
46
51
  };
47
52
  acc.push(item);
@@ -15,6 +15,12 @@ export interface ChartLegend {
15
15
  * @default center
16
16
  * */
17
17
  align?: 'left' | 'center' | 'right';
18
+ /**
19
+ * Defines how items should be positioned in the legend when overflowing (moving to the next line).
20
+ *
21
+ * @default center
22
+ * */
23
+ justifyContent?: 'start' | 'center';
18
24
  /**
19
25
  * Defines the pixel distance between each legend item
20
26
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/charts",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "React component used to render charts",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",