@gravity-ui/chartkit 4.5.0 → 4.6.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.
Files changed (63) hide show
  1. package/build/plugins/d3/renderer/D3Widget.js +11 -1
  2. package/build/plugins/d3/renderer/components/AxisX.d.ts +1 -2
  3. package/build/plugins/d3/renderer/components/AxisX.js +39 -61
  4. package/build/plugins/d3/renderer/components/AxisY.js +28 -31
  5. package/build/plugins/d3/renderer/components/Chart.js +19 -7
  6. package/build/plugins/d3/renderer/components/Legend.d.ts +5 -6
  7. package/build/plugins/d3/renderer/components/Legend.js +139 -84
  8. package/build/plugins/d3/renderer/components/styles.css +27 -0
  9. package/build/plugins/d3/renderer/constants/defaults/axis.d.ts +5 -0
  10. package/build/plugins/d3/renderer/constants/defaults/axis.js +5 -0
  11. package/build/plugins/d3/renderer/constants/defaults/index.d.ts +2 -0
  12. package/build/plugins/d3/renderer/constants/defaults/index.js +2 -0
  13. package/build/plugins/d3/renderer/constants/defaults/legend.d.ts +4 -0
  14. package/build/plugins/d3/renderer/constants/defaults/legend.js +8 -0
  15. package/build/plugins/d3/renderer/{constants.d.ts → constants/index.d.ts} +1 -1
  16. package/build/plugins/d3/renderer/{constants.js → constants/index.js} +1 -1
  17. package/build/plugins/d3/renderer/hooks/useAxisScales/index.d.ts +3 -1
  18. package/build/plugins/d3/renderer/hooks/useAxisScales/index.js +64 -62
  19. package/build/plugins/d3/renderer/hooks/useChartDimensions/index.d.ts +7 -4
  20. package/build/plugins/d3/renderer/hooks/useChartDimensions/index.js +65 -7
  21. package/build/plugins/d3/renderer/hooks/useChartDimensions/utils.d.ts +6 -0
  22. package/build/plugins/d3/renderer/hooks/useChartDimensions/utils.js +7 -0
  23. package/build/plugins/d3/renderer/hooks/useChartOptions/chart.d.ts +1 -3
  24. package/build/plugins/d3/renderer/hooks/useChartOptions/chart.js +9 -68
  25. package/build/plugins/d3/renderer/hooks/useChartOptions/index.d.ts +3 -1
  26. package/build/plugins/d3/renderer/hooks/useChartOptions/index.js +3 -8
  27. package/build/plugins/d3/renderer/hooks/useChartOptions/types.d.ts +3 -6
  28. package/build/plugins/d3/renderer/hooks/useChartOptions/x-axis.js +4 -2
  29. package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.d.ts +3 -2
  30. package/build/plugins/d3/renderer/hooks/useChartOptions/y-axis.js +31 -4
  31. package/build/plugins/d3/renderer/hooks/useSeries/constants.d.ts +1 -1
  32. package/build/plugins/d3/renderer/hooks/useSeries/constants.js +1 -1
  33. package/build/plugins/d3/renderer/hooks/useSeries/index.d.ts +19 -7
  34. package/build/plugins/d3/renderer/hooks/useSeries/index.js +26 -8
  35. package/build/plugins/d3/renderer/hooks/useSeries/prepare-legend.d.ts +27 -0
  36. package/build/plugins/d3/renderer/hooks/useSeries/prepare-legend.js +92 -0
  37. package/build/plugins/d3/renderer/hooks/useSeries/prepareSeries.d.ts +1 -2
  38. package/build/plugins/d3/renderer/hooks/useSeries/types.d.ts +26 -1
  39. package/build/plugins/d3/renderer/hooks/useShapes/bar-x.js +2 -1
  40. package/build/plugins/d3/renderer/utils/axis-generators/bottom.d.ts +21 -0
  41. package/build/plugins/d3/renderer/utils/axis-generators/bottom.js +104 -0
  42. package/build/plugins/d3/renderer/utils/axis-generators/index.d.ts +1 -0
  43. package/build/plugins/d3/renderer/utils/axis-generators/index.js +1 -0
  44. package/build/plugins/d3/renderer/utils/axis.d.ts +22 -0
  45. package/build/plugins/d3/renderer/utils/axis.js +43 -0
  46. package/build/plugins/d3/renderer/utils/index.d.ts +7 -4
  47. package/build/plugins/d3/renderer/utils/index.js +16 -6
  48. package/build/plugins/d3/renderer/utils/text.d.ts +20 -2
  49. package/build/plugins/d3/renderer/utils/text.js +51 -1
  50. package/build/plugins/d3/renderer/utils/time.d.ts +3 -0
  51. package/build/plugins/d3/renderer/utils/time.js +34 -0
  52. package/build/plugins/highcharts/renderer/components/HighchartsComponent.js +3 -3
  53. package/build/plugins/shared/format-number/format-number.d.ts +1 -0
  54. package/build/plugins/shared/format-number/format-number.js +19 -20
  55. package/build/types/widget-data/axis.d.ts +13 -1
  56. package/build/types/widget-data/legend.d.ts +24 -7
  57. package/build/utils/common.d.ts +1 -0
  58. package/build/utils/common.js +1 -1
  59. package/build/utils/index.d.ts +1 -1
  60. package/build/utils/index.js +1 -1
  61. package/package.json +1 -1
  62. package/build/plugins/d3/renderer/hooks/useChartOptions/legend.d.ts +0 -6
  63. package/build/plugins/d3/renderer/hooks/useChartOptions/legend.js +0 -12
@@ -4,9 +4,19 @@ import debounce from 'lodash/debounce';
4
4
  import { getRandomCKId } from '../../../utils';
5
5
  import { Chart } from './components';
6
6
  const D3Widget = React.forwardRef(function D3Widget(props, forwardedRef) {
7
+ const { data, onLoad, onRender } = props;
7
8
  const ref = React.useRef(null);
8
9
  const debounced = React.useRef();
9
10
  const [dimensions, setDimensions] = React.useState();
11
+ //FIXME: add chartPerfomance data to callbacks;
12
+ React.useLayoutEffect(() => {
13
+ if (onLoad) {
14
+ onLoad({});
15
+ }
16
+ if (onRender) {
17
+ onRender({});
18
+ }
19
+ }, []);
10
20
  const handleResize = React.useCallback(() => {
11
21
  var _a;
12
22
  const parentElement = (_a = ref.current) === null || _a === void 0 ? void 0 : _a.parentElement;
@@ -44,6 +54,6 @@ const D3Widget = React.forwardRef(function D3Widget(props, forwardedRef) {
44
54
  width: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) || '100%',
45
55
  height: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) || '100%',
46
56
  position: 'relative',
47
- } }, (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) && (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) && (React.createElement(Chart, { top: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.top) || 0, left: dimensions.left || 0, width: dimensions.width, height: dimensions.height, data: props.data }))));
57
+ } }, (dimensions === null || dimensions === void 0 ? void 0 : dimensions.width) && (dimensions === null || dimensions === void 0 ? void 0 : dimensions.height) && (React.createElement(Chart, { top: (dimensions === null || dimensions === void 0 ? void 0 : dimensions.top) || 0, left: dimensions.left || 0, width: dimensions.width, height: dimensions.height, data: data }))));
48
58
  });
49
59
  export default D3Widget;
@@ -5,7 +5,6 @@ type Props = {
5
5
  width: number;
6
6
  height: number;
7
7
  scale: ChartScale;
8
- chartWidth: number;
9
8
  };
10
- export declare const AxisX: ({ axis, width, height, scale, chartWidth }: Props) => React.JSX.Element;
9
+ export declare const AxisX: React.MemoExoticComponent<({ axis, width, height, scale }: Props) => React.JSX.Element>;
11
10
  export {};
@@ -1,74 +1,52 @@
1
1
  import React from 'react';
2
- import { axisBottom, select } from 'd3';
2
+ import { select } from 'd3';
3
3
  import { block } from '../../../../utils/cn';
4
- import { formatAxisTickLabel, parseTransformStyle, setEllipsisForOverflowText } from '../utils';
4
+ import { formatAxisTickLabel, getClosestPointsRange, setEllipsisForOverflowText, getTicksCount, getScaleTicks, getMaxTickCount, } from '../utils';
5
+ import { axisBottom } from '../utils/axis-generators';
5
6
  const b = block('d3-axis');
6
- const EMPTY_SPACE_BETWEEN_LABELS = 10;
7
- // FIXME: add overflow ellipsis for the labels that out of boundaries
8
- export const AxisX = ({ axis, width, height, scale, chartWidth }) => {
7
+ function getLabelFormatter({ axis, scale }) {
8
+ const ticks = getScaleTicks(scale);
9
+ const tickStep = getClosestPointsRange(axis, ticks);
10
+ return (value) => {
11
+ if (!axis.labels.enabled) {
12
+ return '';
13
+ }
14
+ return formatAxisTickLabel({
15
+ axis,
16
+ value,
17
+ step: tickStep,
18
+ });
19
+ };
20
+ }
21
+ export const AxisX = React.memo(({ axis, width, height, scale }) => {
9
22
  const ref = React.useRef(null);
10
23
  React.useEffect(() => {
11
24
  if (!ref.current) {
12
25
  return;
13
26
  }
27
+ const xAxisGenerator = axisBottom({
28
+ scale: scale,
29
+ ticks: {
30
+ size: axis.grid.enabled ? height * -1 : 0,
31
+ labelFormat: getLabelFormatter({ axis, scale }),
32
+ labelsPaddings: axis.labels.padding,
33
+ labelsMargin: axis.labels.margin,
34
+ labelsStyle: axis.labels.style,
35
+ count: getTicksCount({ axis, range: width }),
36
+ maxTickCount: getMaxTickCount({ axis, width }),
37
+ autoRotation: axis.labels.autoRotation,
38
+ },
39
+ domain: {
40
+ size: width,
41
+ color: axis.lineColor,
42
+ },
43
+ });
14
44
  const svgElement = select(ref.current);
15
45
  svgElement.selectAll('*').remove();
16
- const tickSize = axis.grid.enabled ? height * -1 : 0;
17
- let xAxisGenerator = axisBottom(scale)
18
- .tickSize(tickSize)
19
- .tickPadding(axis.labels.padding)
20
- .tickFormat((value) => {
21
- if (!axis.labels.enabled) {
22
- return '';
23
- }
24
- return formatAxisTickLabel({
25
- axisType: axis.type,
26
- value,
27
- dateFormat: axis.labels['dateFormat'],
28
- numberFormat: axis.labels['numberFormat'],
29
- });
30
- });
31
- if (axis.ticks.pixelInterval) {
32
- const ticksCount = width / axis.ticks.pixelInterval;
33
- xAxisGenerator = xAxisGenerator.ticks(ticksCount);
34
- }
35
- svgElement.call(xAxisGenerator).attr('class', b());
36
46
  svgElement
37
- .select('.domain')
38
- .attr('d', `M0,0V0H${width}`)
39
- .style('stroke', axis.lineColor || '');
40
- if (axis.labels.enabled) {
41
- svgElement.selectAll('.tick text').style('font-size', axis.labels.style.fontSize);
42
- }
43
- const transformStyle = svgElement.select('.tick').attr('transform');
44
- const { x } = parseTransformStyle(transformStyle);
45
- if (x === 0) {
46
- // Remove tick that has the same x coordinate like domain
47
- svgElement.select('.tick').remove();
48
- }
49
- // remove overlapping labels
50
- let elementX = 0;
51
- svgElement
52
- .selectAll('.tick')
53
- .filter(function () {
54
- const node = this;
55
- const r = node.getBoundingClientRect();
56
- if (r.left < elementX) {
57
- return true;
58
- }
59
- elementX = r.right + EMPTY_SPACE_BETWEEN_LABELS;
60
- return false;
61
- })
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
- });
47
+ .call(xAxisGenerator)
48
+ .attr('class', b())
49
+ .style('font-size', axis.labels.style.fontSize);
72
50
  // add an axis header if necessary
73
51
  if (axis.title.text) {
74
52
  const textY = axis.title.height + parseInt(axis.labels.style.fontSize) + axis.labels.padding;
@@ -84,4 +62,4 @@ export const AxisX = ({ axis, width, height, scale, chartWidth }) => {
84
62
  }
85
63
  }, [axis, width, height, scale]);
86
64
  return React.createElement("g", { ref: ref });
87
- };
65
+ });
@@ -1,28 +1,9 @@
1
1
  import React from 'react';
2
2
  import { axisLeft, select } from 'd3';
3
3
  import { block } from '../../../../utils/cn';
4
- import { formatAxisTickLabel, parseTransformStyle } from '../utils';
4
+ import { formatAxisTickLabel, getClosestPointsRange, parseTransformStyle, setEllipsisForOverflowText, setEllipsisForOverflowTexts, getTicksCount, getScaleTicks, } from '../utils';
5
5
  const b = block('d3-axis');
6
- const EMPTY_SPACE_BETWEEN_LABELS = 10;
7
- // Note: this method do not prepared for rotated labels
8
- const removeOverlappingYTicks = (axis) => {
9
- var _a;
10
- const a = axis.selectAll('g.tick').nodes();
11
- if (a.length <= 1) {
12
- return;
13
- }
14
- for (let i = 0, x = 0; i < a.length; i++) {
15
- const node = a[i];
16
- const r = node.getBoundingClientRect();
17
- if (r.bottom > x && i !== 0) {
18
- (_a = node === null || node === void 0 ? void 0 : node.parentNode) === null || _a === void 0 ? void 0 : _a.removeChild(node);
19
- }
20
- else {
21
- x = r.top - EMPTY_SPACE_BETWEEN_LABELS;
22
- }
23
- }
24
- };
25
- // FIXME: add overflow ellipsis for the labels that out of boundaries
6
+ const MAX_WIDTH = 80;
26
7
  export const AxisY = ({ axises, width, height, scale }) => {
27
8
  const ref = React.useRef(null);
28
9
  React.useEffect(() => {
@@ -33,22 +14,22 @@ export const AxisY = ({ axises, width, height, scale }) => {
33
14
  const svgElement = select(ref.current);
34
15
  svgElement.selectAll('*').remove();
35
16
  const tickSize = axis.grid.enabled ? width * -1 : 0;
17
+ const step = getClosestPointsRange(axis, getScaleTicks(scale));
36
18
  let yAxisGenerator = axisLeft(scale)
37
19
  .tickSize(tickSize)
38
- .tickPadding(axis.labels.padding)
20
+ .tickPadding(axis.labels.margin)
39
21
  .tickFormat((value) => {
40
22
  if (!axis.labels.enabled) {
41
23
  return '';
42
24
  }
43
25
  return formatAxisTickLabel({
44
- axisType: axis.type,
26
+ axis,
45
27
  value,
46
- dateFormat: axis.labels['dateFormat'],
47
- numberFormat: axis.labels['numberFormat'],
28
+ step,
48
29
  });
49
30
  });
50
- if (axis.ticks.pixelInterval) {
51
- const ticksCount = height / axis.ticks.pixelInterval;
31
+ const ticksCount = getTicksCount({ axis, range: height });
32
+ if (ticksCount) {
52
33
  yAxisGenerator = yAxisGenerator.ticks(ticksCount);
53
34
  }
54
35
  svgElement.call(yAxisGenerator).attr('class', b());
@@ -57,10 +38,11 @@ export const AxisY = ({ axises, width, height, scale }) => {
57
38
  .attr('d', `M0,${height}H0V0`)
58
39
  .style('stroke', axis.lineColor || '');
59
40
  if (axis.labels.enabled) {
60
- svgElement
41
+ const tickTexts = svgElement
61
42
  .selectAll('.tick text')
62
43
  .style('font-size', axis.labels.style.fontSize)
63
44
  .style('transform', 'translateY(-1px)');
45
+ tickTexts.call(setEllipsisForOverflowTexts, MAX_WIDTH);
64
46
  }
65
47
  const transformStyle = svgElement.select('.tick').attr('transform');
66
48
  const { y } = parseTransformStyle(transformStyle);
@@ -68,8 +50,23 @@ export const AxisY = ({ axises, width, height, scale }) => {
68
50
  // Remove stroke from tick that has the same y coordinate like domain
69
51
  svgElement.select('.tick line').style('stroke', 'none');
70
52
  }
53
+ // remove overlapping ticks
54
+ // Note: this method do not prepared for rotated labels
55
+ let elementY = 0;
56
+ svgElement
57
+ .selectAll('.tick')
58
+ .filter(function (_d, index) {
59
+ const node = this;
60
+ const r = node.getBoundingClientRect();
61
+ if (r.bottom > elementY && index !== 0) {
62
+ return true;
63
+ }
64
+ elementY = r.top - axis.labels.padding;
65
+ return false;
66
+ })
67
+ .remove();
71
68
  if (axis.title.text) {
72
- const textY = axis.title.height + axis.labels.padding;
69
+ const textY = axis.title.height + axis.labels.margin;
73
70
  svgElement
74
71
  .append('text')
75
72
  .attr('class', b('title'))
@@ -78,9 +75,9 @@ export const AxisY = ({ axises, width, height, scale }) => {
78
75
  .attr('dx', -height / 2)
79
76
  .attr('font-size', axis.title.style.fontSize)
80
77
  .attr('transform', 'rotate(-90)')
81
- .text(axis.title.text);
78
+ .text(axis.title.text)
79
+ .call(setEllipsisForOverflowText, height);
82
80
  }
83
- removeOverlappingYTicks(svgElement);
84
81
  }, [axises, width, height, scale]);
85
82
  return React.createElement("g", { ref: ref });
86
83
  };
@@ -1,26 +1,38 @@
1
1
  import React from 'react';
2
2
  import { block } from '../../../../utils/cn';
3
+ import { useAxisScales, useChartDimensions, useChartEvents, useChartOptions, useSeries, useShapes, useTooltip, } from '../hooks';
3
4
  import { AxisY } from './AxisY';
4
5
  import { AxisX } from './AxisX';
5
6
  import { Legend } from './Legend';
6
7
  import { Title } from './Title';
7
8
  import { Tooltip } from './Tooltip';
8
- import { useChartDimensions, useChartEvents, useChartOptions, useAxisScales, useSeries, useShapes, useTooltip, } from '../hooks';
9
9
  import './styles.css';
10
10
  const b = block('d3');
11
11
  export const Chart = (props) => {
12
- const { top, left, width, height, data } = props;
13
12
  // FIXME: add data validation
13
+ const { top, left, width, height, data } = props;
14
14
  const svgRef = React.createRef();
15
15
  const { chartHovered, handleMouseEnter, handleMouseLeave } = useChartEvents();
16
- const { chart, legend, title, tooltip, xAxis, yAxis } = useChartOptions(data);
16
+ const { chart, title, tooltip, xAxis, yAxis } = useChartOptions({
17
+ data,
18
+ });
19
+ const { legendItems, legendConfig, preparedSeries, preparedLegend, handleLegendItemClick } = useSeries({
20
+ chartWidth: width,
21
+ chartHeight: height,
22
+ chartMargin: chart.margin,
23
+ series: data.series,
24
+ legend: data.legend,
25
+ preparedYAxis: yAxis,
26
+ });
17
27
  const { boundsWidth, boundsHeight } = useChartDimensions({
18
28
  width,
19
29
  height,
20
30
  margin: chart.margin,
21
- yAxis,
31
+ preparedLegend,
32
+ preparedXAxis: xAxis,
33
+ preparedYAxis: yAxis,
34
+ preparedSeries: preparedSeries,
22
35
  });
23
- const { preparedSeries, handleLegendItemClick } = useSeries({ series: data.series, legend });
24
36
  const { xScale, yScale } = useAxisScales({
25
37
  boundsWidth,
26
38
  boundsHeight,
@@ -53,8 +65,8 @@ export const Chart = (props) => {
53
65
  xScale && yScale && (React.createElement(React.Fragment, null,
54
66
  React.createElement(AxisY, { axises: yAxis, width: boundsWidth, height: boundsHeight, scale: yScale }),
55
67
  React.createElement("g", { transform: `translate(0, ${boundsHeight})` },
56
- React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale, chartWidth: width })))),
68
+ React.createElement(AxisX, { axis: xAxis, width: boundsWidth, height: boundsHeight, scale: xScale })))),
57
69
  shapes),
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 }))),
70
+ preparedLegend.enabled && (React.createElement(Legend, { chartSeries: preparedSeries, boundsWidth: boundsWidth, legend: preparedLegend, items: legendItems, config: legendConfig, onItemClick: handleLegendItemClick }))),
59
71
  React.createElement(Tooltip, { hovered: hovered, pointerPosition: pointerPosition, tooltip: tooltip, xAxis: xAxis, yAxis: yAxis[0] })));
60
72
  };
@@ -1,12 +1,11 @@
1
1
  import React from 'react';
2
- import type { OnLegendItemClick, PreparedLegend, PreparedSeries } from '../hooks';
2
+ import type { OnLegendItemClick, PreparedLegend, PreparedSeries, LegendItem, LegendConfig } from '../hooks';
3
3
  type Props = {
4
- width: number;
5
- height: number;
6
- legend: PreparedLegend;
7
- offsetWidth: number;
8
- offsetHeight: number;
4
+ boundsWidth: number;
9
5
  chartSeries: PreparedSeries[];
6
+ legend: PreparedLegend;
7
+ items: LegendItem[][];
8
+ config: LegendConfig;
10
9
  onItemClick: OnLegendItemClick;
11
10
  };
12
11
  export declare const Legend: (props: Props) => React.JSX.Element;
@@ -1,18 +1,8 @@
1
1
  import React from 'react';
2
- import { select, sum } from 'd3';
3
- import get from 'lodash/get';
2
+ import { select } from 'd3';
4
3
  import { block } from '../../../../utils/cn';
5
4
  const b = block('d3-legend');
6
- const getLegendItems = (series) => {
7
- return series.reduce((acc, s) => {
8
- const legendEnabled = get(s, 'legend.enabled', true);
9
- if (legendEnabled) {
10
- acc.push(Object.assign(Object.assign({}, s), { symbol: s.legend.symbol }));
11
- }
12
- return acc;
13
- }, []);
14
- };
15
- function getLegendPosition(args) {
5
+ const getLegendPosition = (args) => {
16
6
  const { align, offsetWidth, width, contentWidth } = args;
17
7
  const top = 0;
18
8
  if (align === 'left') {
@@ -22,85 +12,150 @@ function getLegendPosition(args) {
22
12
  return { top, left: offsetWidth + width - contentWidth };
23
13
  }
24
14
  return { top, left: offsetWidth + width / 2 - contentWidth / 2 };
25
- }
15
+ };
16
+ const appendPaginator = (args) => {
17
+ const { container, offset, maxPage, legend, transform, onArrowClick } = args;
18
+ const paginationLine = container.append('g').attr('class', b('pagination'));
19
+ let computedWidth = 0;
20
+ paginationLine
21
+ .append('text')
22
+ .text('▲')
23
+ .attr('class', function () {
24
+ return b('pagination-arrow', { inactive: offset === 0 });
25
+ })
26
+ .style('font-size', legend.itemStyle.fontSize)
27
+ .each(function () {
28
+ computedWidth += this.getComputedTextLength();
29
+ })
30
+ .on('click', function () {
31
+ if (offset - 1 >= 0) {
32
+ onArrowClick(offset - 1);
33
+ }
34
+ });
35
+ paginationLine
36
+ .append('text')
37
+ .text(`${offset + 1}/${maxPage}`)
38
+ .attr('class', b('pagination-counter'))
39
+ .attr('x', computedWidth)
40
+ .style('font-size', legend.itemStyle.fontSize)
41
+ .each(function () {
42
+ computedWidth += this.getComputedTextLength();
43
+ });
44
+ paginationLine
45
+ .append('text')
46
+ .text('▼')
47
+ .attr('class', function () {
48
+ return b('pagination-arrow', { inactive: offset === maxPage - 1 });
49
+ })
50
+ .attr('x', computedWidth)
51
+ .style('font-size', legend.itemStyle.fontSize)
52
+ .on('click', function () {
53
+ if (offset + 1 < maxPage) {
54
+ onArrowClick(offset + 1);
55
+ }
56
+ });
57
+ paginationLine.attr('transform', transform);
58
+ };
26
59
  export const Legend = (props) => {
27
- const { width, offsetWidth, height, offsetHeight, chartSeries, legend, onItemClick } = props;
60
+ const { boundsWidth, chartSeries, legend, items, config, onItemClick } = props;
28
61
  const ref = React.useRef(null);
62
+ const [paginationOffset, setPaginationOffset] = React.useState(0);
63
+ React.useEffect(() => {
64
+ setPaginationOffset(0);
65
+ }, [boundsWidth]);
29
66
  React.useEffect(() => {
67
+ var _a;
30
68
  if (!ref.current) {
31
69
  return;
32
70
  }
33
- const legendItems = getLegendItems(chartSeries);
34
- const textWidths = [0];
35
71
  const svgElement = select(ref.current);
36
72
  svgElement.selectAll('*').remove();
37
- const legendItemTemplate = svgElement
38
- .selectAll('legend-history')
39
- .data(legendItems)
40
- .enter()
41
- .append('g')
42
- .attr('class', b('item'))
43
- .on('click', function (e, d) {
44
- onItemClick({ name: d.name, metaKey: e.metaKey });
45
- });
46
- svgElement
47
- .selectAll('*')
48
- .data(legendItems)
49
- .append('text')
50
- .text(function (d) {
51
- return d.name;
52
- })
53
- .each(function () {
54
- textWidths.push(this.getComputedTextLength());
55
- })
56
- .remove();
57
- legendItemTemplate
58
- .append('rect')
59
- .attr('x', function (legendItem, i) {
60
- return (i * legendItem.symbol.width +
61
- i * legend.itemDistance +
62
- i * legendItem.symbol.padding +
63
- textWidths.slice(0, i + 1).reduce((acc, tw) => acc + tw, 0));
64
- })
65
- .attr('y', (legendItem) => offsetHeight - legendItem.symbol.height / 2)
66
- .attr('width', (legendItem) => {
67
- return legendItem.symbol.width;
68
- })
69
- .attr('height', (legendItem) => legendItem.symbol.height)
70
- .attr('rx', (legendItem) => legendItem.symbol.radius)
71
- .attr('class', b('item-shape'))
72
- .style('fill', function (d) {
73
- return d.color;
73
+ const limit = (_a = config.pagination) === null || _a === void 0 ? void 0 : _a.limit;
74
+ const pageItems = typeof limit === 'number'
75
+ ? items.slice(paginationOffset * limit, paginationOffset * limit + limit)
76
+ : items;
77
+ pageItems.forEach((line, lineIndex) => {
78
+ var _a;
79
+ const textWidths = [];
80
+ const legendLine = svgElement.append('g').attr('class', b('line'));
81
+ const legendItemTemplate = legendLine
82
+ .selectAll('legend-history')
83
+ .data(line)
84
+ .enter()
85
+ .append('g')
86
+ .attr('class', b('item'))
87
+ .on('click', function (e, d) {
88
+ onItemClick({ name: d.name, metaKey: e.metaKey });
89
+ })
90
+ .each(function (d) {
91
+ textWidths.push(d.textWidth);
92
+ });
93
+ legendItemTemplate
94
+ .append('rect')
95
+ .attr('x', function (legendItem, i) {
96
+ return (i * legendItem.symbol.width +
97
+ i * legend.itemDistance +
98
+ i * legendItem.symbol.padding +
99
+ textWidths.slice(0, i).reduce((acc, tw) => acc + tw, 0));
100
+ })
101
+ .attr('y', (legendItem) => {
102
+ const lineOffset = legend.lineHeight * lineIndex;
103
+ return config.offset.top + lineOffset - legendItem.symbol.height / 2;
104
+ })
105
+ .attr('width', (legendItem) => {
106
+ return legendItem.symbol.width;
107
+ })
108
+ .attr('height', (legendItem) => legendItem.symbol.height)
109
+ .attr('rx', (legendItem) => legendItem.symbol.radius)
110
+ .attr('class', function (d) {
111
+ return b('item-shape', { unselected: !d.visible });
112
+ })
113
+ .style('fill', function (d) {
114
+ return d.visible ? d.color : '';
115
+ });
116
+ legendItemTemplate
117
+ .append('text')
118
+ .attr('x', function (legendItem, i) {
119
+ return (i * legendItem.symbol.width +
120
+ i * legend.itemDistance +
121
+ i * legendItem.symbol.padding +
122
+ legendItem.symbol.width +
123
+ legendItem.symbol.padding +
124
+ textWidths.slice(0, i).reduce((acc, tw) => acc + tw, 0));
125
+ })
126
+ .attr('y', config.offset.top + legend.lineHeight * lineIndex)
127
+ .attr('class', function (d) {
128
+ const mods = { selected: d.visible, unselected: !d.visible };
129
+ return b('item-text', mods);
130
+ })
131
+ .text(function (d) {
132
+ return ('name' in d && d.name);
133
+ })
134
+ .style('font-size', legend.itemStyle.fontSize)
135
+ .style('alignment-baseline', 'middle');
136
+ const contentWidth = ((_a = legendLine.node()) === null || _a === void 0 ? void 0 : _a.getBoundingClientRect().width) || 0;
137
+ const { left } = getLegendPosition({
138
+ align: legend.align,
139
+ width: boundsWidth,
140
+ offsetWidth: config.offset.left,
141
+ contentWidth,
142
+ });
143
+ legendLine.attr('transform', `translate(${[left, 0].join(',')})`);
74
144
  });
75
- legendItemTemplate
76
- .append('text')
77
- .attr('x', function (legendItem, i) {
78
- return (i * legendItem.symbol.width +
79
- i * legend.itemDistance +
80
- i * legendItem.symbol.padding +
81
- legendItem.symbol.width +
82
- legendItem.symbol.padding +
83
- textWidths.slice(0, i + 1).reduce((acc, tw) => acc + tw, 0));
84
- })
85
- .attr('y', offsetHeight)
86
- .attr('class', function (d) {
87
- const mods = { selected: d.visible, unselected: !d.visible };
88
- return b('item-text', mods);
89
- })
90
- .text(function (d) {
91
- return ('name' in d && d.name);
92
- })
93
- .style('alignment-baseline', 'middle');
94
- const contentWidth = sum(textWidths) +
95
- sum(legendItems, (item) => item.symbol.width + item.symbol.padding) +
96
- legend.itemDistance * (legendItems.length - 1);
97
- const { left } = getLegendPosition({
98
- align: legend.align,
99
- width,
100
- offsetWidth,
101
- contentWidth,
102
- });
103
- svgElement.attr('transform', `translate(${[left, 0].join(',')})`);
104
- }, [width, offsetWidth, height, offsetHeight, chartSeries, onItemClick, legend]);
105
- return React.createElement("g", { ref: ref, width: width, height: height });
145
+ if (config.pagination) {
146
+ const transform = `translate(${[
147
+ config.offset.left,
148
+ config.offset.top + legend.lineHeight * config.pagination.limit,
149
+ ].join(',')})`;
150
+ appendPaginator({
151
+ container: svgElement,
152
+ offset: paginationOffset,
153
+ maxPage: config.pagination.maxPage,
154
+ legend,
155
+ transform,
156
+ onArrowClick: setPaginationOffset,
157
+ });
158
+ }
159
+ }, [boundsWidth, chartSeries, onItemClick, legend, items, config, paginationOffset]);
160
+ return React.createElement("g", { ref: ref, width: boundsWidth, height: legend.height });
106
161
  };
@@ -23,6 +23,10 @@
23
23
  fill: var(--g-color-base-misc-medium);
24
24
  }
25
25
 
26
+ .chartkit-d3-legend__item-shape_unselected {
27
+ fill: var(--g-color-text-hint);
28
+ }
29
+
26
30
  .chartkit-d3-legend__item-text {
27
31
  fill: var(--g-color-text-secondary);
28
32
  }
@@ -35,6 +39,29 @@
35
39
  fill: var(--g-color-text-complementary);
36
40
  }
37
41
 
42
+ .chartkit-d3-legend__pagination {
43
+ fill: var(--g-color-text-primary);
44
+ user-select: none;
45
+ }
46
+
47
+ .chartkit-d3-legend__pagination-counter, .chartkit-d3-legend__pagination-arrow {
48
+ alignment-baseline: middle;
49
+ }
50
+
51
+ .chartkit-d3-legend__pagination-arrow {
52
+ fill: var(--g-color-text-brand);
53
+ cursor: pointer;
54
+ }
55
+
56
+ .chartkit-d3-legend__pagination-arrow_inactive {
57
+ fill: var(--g-color-base-generic-accent-disabled);
58
+ cursor: inherit;
59
+ }
60
+
61
+ .chartkit-d3-legend__pagination-arrow:hover:not(.chartkit-d3-legend__pagination-arrow_inactive) {
62
+ fill: var(--g-color-base-brand-hover);
63
+ }
64
+
38
65
  .chartkit-d3-title {
39
66
  font-size: var(--g-text-subheader-2-font-size);
40
67
  font-weight: var(--g-text-subheader-font-weight);
@@ -0,0 +1,5 @@
1
+ export declare const axisLabelsDefaults: {
2
+ margin: number;
3
+ padding: number;
4
+ fontSize: number;
5
+ };
@@ -0,0 +1,5 @@
1
+ export const axisLabelsDefaults = {
2
+ margin: 10,
3
+ padding: 10,
4
+ fontSize: 11,
5
+ };
@@ -0,0 +1,2 @@
1
+ export * from './axis';
2
+ export * from './legend';
@@ -0,0 +1,2 @@
1
+ export * from './axis';
2
+ export * from './legend';
@@ -0,0 +1,4 @@
1
+ import type { ChartKitWidgetLegend } from '../../../../../types';
2
+ type LegendDefaults = Required<Omit<ChartKitWidgetLegend, 'enabled'>> & Pick<ChartKitWidgetLegend, 'enabled'>;
3
+ export declare const legendDefaults: LegendDefaults;
4
+ export {};