@gravity-ui/charts 1.30.0 → 1.31.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.
Files changed (39) hide show
  1. package/dist/cjs/components/AxisX/AxisX.js +6 -4
  2. package/dist/cjs/components/AxisX/prepare-axis-data.js +4 -2
  3. package/dist/cjs/components/AxisX/types.d.ts +1 -0
  4. package/dist/cjs/components/AxisY/AxisY.js +4 -2
  5. package/dist/cjs/components/AxisY/prepare-axis-data.js +2 -0
  6. package/dist/cjs/components/AxisY/types.d.ts +1 -0
  7. package/dist/cjs/components/Legend/index.js +13 -20
  8. package/dist/cjs/components/Tooltip/DefaultTooltipContent/Row.d.ts +1 -0
  9. package/dist/cjs/components/Tooltip/DefaultTooltipContent/Row.js +11 -2
  10. package/dist/cjs/components/Tooltip/DefaultTooltipContent/index.js +5 -3
  11. package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.d.ts +6 -0
  12. package/dist/cjs/components/Tooltip/DefaultTooltipContent/utils.js +18 -0
  13. package/dist/cjs/components/utils.d.ts +11 -0
  14. package/dist/cjs/components/utils.js +25 -1
  15. package/dist/cjs/hooks/useAxis/types.d.ts +2 -0
  16. package/dist/cjs/hooks/useAxis/utils.d.ts +1 -0
  17. package/dist/cjs/hooks/useAxis/utils.js +2 -1
  18. package/dist/cjs/hooks/useChartOptions/title.js +1 -1
  19. package/dist/cjs/types/chart/axis.d.ts +3 -0
  20. package/dist/esm/components/AxisX/AxisX.js +6 -4
  21. package/dist/esm/components/AxisX/prepare-axis-data.js +4 -2
  22. package/dist/esm/components/AxisX/types.d.ts +1 -0
  23. package/dist/esm/components/AxisY/AxisY.js +4 -2
  24. package/dist/esm/components/AxisY/prepare-axis-data.js +2 -0
  25. package/dist/esm/components/AxisY/types.d.ts +1 -0
  26. package/dist/esm/components/Legend/index.js +13 -20
  27. package/dist/esm/components/Tooltip/DefaultTooltipContent/Row.d.ts +1 -0
  28. package/dist/esm/components/Tooltip/DefaultTooltipContent/Row.js +11 -2
  29. package/dist/esm/components/Tooltip/DefaultTooltipContent/index.js +5 -3
  30. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.d.ts +6 -0
  31. package/dist/esm/components/Tooltip/DefaultTooltipContent/utils.js +18 -0
  32. package/dist/esm/components/utils.d.ts +11 -0
  33. package/dist/esm/components/utils.js +25 -1
  34. package/dist/esm/hooks/useAxis/types.d.ts +2 -0
  35. package/dist/esm/hooks/useAxis/utils.d.ts +1 -0
  36. package/dist/esm/hooks/useAxis/utils.js +2 -1
  37. package/dist/esm/hooks/useChartOptions/title.js +1 -1
  38. package/dist/esm/types/chart/axis.d.ts +3 -0
  39. package/package.json +1 -1
@@ -112,7 +112,7 @@ export const AxisX = (props) => {
112
112
  .attr('fill', (d) => d.color)
113
113
  .attr('opacity', (d) => d.opacity);
114
114
  plotBandsSelection.each(function () {
115
- var _a, _b;
115
+ var _a, _b, _c;
116
116
  const plotBandSelection = select(this);
117
117
  const band = plotBandSelection.datum();
118
118
  const label = band.label;
@@ -125,7 +125,8 @@ export const AxisX = (props) => {
125
125
  .style('font-weight', (_b = label.style.fontWeight) !== null && _b !== void 0 ? _b : '')
126
126
  .style('dominant-baseline', 'text-before-edge')
127
127
  .style('text-anchor', 'start')
128
- .style('transform', `translate(${label.x}px, ${label.y}px) rotate(${label.rotate}deg)`);
128
+ .style('transform', `translate(${label.x}px, ${label.y}px) rotate(${label.rotate}deg)`)
129
+ .attr('data-qa', (_c = label.qa) !== null && _c !== void 0 ? _c : null);
129
130
  }
130
131
  });
131
132
  };
@@ -153,7 +154,7 @@ export const AxisX = (props) => {
153
154
  .attr('stroke-dasharray', (d) => getLineDashArray(d.dashStyle, d.lineWidth))
154
155
  .attr('opacity', (d) => d.opacity);
155
156
  plotLinesSelection.each(function () {
156
- var _a, _b;
157
+ var _a, _b, _c;
157
158
  const itemSelection = select(this);
158
159
  const plotLine = itemSelection.datum();
159
160
  const label = plotLine.label;
@@ -166,7 +167,8 @@ export const AxisX = (props) => {
166
167
  .style('font-weight', (_b = label.style.fontWeight) !== null && _b !== void 0 ? _b : '')
167
168
  .style('dominant-baseline', 'text-before-edge')
168
169
  .style('text-anchor', 'start')
169
- .style('transform', `translate(${label.x}px, ${label.y}px) rotate(${label.rotate}deg)`);
170
+ .style('transform', `translate(${label.x}px, ${label.y}px) rotate(${label.rotate}deg)`)
171
+ .attr('data-qa', (_c = label.qa) !== null && _c !== void 0 ? _c : null);
170
172
  }
171
173
  });
172
174
  };
@@ -251,7 +251,7 @@ export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, bounds
251
251
  plotBands.push({
252
252
  layerPlacement: plotBand.layerPlacement,
253
253
  x: Math.max(0, startPos),
254
- y: 0,
254
+ y: axisTop,
255
255
  width: plotBandWidth,
256
256
  height: axisHeight,
257
257
  color: plotBand.color,
@@ -263,6 +263,7 @@ export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, bounds
263
263
  x: plotBand.label.padding,
264
264
  y: plotBand.label.padding + ((_f = labelSize === null || labelSize === void 0 ? void 0 : labelSize.width) !== null && _f !== void 0 ? _f : 0),
265
265
  rotate: -90,
266
+ qa: plotBand.label.qa,
266
267
  }
267
268
  : null,
268
269
  });
@@ -289,12 +290,13 @@ export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, bounds
289
290
  x: plotLineValue - plotLine.label.padding - size.height,
290
291
  y: plotLine.label.padding + size.width,
291
292
  rotate: -90,
293
+ qa: plotLine.label.qa,
292
294
  };
293
295
  }
294
296
  plotLines.push({
295
297
  layerPlacement: plotLine.layerPlacement,
296
298
  x: 0,
297
- y: 0,
299
+ y: axisTop,
298
300
  width: axisWidth,
299
301
  color: plotLine.color,
300
302
  opacity: plotLine.opacity,
@@ -47,6 +47,7 @@ export type AxisPlotLineLabel = {
47
47
  style: BaseTextStyle;
48
48
  x: number;
49
49
  y: number;
50
+ qa?: string;
50
51
  };
51
52
  export type AxisPlotLineData = {
52
53
  layerPlacement: PlotLayerPlacement;
@@ -118,7 +118,7 @@ export const AxisY = (props) => {
118
118
  .attr('fill', (d) => d.color)
119
119
  .attr('opacity', (d) => d.opacity);
120
120
  plotBandsSelection.each(function () {
121
- var _a, _b;
121
+ var _a, _b, _c;
122
122
  const plotBandSelection = select(this);
123
123
  const band = plotBandSelection.datum();
124
124
  const label = band.label;
@@ -130,6 +130,7 @@ export const AxisY = (props) => {
130
130
  .style('font-size', label.style.fontSize)
131
131
  .style('font-weight', (_b = label.style.fontWeight) !== null && _b !== void 0 ? _b : '')
132
132
  .style('dominant-baseline', 'text-before-edge')
133
+ .attr('data-qa', (_c = label.qa) !== null && _c !== void 0 ? _c : null)
133
134
  .attr('x', label.x)
134
135
  .attr('y', label.y);
135
136
  }
@@ -159,7 +160,7 @@ export const AxisY = (props) => {
159
160
  .attr('stroke-dasharray', (d) => getLineDashArray(d.dashStyle, d.lineWidth))
160
161
  .attr('opacity', (d) => d.opacity);
161
162
  plotLinesSelection.each(function () {
162
- var _a, _b;
163
+ var _a, _b, _c;
163
164
  const itemSelection = select(this);
164
165
  const plotLine = itemSelection.datum();
165
166
  const label = plotLine.label;
@@ -171,6 +172,7 @@ export const AxisY = (props) => {
171
172
  .style('font-size', label.style.fontSize)
172
173
  .style('font-weight', (_b = label.style.fontWeight) !== null && _b !== void 0 ? _b : '')
173
174
  .style('dominant-baseline', 'text-before-edge')
175
+ .attr('data-qa', (_c = label.qa) !== null && _c !== void 0 ? _c : null)
174
176
  .attr('x', label.x)
175
177
  .attr('y', label.y);
176
178
  }
@@ -229,6 +229,7 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
229
229
  style: plotBand.label.style,
230
230
  x: plotBand.label.padding,
231
231
  y: plotBand.label.padding,
232
+ qa: plotBand.label.qa,
232
233
  }
233
234
  : null,
234
235
  });
@@ -254,6 +255,7 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
254
255
  style: plotLine.label.style,
255
256
  x: plotLine.label.padding,
256
257
  y: Math.max(0, plotLineValue - size.height - plotLine.label.padding),
258
+ qa: plotLine.label.qa,
257
259
  };
258
260
  }
259
261
  plotLines.push({
@@ -58,6 +58,7 @@ export type AxisPlotLineLabel = {
58
58
  style: BaseTextStyle;
59
59
  x: number;
60
60
  y: number;
61
+ qa?: string;
61
62
  };
62
63
  export type AxisPlotLineData = {
63
64
  layerPlacement: PlotLayerPlacement;
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
- import { line as lineGenerator, scaleLinear, select, symbol } from 'd3';
2
+ import { scaleLinear, select, symbol } from 'd3';
3
3
  import { CONTINUOUS_LEGEND_SIZE } from '../../constants';
4
4
  import { formatNumber } from '../../libs';
5
- import { block, createGradientRect, getContinuesColorFn, getLabelsSize, getLineDashArray, getSymbol, getUniqId, handleOverflowingText, } from '../../utils';
5
+ import { block, createGradientRect, getContinuesColorFn, getLabelsSize, getSymbol, getUniqId, handleOverflowingText, } from '../../utils';
6
6
  import { axisBottom } from '../../utils/chart/axis-generators';
7
+ import { appendLinePathElement } from '../utils';
7
8
  import './styles.css';
8
9
  const b = block('legend');
9
10
  const getLegendItemLeftPosition = (args) => {
@@ -64,9 +65,6 @@ const appendPaginator = (args) => {
64
65
  });
65
66
  paginationLine.attr('transform', transform);
66
67
  };
67
- const legendSymbolGenerator = lineGenerator()
68
- .x((d) => d.x)
69
- .y((d) => d.y);
70
68
  function renderLegendSymbol(args) {
71
69
  const { selection, legend, legendLineHeight } = args;
72
70
  const line = selection.data();
@@ -86,21 +84,16 @@ function renderLegendSymbol(args) {
86
84
  const color = d.visible ? d.color : '';
87
85
  switch (d.symbol.shape) {
88
86
  case 'path': {
89
- const y = legendLineHeight / 2;
90
- const points = [
91
- { x, y },
92
- { x: x + d.symbol.width, y },
93
- ];
94
- element
95
- .append('path')
96
- .attr('d', legendSymbolGenerator(points))
97
- .attr('fill', 'none')
98
- .attr('stroke-width', d.symbol.strokeWidth)
99
- .attr('class', className)
100
- .style('stroke', color);
101
- if (d.dashStyle) {
102
- element.attr('stroke-dasharray', getLineDashArray(d.dashStyle, d.symbol.strokeWidth));
103
- }
87
+ appendLinePathElement({
88
+ svgRootElement: element.node(),
89
+ x,
90
+ height: legendLineHeight,
91
+ width: d.symbol.width,
92
+ color,
93
+ className,
94
+ dashStyle: d.dashStyle,
95
+ lineWidth: d.symbol.strokeWidth,
96
+ });
104
97
  break;
105
98
  }
106
99
  case 'rect': {
@@ -4,6 +4,7 @@ export declare function Row(props: {
4
4
  active?: boolean;
5
5
  className?: string;
6
6
  color?: string;
7
+ colorSymbol?: React.ReactNode;
7
8
  striped?: boolean;
8
9
  style?: React.CSSProperties;
9
10
  value?: React.ReactNode;
@@ -2,9 +2,18 @@ import React from 'react';
2
2
  import { block } from '../../../utils';
3
3
  const b = block('tooltip');
4
4
  export function Row(props) {
5
- const { label, value, active, color, className, striped, style } = props;
5
+ const { label, value, active, color, colorSymbol, className, striped, style } = props;
6
+ const colorItem = React.useMemo(() => {
7
+ if (colorSymbol) {
8
+ return colorSymbol;
9
+ }
10
+ if (color) {
11
+ return React.createElement("div", { className: b('content-row-color'), style: { backgroundColor: color } });
12
+ }
13
+ return null;
14
+ }, [color, colorSymbol]);
6
15
  return (React.createElement("div", { className: b('content-row', { active, striped }, className), style: style },
7
- color && React.createElement("div", { className: b('content-row-color'), style: { backgroundColor: color } }),
16
+ colorItem,
8
17
  label,
9
18
  value && React.createElement("span", { className: b('content-row-value') }, value)));
10
19
  }
@@ -8,7 +8,7 @@ import { block, hasVerticalScrollbar } from '../../../utils';
8
8
  import { getFormattedValue } from '../../../utils/chart/format';
9
9
  import { Row } from './Row';
10
10
  import { RowWithAggregation } from './RowWithAggregation';
11
- import { getDefaultValueFormat, getHoveredValues, getMeasureValue, getPreparedAggregation, getXRowData, } from './utils';
11
+ import { getDefaultValueFormat, getHoveredValues, getMeasureValue, getPreparedAggregation, getTooltipRowColorSymbol, getXRowData, } from './utils';
12
12
  const b = block('tooltip');
13
13
  export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, valueFormat, headerFormat, xAxis, yAxis, qa, }) => {
14
14
  var _a;
@@ -21,7 +21,7 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
21
21
  const prevHoveredValues = usePrevious(hoveredValues);
22
22
  const visibleHovered = pinned || !visibleRows ? hovered : hovered.slice(0, visibleRows);
23
23
  const restHoveredValues = pinned || !visibleRows ? [] : hoveredValues.slice(visibleRows);
24
- const renderRow = ({ id, name, color, active, striped, value, formattedValue, }) => {
24
+ const renderRow = ({ id, name, color, active, striped, value, formattedValue, series, }) => {
25
25
  if (typeof rowRenderer === 'function') {
26
26
  return rowRenderer({
27
27
  id,
@@ -35,7 +35,8 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
35
35
  hovered,
36
36
  });
37
37
  }
38
- return (React.createElement(Row, { key: id, active: active, color: color, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: name } }), striped: striped, value: formattedValue }));
38
+ const colorSymbol = getTooltipRowColorSymbol({ series, color });
39
+ return (React.createElement(Row, { key: id, active: active, color: color, colorSymbol: colorSymbol ? (React.createElement("div", { dangerouslySetInnerHTML: { __html: colorSymbol.outerHTML } })) : undefined, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: name } }), striped: striped, value: formattedValue }));
39
40
  };
40
41
  const formattedHeadValue = headerFormat
41
42
  ? getFormattedValue({
@@ -113,6 +114,7 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
113
114
  striped,
114
115
  value: hoveredValues[i],
115
116
  formattedValue,
117
+ series,
116
118
  });
117
119
  }
118
120
  case 'waterfall': {
@@ -35,3 +35,9 @@ export declare function getPreparedAggregation(args: {
35
35
  xAxis?: ChartXAxis | null;
36
36
  yAxis?: ChartYAxis;
37
37
  }): ChartTooltipTotalsBuiltInAggregation | (() => ChartTooltipTotalsAggregationValue);
38
+ export declare function getTooltipRowColorSymbol({ series, color, height, width, }: {
39
+ color?: string;
40
+ series?: TooltipDataChunk['series'];
41
+ height?: number;
42
+ width?: number;
43
+ }): SVGSVGElement | null;
@@ -1,7 +1,9 @@
1
+ import { create } from 'd3-selection';
1
2
  import get from 'lodash/get';
2
3
  import { i18n } from '../../../i18n';
3
4
  import { getDataCategoryValue, getDefaultDateFormat } from '../../../utils';
4
5
  import { getFormattedValue } from '../../../utils/chart/format';
6
+ import { appendLinePathElement } from '../../utils';
5
7
  function getRowData(fieldName, data, axis) {
6
8
  switch (axis === null || axis === void 0 ? void 0 : axis.type) {
7
9
  case 'category': {
@@ -128,3 +130,19 @@ export function getPreparedAggregation(args) {
128
130
  }
129
131
  return 'sum';
130
132
  }
133
+ export function getTooltipRowColorSymbol({ series, color, height = 8, width = 16, }) {
134
+ if ((series === null || series === void 0 ? void 0 : series.type) === 'line') {
135
+ const colorSymbol = create('svg').attr('height', height).attr('width', width);
136
+ const g = colorSymbol.append('g');
137
+ appendLinePathElement({
138
+ svgRootElement: g.node(),
139
+ height,
140
+ width,
141
+ color,
142
+ dashStyle: get(series, 'dashStyle'),
143
+ lineWidth: get(series, 'lineWidth'),
144
+ });
145
+ return colorSymbol.node();
146
+ }
147
+ return null;
148
+ }
@@ -1,3 +1,4 @@
1
+ import type { DashStyle } from '../constants';
1
2
  import type { ChartScaleLinear, ChartScaleTime } from '../hooks';
2
3
  import type { ChartAxisRangeSlider } from '../types';
3
4
  export declare function getInitialRangeSliderState(args: {
@@ -7,3 +8,13 @@ export declare function getInitialRangeSliderState(args: {
7
8
  min: number;
8
9
  max: number;
9
10
  };
11
+ export declare function appendLinePathElement({ svgRootElement, height, width, x, lineWidth, dashStyle, className, color, }: {
12
+ svgRootElement: SVGGElement | null;
13
+ height: number;
14
+ width: number;
15
+ x?: number;
16
+ lineWidth?: number;
17
+ dashStyle?: DashStyle;
18
+ className?: string;
19
+ color?: string;
20
+ }): import("d3-selection").Selection<SVGGElement | null, unknown, null, undefined>;
@@ -1,5 +1,7 @@
1
1
  import { duration } from '@gravity-ui/date-utils';
2
- import { isTimeScale } from '../utils';
2
+ import { line as lineGenerator } from 'd3';
3
+ import { select } from 'd3-selection';
4
+ import { getLineDashArray, isTimeScale } from '../utils';
3
5
  export function getInitialRangeSliderState(args) {
4
6
  const { defaultRange, xScale } = args;
5
7
  let minRange;
@@ -32,3 +34,25 @@ export function getInitialRangeSliderState(args) {
32
34
  }
33
35
  return { min: minRange, max: maxRange };
34
36
  }
37
+ const legendSymbolGenerator = lineGenerator()
38
+ .x((d) => d.x)
39
+ .y((d) => d.y);
40
+ export function appendLinePathElement({ svgRootElement, height, width, x = 0, lineWidth = 1, dashStyle, className, color, }) {
41
+ const rootELementSelection = select(svgRootElement);
42
+ const y = height / 2;
43
+ const points = [
44
+ { x, y },
45
+ { x: x + width, y },
46
+ ];
47
+ const pathElement = rootELementSelection
48
+ .append('path')
49
+ .attr('d', legendSymbolGenerator(points))
50
+ .attr('fill', 'none')
51
+ .attr('stroke-width', lineWidth)
52
+ .attr('stroke', color !== null && color !== void 0 ? color : '')
53
+ .attr('class', className !== null && className !== void 0 ? className : null);
54
+ if (dashStyle) {
55
+ pathElement.attr('stroke-dasharray', getLineDashArray(dashStyle, lineWidth));
56
+ }
57
+ return rootELementSelection;
58
+ }
@@ -13,6 +13,7 @@ export type PreparedAxisPlotBand = Required<AxisPlotBand> & {
13
13
  text: string;
14
14
  style: BaseTextStyle;
15
15
  padding: number;
16
+ qa?: string;
16
17
  };
17
18
  };
18
19
  type PreparedAxisCrosshair = Required<AxisCrosshair>;
@@ -27,6 +28,7 @@ export type PreparedAxisPlotLine = {
27
28
  text: string;
28
29
  style: BaseTextStyle;
29
30
  padding: number;
31
+ qa?: string;
30
32
  };
31
33
  };
32
34
  export type PreparedRangeSlider = DeepRequired<Omit<ChartAxisRangeSlider, 'defaultRange'>> & {
@@ -7,4 +7,5 @@ export declare function prepareAxisPlotLabel(d: AxisPlot): {
7
7
  fontColor: string;
8
8
  };
9
9
  padding: number;
10
+ qa: string | undefined;
10
11
  };
@@ -1,9 +1,10 @@
1
1
  import { DEFAULT_AXIS_LABEL_FONT_SIZE } from '../../constants';
2
2
  export function prepareAxisPlotLabel(d) {
3
- var _a, _b, _c, _d, _e;
3
+ var _a, _b, _c, _d, _e, _f;
4
4
  return {
5
5
  text: (_b = (_a = d.label) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : '',
6
6
  style: Object.assign({ fontSize: DEFAULT_AXIS_LABEL_FONT_SIZE, fontColor: 'var(--g-color-text-secondary)' }, (_c = d.label) === null || _c === void 0 ? void 0 : _c.style),
7
7
  padding: (_e = (_d = d.label) === null || _d === void 0 ? void 0 : _d.padding) !== null && _e !== void 0 ? _e : 5,
8
+ qa: (_f = d.label) === null || _f === void 0 ? void 0 : _f.qa,
8
9
  };
9
10
  }
@@ -14,7 +14,7 @@ export const getPreparedTitle = ({ title, }) => {
14
14
  ? getHorizontalSvgTextHeight({ text: titleText, style: titleStyle }) + TITLE_PADDINGS
15
15
  : 0;
16
16
  const preparedTitle = titleText
17
- ? { text: titleText, style: titleStyle, height: titleHeight }
17
+ ? { text: titleText, style: titleStyle, height: titleHeight, qa: title === null || title === void 0 ? void 0 : title.qa }
18
18
  : undefined;
19
19
  return preparedTitle;
20
20
  };
@@ -194,6 +194,9 @@ export interface AxisPlot {
194
194
  * @default 5
195
195
  */
196
196
  padding?: number;
197
+ /** Can be used for the UI automated test.
198
+ * It is assigned as a data-qa attribute to an element. */
199
+ qa?: string;
197
200
  };
198
201
  }
199
202
  export interface AxisPlotLine extends AxisPlot {
@@ -112,7 +112,7 @@ export const AxisX = (props) => {
112
112
  .attr('fill', (d) => d.color)
113
113
  .attr('opacity', (d) => d.opacity);
114
114
  plotBandsSelection.each(function () {
115
- var _a, _b;
115
+ var _a, _b, _c;
116
116
  const plotBandSelection = select(this);
117
117
  const band = plotBandSelection.datum();
118
118
  const label = band.label;
@@ -125,7 +125,8 @@ export const AxisX = (props) => {
125
125
  .style('font-weight', (_b = label.style.fontWeight) !== null && _b !== void 0 ? _b : '')
126
126
  .style('dominant-baseline', 'text-before-edge')
127
127
  .style('text-anchor', 'start')
128
- .style('transform', `translate(${label.x}px, ${label.y}px) rotate(${label.rotate}deg)`);
128
+ .style('transform', `translate(${label.x}px, ${label.y}px) rotate(${label.rotate}deg)`)
129
+ .attr('data-qa', (_c = label.qa) !== null && _c !== void 0 ? _c : null);
129
130
  }
130
131
  });
131
132
  };
@@ -153,7 +154,7 @@ export const AxisX = (props) => {
153
154
  .attr('stroke-dasharray', (d) => getLineDashArray(d.dashStyle, d.lineWidth))
154
155
  .attr('opacity', (d) => d.opacity);
155
156
  plotLinesSelection.each(function () {
156
- var _a, _b;
157
+ var _a, _b, _c;
157
158
  const itemSelection = select(this);
158
159
  const plotLine = itemSelection.datum();
159
160
  const label = plotLine.label;
@@ -166,7 +167,8 @@ export const AxisX = (props) => {
166
167
  .style('font-weight', (_b = label.style.fontWeight) !== null && _b !== void 0 ? _b : '')
167
168
  .style('dominant-baseline', 'text-before-edge')
168
169
  .style('text-anchor', 'start')
169
- .style('transform', `translate(${label.x}px, ${label.y}px) rotate(${label.rotate}deg)`);
170
+ .style('transform', `translate(${label.x}px, ${label.y}px) rotate(${label.rotate}deg)`)
171
+ .attr('data-qa', (_c = label.qa) !== null && _c !== void 0 ? _c : null);
170
172
  }
171
173
  });
172
174
  };
@@ -251,7 +251,7 @@ export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, bounds
251
251
  plotBands.push({
252
252
  layerPlacement: plotBand.layerPlacement,
253
253
  x: Math.max(0, startPos),
254
- y: 0,
254
+ y: axisTop,
255
255
  width: plotBandWidth,
256
256
  height: axisHeight,
257
257
  color: plotBand.color,
@@ -263,6 +263,7 @@ export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, bounds
263
263
  x: plotBand.label.padding,
264
264
  y: plotBand.label.padding + ((_f = labelSize === null || labelSize === void 0 ? void 0 : labelSize.width) !== null && _f !== void 0 ? _f : 0),
265
265
  rotate: -90,
266
+ qa: plotBand.label.qa,
266
267
  }
267
268
  : null,
268
269
  });
@@ -289,12 +290,13 @@ export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, bounds
289
290
  x: plotLineValue - plotLine.label.padding - size.height,
290
291
  y: plotLine.label.padding + size.width,
291
292
  rotate: -90,
293
+ qa: plotLine.label.qa,
292
294
  };
293
295
  }
294
296
  plotLines.push({
295
297
  layerPlacement: plotLine.layerPlacement,
296
298
  x: 0,
297
- y: 0,
299
+ y: axisTop,
298
300
  width: axisWidth,
299
301
  color: plotLine.color,
300
302
  opacity: plotLine.opacity,
@@ -47,6 +47,7 @@ export type AxisPlotLineLabel = {
47
47
  style: BaseTextStyle;
48
48
  x: number;
49
49
  y: number;
50
+ qa?: string;
50
51
  };
51
52
  export type AxisPlotLineData = {
52
53
  layerPlacement: PlotLayerPlacement;
@@ -118,7 +118,7 @@ export const AxisY = (props) => {
118
118
  .attr('fill', (d) => d.color)
119
119
  .attr('opacity', (d) => d.opacity);
120
120
  plotBandsSelection.each(function () {
121
- var _a, _b;
121
+ var _a, _b, _c;
122
122
  const plotBandSelection = select(this);
123
123
  const band = plotBandSelection.datum();
124
124
  const label = band.label;
@@ -130,6 +130,7 @@ export const AxisY = (props) => {
130
130
  .style('font-size', label.style.fontSize)
131
131
  .style('font-weight', (_b = label.style.fontWeight) !== null && _b !== void 0 ? _b : '')
132
132
  .style('dominant-baseline', 'text-before-edge')
133
+ .attr('data-qa', (_c = label.qa) !== null && _c !== void 0 ? _c : null)
133
134
  .attr('x', label.x)
134
135
  .attr('y', label.y);
135
136
  }
@@ -159,7 +160,7 @@ export const AxisY = (props) => {
159
160
  .attr('stroke-dasharray', (d) => getLineDashArray(d.dashStyle, d.lineWidth))
160
161
  .attr('opacity', (d) => d.opacity);
161
162
  plotLinesSelection.each(function () {
162
- var _a, _b;
163
+ var _a, _b, _c;
163
164
  const itemSelection = select(this);
164
165
  const plotLine = itemSelection.datum();
165
166
  const label = plotLine.label;
@@ -171,6 +172,7 @@ export const AxisY = (props) => {
171
172
  .style('font-size', label.style.fontSize)
172
173
  .style('font-weight', (_b = label.style.fontWeight) !== null && _b !== void 0 ? _b : '')
173
174
  .style('dominant-baseline', 'text-before-edge')
175
+ .attr('data-qa', (_c = label.qa) !== null && _c !== void 0 ? _c : null)
174
176
  .attr('x', label.x)
175
177
  .attr('y', label.y);
176
178
  }
@@ -229,6 +229,7 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
229
229
  style: plotBand.label.style,
230
230
  x: plotBand.label.padding,
231
231
  y: plotBand.label.padding,
232
+ qa: plotBand.label.qa,
232
233
  }
233
234
  : null,
234
235
  });
@@ -254,6 +255,7 @@ export async function prepareYAxisData({ axis, split, scale, top: topOffset, wid
254
255
  style: plotLine.label.style,
255
256
  x: plotLine.label.padding,
256
257
  y: Math.max(0, plotLineValue - size.height - plotLine.label.padding),
258
+ qa: plotLine.label.qa,
257
259
  };
258
260
  }
259
261
  plotLines.push({
@@ -58,6 +58,7 @@ export type AxisPlotLineLabel = {
58
58
  style: BaseTextStyle;
59
59
  x: number;
60
60
  y: number;
61
+ qa?: string;
61
62
  };
62
63
  export type AxisPlotLineData = {
63
64
  layerPlacement: PlotLayerPlacement;
@@ -1,9 +1,10 @@
1
1
  import React from 'react';
2
- import { line as lineGenerator, scaleLinear, select, symbol } from 'd3';
2
+ import { scaleLinear, select, symbol } from 'd3';
3
3
  import { CONTINUOUS_LEGEND_SIZE } from '../../constants';
4
4
  import { formatNumber } from '../../libs';
5
- import { block, createGradientRect, getContinuesColorFn, getLabelsSize, getLineDashArray, getSymbol, getUniqId, handleOverflowingText, } from '../../utils';
5
+ import { block, createGradientRect, getContinuesColorFn, getLabelsSize, getSymbol, getUniqId, handleOverflowingText, } from '../../utils';
6
6
  import { axisBottom } from '../../utils/chart/axis-generators';
7
+ import { appendLinePathElement } from '../utils';
7
8
  import './styles.css';
8
9
  const b = block('legend');
9
10
  const getLegendItemLeftPosition = (args) => {
@@ -64,9 +65,6 @@ const appendPaginator = (args) => {
64
65
  });
65
66
  paginationLine.attr('transform', transform);
66
67
  };
67
- const legendSymbolGenerator = lineGenerator()
68
- .x((d) => d.x)
69
- .y((d) => d.y);
70
68
  function renderLegendSymbol(args) {
71
69
  const { selection, legend, legendLineHeight } = args;
72
70
  const line = selection.data();
@@ -86,21 +84,16 @@ function renderLegendSymbol(args) {
86
84
  const color = d.visible ? d.color : '';
87
85
  switch (d.symbol.shape) {
88
86
  case 'path': {
89
- const y = legendLineHeight / 2;
90
- const points = [
91
- { x, y },
92
- { x: x + d.symbol.width, y },
93
- ];
94
- element
95
- .append('path')
96
- .attr('d', legendSymbolGenerator(points))
97
- .attr('fill', 'none')
98
- .attr('stroke-width', d.symbol.strokeWidth)
99
- .attr('class', className)
100
- .style('stroke', color);
101
- if (d.dashStyle) {
102
- element.attr('stroke-dasharray', getLineDashArray(d.dashStyle, d.symbol.strokeWidth));
103
- }
87
+ appendLinePathElement({
88
+ svgRootElement: element.node(),
89
+ x,
90
+ height: legendLineHeight,
91
+ width: d.symbol.width,
92
+ color,
93
+ className,
94
+ dashStyle: d.dashStyle,
95
+ lineWidth: d.symbol.strokeWidth,
96
+ });
104
97
  break;
105
98
  }
106
99
  case 'rect': {
@@ -4,6 +4,7 @@ export declare function Row(props: {
4
4
  active?: boolean;
5
5
  className?: string;
6
6
  color?: string;
7
+ colorSymbol?: React.ReactNode;
7
8
  striped?: boolean;
8
9
  style?: React.CSSProperties;
9
10
  value?: React.ReactNode;
@@ -2,9 +2,18 @@ import React from 'react';
2
2
  import { block } from '../../../utils';
3
3
  const b = block('tooltip');
4
4
  export function Row(props) {
5
- const { label, value, active, color, className, striped, style } = props;
5
+ const { label, value, active, color, colorSymbol, className, striped, style } = props;
6
+ const colorItem = React.useMemo(() => {
7
+ if (colorSymbol) {
8
+ return colorSymbol;
9
+ }
10
+ if (color) {
11
+ return React.createElement("div", { className: b('content-row-color'), style: { backgroundColor: color } });
12
+ }
13
+ return null;
14
+ }, [color, colorSymbol]);
6
15
  return (React.createElement("div", { className: b('content-row', { active, striped }, className), style: style },
7
- color && React.createElement("div", { className: b('content-row-color'), style: { backgroundColor: color } }),
16
+ colorItem,
8
17
  label,
9
18
  value && React.createElement("span", { className: b('content-row-value') }, value)));
10
19
  }
@@ -8,7 +8,7 @@ import { block, hasVerticalScrollbar } from '../../../utils';
8
8
  import { getFormattedValue } from '../../../utils/chart/format';
9
9
  import { Row } from './Row';
10
10
  import { RowWithAggregation } from './RowWithAggregation';
11
- import { getDefaultValueFormat, getHoveredValues, getMeasureValue, getPreparedAggregation, getXRowData, } from './utils';
11
+ import { getDefaultValueFormat, getHoveredValues, getMeasureValue, getPreparedAggregation, getTooltipRowColorSymbol, getXRowData, } from './utils';
12
12
  const b = block('tooltip');
13
13
  export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, valueFormat, headerFormat, xAxis, yAxis, qa, }) => {
14
14
  var _a;
@@ -21,7 +21,7 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
21
21
  const prevHoveredValues = usePrevious(hoveredValues);
22
22
  const visibleHovered = pinned || !visibleRows ? hovered : hovered.slice(0, visibleRows);
23
23
  const restHoveredValues = pinned || !visibleRows ? [] : hoveredValues.slice(visibleRows);
24
- const renderRow = ({ id, name, color, active, striped, value, formattedValue, }) => {
24
+ const renderRow = ({ id, name, color, active, striped, value, formattedValue, series, }) => {
25
25
  if (typeof rowRenderer === 'function') {
26
26
  return rowRenderer({
27
27
  id,
@@ -35,7 +35,8 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
35
35
  hovered,
36
36
  });
37
37
  }
38
- return (React.createElement(Row, { key: id, active: active, color: color, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: name } }), striped: striped, value: formattedValue }));
38
+ const colorSymbol = getTooltipRowColorSymbol({ series, color });
39
+ return (React.createElement(Row, { key: id, active: active, color: color, colorSymbol: colorSymbol ? (React.createElement("div", { dangerouslySetInnerHTML: { __html: colorSymbol.outerHTML } })) : undefined, label: React.createElement("span", { dangerouslySetInnerHTML: { __html: name } }), striped: striped, value: formattedValue }));
39
40
  };
40
41
  const formattedHeadValue = headerFormat
41
42
  ? getFormattedValue({
@@ -113,6 +114,7 @@ export const DefaultTooltipContent = ({ hovered, pinned, rowRenderer, totals, va
113
114
  striped,
114
115
  value: hoveredValues[i],
115
116
  formattedValue,
117
+ series,
116
118
  });
117
119
  }
118
120
  case 'waterfall': {
@@ -35,3 +35,9 @@ export declare function getPreparedAggregation(args: {
35
35
  xAxis?: ChartXAxis | null;
36
36
  yAxis?: ChartYAxis;
37
37
  }): ChartTooltipTotalsBuiltInAggregation | (() => ChartTooltipTotalsAggregationValue);
38
+ export declare function getTooltipRowColorSymbol({ series, color, height, width, }: {
39
+ color?: string;
40
+ series?: TooltipDataChunk['series'];
41
+ height?: number;
42
+ width?: number;
43
+ }): SVGSVGElement | null;
@@ -1,7 +1,9 @@
1
+ import { create } from 'd3-selection';
1
2
  import get from 'lodash/get';
2
3
  import { i18n } from '../../../i18n';
3
4
  import { getDataCategoryValue, getDefaultDateFormat } from '../../../utils';
4
5
  import { getFormattedValue } from '../../../utils/chart/format';
6
+ import { appendLinePathElement } from '../../utils';
5
7
  function getRowData(fieldName, data, axis) {
6
8
  switch (axis === null || axis === void 0 ? void 0 : axis.type) {
7
9
  case 'category': {
@@ -128,3 +130,19 @@ export function getPreparedAggregation(args) {
128
130
  }
129
131
  return 'sum';
130
132
  }
133
+ export function getTooltipRowColorSymbol({ series, color, height = 8, width = 16, }) {
134
+ if ((series === null || series === void 0 ? void 0 : series.type) === 'line') {
135
+ const colorSymbol = create('svg').attr('height', height).attr('width', width);
136
+ const g = colorSymbol.append('g');
137
+ appendLinePathElement({
138
+ svgRootElement: g.node(),
139
+ height,
140
+ width,
141
+ color,
142
+ dashStyle: get(series, 'dashStyle'),
143
+ lineWidth: get(series, 'lineWidth'),
144
+ });
145
+ return colorSymbol.node();
146
+ }
147
+ return null;
148
+ }
@@ -1,3 +1,4 @@
1
+ import type { DashStyle } from '../constants';
1
2
  import type { ChartScaleLinear, ChartScaleTime } from '../hooks';
2
3
  import type { ChartAxisRangeSlider } from '../types';
3
4
  export declare function getInitialRangeSliderState(args: {
@@ -7,3 +8,13 @@ export declare function getInitialRangeSliderState(args: {
7
8
  min: number;
8
9
  max: number;
9
10
  };
11
+ export declare function appendLinePathElement({ svgRootElement, height, width, x, lineWidth, dashStyle, className, color, }: {
12
+ svgRootElement: SVGGElement | null;
13
+ height: number;
14
+ width: number;
15
+ x?: number;
16
+ lineWidth?: number;
17
+ dashStyle?: DashStyle;
18
+ className?: string;
19
+ color?: string;
20
+ }): import("d3-selection").Selection<SVGGElement | null, unknown, null, undefined>;
@@ -1,5 +1,7 @@
1
1
  import { duration } from '@gravity-ui/date-utils';
2
- import { isTimeScale } from '../utils';
2
+ import { line as lineGenerator } from 'd3';
3
+ import { select } from 'd3-selection';
4
+ import { getLineDashArray, isTimeScale } from '../utils';
3
5
  export function getInitialRangeSliderState(args) {
4
6
  const { defaultRange, xScale } = args;
5
7
  let minRange;
@@ -32,3 +34,25 @@ export function getInitialRangeSliderState(args) {
32
34
  }
33
35
  return { min: minRange, max: maxRange };
34
36
  }
37
+ const legendSymbolGenerator = lineGenerator()
38
+ .x((d) => d.x)
39
+ .y((d) => d.y);
40
+ export function appendLinePathElement({ svgRootElement, height, width, x = 0, lineWidth = 1, dashStyle, className, color, }) {
41
+ const rootELementSelection = select(svgRootElement);
42
+ const y = height / 2;
43
+ const points = [
44
+ { x, y },
45
+ { x: x + width, y },
46
+ ];
47
+ const pathElement = rootELementSelection
48
+ .append('path')
49
+ .attr('d', legendSymbolGenerator(points))
50
+ .attr('fill', 'none')
51
+ .attr('stroke-width', lineWidth)
52
+ .attr('stroke', color !== null && color !== void 0 ? color : '')
53
+ .attr('class', className !== null && className !== void 0 ? className : null);
54
+ if (dashStyle) {
55
+ pathElement.attr('stroke-dasharray', getLineDashArray(dashStyle, lineWidth));
56
+ }
57
+ return rootELementSelection;
58
+ }
@@ -13,6 +13,7 @@ export type PreparedAxisPlotBand = Required<AxisPlotBand> & {
13
13
  text: string;
14
14
  style: BaseTextStyle;
15
15
  padding: number;
16
+ qa?: string;
16
17
  };
17
18
  };
18
19
  type PreparedAxisCrosshair = Required<AxisCrosshair>;
@@ -27,6 +28,7 @@ export type PreparedAxisPlotLine = {
27
28
  text: string;
28
29
  style: BaseTextStyle;
29
30
  padding: number;
31
+ qa?: string;
30
32
  };
31
33
  };
32
34
  export type PreparedRangeSlider = DeepRequired<Omit<ChartAxisRangeSlider, 'defaultRange'>> & {
@@ -7,4 +7,5 @@ export declare function prepareAxisPlotLabel(d: AxisPlot): {
7
7
  fontColor: string;
8
8
  };
9
9
  padding: number;
10
+ qa: string | undefined;
10
11
  };
@@ -1,9 +1,10 @@
1
1
  import { DEFAULT_AXIS_LABEL_FONT_SIZE } from '../../constants';
2
2
  export function prepareAxisPlotLabel(d) {
3
- var _a, _b, _c, _d, _e;
3
+ var _a, _b, _c, _d, _e, _f;
4
4
  return {
5
5
  text: (_b = (_a = d.label) === null || _a === void 0 ? void 0 : _a.text) !== null && _b !== void 0 ? _b : '',
6
6
  style: Object.assign({ fontSize: DEFAULT_AXIS_LABEL_FONT_SIZE, fontColor: 'var(--g-color-text-secondary)' }, (_c = d.label) === null || _c === void 0 ? void 0 : _c.style),
7
7
  padding: (_e = (_d = d.label) === null || _d === void 0 ? void 0 : _d.padding) !== null && _e !== void 0 ? _e : 5,
8
+ qa: (_f = d.label) === null || _f === void 0 ? void 0 : _f.qa,
8
9
  };
9
10
  }
@@ -14,7 +14,7 @@ export const getPreparedTitle = ({ title, }) => {
14
14
  ? getHorizontalSvgTextHeight({ text: titleText, style: titleStyle }) + TITLE_PADDINGS
15
15
  : 0;
16
16
  const preparedTitle = titleText
17
- ? { text: titleText, style: titleStyle, height: titleHeight }
17
+ ? { text: titleText, style: titleStyle, height: titleHeight, qa: title === null || title === void 0 ? void 0 : title.qa }
18
18
  : undefined;
19
19
  return preparedTitle;
20
20
  };
@@ -194,6 +194,9 @@ export interface AxisPlot {
194
194
  * @default 5
195
195
  */
196
196
  padding?: number;
197
+ /** Can be used for the UI automated test.
198
+ * It is assigned as a data-qa attribute to an element. */
199
+ qa?: string;
197
200
  };
198
201
  }
199
202
  export interface AxisPlotLine extends AxisPlot {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gravity-ui/charts",
3
- "version": "1.30.0",
3
+ "version": "1.31.0",
4
4
  "description": "A flexible JavaScript library for data visualization and chart rendering using React",
5
5
  "license": "MIT",
6
6
  "main": "dist/cjs/index.js",