@gravity-ui/charts 1.26.0 → 1.27.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 (113) hide show
  1. package/dist/cjs/components/AxisX/AxisX.d.ts +5 -20
  2. package/dist/cjs/components/AxisX/AxisX.js +167 -212
  3. package/dist/cjs/components/AxisX/prepare-axis-data.d.ts +10 -0
  4. package/dist/cjs/components/AxisX/prepare-axis-data.js +267 -0
  5. package/dist/cjs/components/AxisX/styles.css +7 -10
  6. package/dist/cjs/components/AxisX/types.d.ts +85 -0
  7. package/dist/cjs/components/AxisX/types.js +1 -0
  8. package/dist/cjs/components/AxisY/AxisY.js +9 -3
  9. package/dist/cjs/components/AxisY/prepare-axis-data.d.ts +1 -1
  10. package/dist/cjs/components/AxisY/prepare-axis-data.js +17 -81
  11. package/dist/cjs/components/AxisY/prepare-axis-title.d.ts +16 -0
  12. package/dist/cjs/components/AxisY/prepare-axis-title.js +149 -0
  13. package/dist/cjs/components/AxisY/types.d.ts +16 -4
  14. package/dist/cjs/components/AxisY/utils.js +1 -8
  15. package/dist/cjs/components/ChartInner/index.js +62 -50
  16. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +7 -7
  17. package/dist/cjs/components/ChartInner/useChartInnerProps.js +15 -20
  18. package/dist/cjs/components/ChartInner/useChartInnerState.d.ts +3 -2
  19. package/dist/cjs/components/ChartInner/useChartInnerState.js +23 -4
  20. package/dist/cjs/components/Legend/index.js +21 -14
  21. package/dist/cjs/components/RangeSlider/index.d.ts +5 -3
  22. package/dist/cjs/components/RangeSlider/index.js +36 -4
  23. package/dist/cjs/components/Title/index.js +2 -2
  24. package/dist/cjs/components/utils.d.ts +9 -0
  25. package/dist/cjs/components/utils.js +34 -0
  26. package/dist/cjs/constants/defaults/axis.js +1 -1
  27. package/dist/cjs/hooks/index.d.ts +0 -1
  28. package/dist/cjs/hooks/index.js +0 -1
  29. package/dist/cjs/hooks/useAxis/index.js +10 -0
  30. package/dist/cjs/hooks/useAxis/types.d.ts +3 -0
  31. package/dist/cjs/hooks/useAxis/x-axis.d.ts +2 -1
  32. package/dist/cjs/hooks/useAxis/x-axis.js +36 -28
  33. package/dist/cjs/hooks/useAxis/y-axis.js +38 -11
  34. package/dist/cjs/hooks/useAxisScales/index.d.ts +4 -1
  35. package/dist/cjs/hooks/useAxisScales/index.js +8 -7
  36. package/dist/cjs/hooks/useAxisScales/utils.d.ts +6 -0
  37. package/dist/cjs/hooks/useAxisScales/utils.js +17 -0
  38. package/dist/cjs/hooks/useChartDimensions/utils.js +7 -1
  39. package/dist/cjs/hooks/useNormalizedOriginalData/index.d.ts +1 -7
  40. package/dist/cjs/hooks/useRangeSlider/index.js +1 -2
  41. package/dist/cjs/hooks/useRangeSlider/types.d.ts +1 -3
  42. package/dist/cjs/hooks/useZoom/index.d.ts +1 -0
  43. package/dist/cjs/hooks/useZoom/index.js +12 -2
  44. package/dist/cjs/types/chart/axis.d.ts +45 -17
  45. package/dist/cjs/types/chart/title.d.ts +3 -0
  46. package/dist/{esm/utils/chart/axis.d.ts → cjs/utils/chart/axis/common.d.ts} +4 -16
  47. package/dist/{esm/utils/chart/axis.js → cjs/utils/chart/axis/common.js} +7 -40
  48. package/dist/cjs/utils/chart/axis/x-axis.d.ts +12 -0
  49. package/dist/cjs/utils/chart/axis/x-axis.js +78 -0
  50. package/dist/cjs/utils/chart/axis-generators/bottom.js +1 -1
  51. package/dist/cjs/utils/chart/index.d.ts +1 -1
  52. package/dist/cjs/utils/chart/index.js +1 -1
  53. package/dist/cjs/utils/chart/text.d.ts +0 -6
  54. package/dist/cjs/utils/chart/text.js +7 -19
  55. package/dist/esm/components/AxisX/AxisX.d.ts +5 -20
  56. package/dist/esm/components/AxisX/AxisX.js +167 -212
  57. package/dist/esm/components/AxisX/prepare-axis-data.d.ts +10 -0
  58. package/dist/esm/components/AxisX/prepare-axis-data.js +267 -0
  59. package/dist/esm/components/AxisX/styles.css +7 -10
  60. package/dist/esm/components/AxisX/types.d.ts +85 -0
  61. package/dist/esm/components/AxisX/types.js +1 -0
  62. package/dist/esm/components/AxisY/AxisY.js +9 -3
  63. package/dist/esm/components/AxisY/prepare-axis-data.d.ts +1 -1
  64. package/dist/esm/components/AxisY/prepare-axis-data.js +17 -81
  65. package/dist/esm/components/AxisY/prepare-axis-title.d.ts +16 -0
  66. package/dist/esm/components/AxisY/prepare-axis-title.js +149 -0
  67. package/dist/esm/components/AxisY/types.d.ts +16 -4
  68. package/dist/esm/components/AxisY/utils.js +1 -8
  69. package/dist/esm/components/ChartInner/index.js +62 -50
  70. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +7 -7
  71. package/dist/esm/components/ChartInner/useChartInnerProps.js +15 -20
  72. package/dist/esm/components/ChartInner/useChartInnerState.d.ts +3 -2
  73. package/dist/esm/components/ChartInner/useChartInnerState.js +23 -4
  74. package/dist/esm/components/Legend/index.js +21 -14
  75. package/dist/esm/components/RangeSlider/index.d.ts +5 -3
  76. package/dist/esm/components/RangeSlider/index.js +36 -4
  77. package/dist/esm/components/Title/index.js +2 -2
  78. package/dist/esm/components/utils.d.ts +9 -0
  79. package/dist/esm/components/utils.js +34 -0
  80. package/dist/esm/constants/defaults/axis.js +1 -1
  81. package/dist/esm/hooks/index.d.ts +0 -1
  82. package/dist/esm/hooks/index.js +0 -1
  83. package/dist/esm/hooks/useAxis/index.js +10 -0
  84. package/dist/esm/hooks/useAxis/types.d.ts +3 -0
  85. package/dist/esm/hooks/useAxis/x-axis.d.ts +2 -1
  86. package/dist/esm/hooks/useAxis/x-axis.js +36 -28
  87. package/dist/esm/hooks/useAxis/y-axis.js +38 -11
  88. package/dist/esm/hooks/useAxisScales/index.d.ts +4 -1
  89. package/dist/esm/hooks/useAxisScales/index.js +8 -7
  90. package/dist/esm/hooks/useAxisScales/utils.d.ts +6 -0
  91. package/dist/esm/hooks/useAxisScales/utils.js +17 -0
  92. package/dist/esm/hooks/useChartDimensions/utils.js +7 -1
  93. package/dist/esm/hooks/useNormalizedOriginalData/index.d.ts +1 -7
  94. package/dist/esm/hooks/useRangeSlider/index.js +1 -2
  95. package/dist/esm/hooks/useRangeSlider/types.d.ts +1 -3
  96. package/dist/esm/hooks/useZoom/index.d.ts +1 -0
  97. package/dist/esm/hooks/useZoom/index.js +12 -2
  98. package/dist/esm/types/chart/axis.d.ts +45 -17
  99. package/dist/esm/types/chart/title.d.ts +3 -0
  100. package/dist/{cjs/utils/chart/axis.d.ts → esm/utils/chart/axis/common.d.ts} +4 -16
  101. package/dist/{cjs/utils/chart/axis.js → esm/utils/chart/axis/common.js} +7 -40
  102. package/dist/esm/utils/chart/axis/x-axis.d.ts +12 -0
  103. package/dist/esm/utils/chart/axis/x-axis.js +78 -0
  104. package/dist/esm/utils/chart/axis-generators/bottom.js +1 -1
  105. package/dist/esm/utils/chart/index.d.ts +1 -1
  106. package/dist/esm/utils/chart/index.js +1 -1
  107. package/dist/esm/utils/chart/text.d.ts +0 -6
  108. package/dist/esm/utils/chart/text.js +7 -19
  109. package/package.json +1 -1
  110. package/dist/cjs/hooks/useChartOptions/index.d.ts +0 -16
  111. package/dist/cjs/hooks/useChartOptions/index.js +0 -21
  112. package/dist/esm/hooks/useChartOptions/index.d.ts +0 -16
  113. package/dist/esm/hooks/useChartOptions/index.js +0 -21
@@ -6,17 +6,15 @@ import { block, createGradientRect, getContinuesColorFn, getLabelsSize, getLineD
6
6
  import { axisBottom } from '../../utils/chart/axis-generators';
7
7
  import './styles.css';
8
8
  const b = block('legend');
9
- const getLegendItemPosition = (args) => {
9
+ const getLegendItemLeftPosition = (args) => {
10
10
  const { align, width, contentWidth } = args;
11
11
  if (align === 'right') {
12
- return { left: width - contentWidth };
12
+ return width - contentWidth;
13
13
  }
14
- else if (align === 'left') {
15
- return { left: 0 };
16
- }
17
- else {
18
- return { left: width / 2 - contentWidth / 2 };
14
+ if (align === 'left') {
15
+ return 0;
19
16
  }
17
+ return width / 2 - contentWidth / 2;
20
18
  };
21
19
  const getLegendPosition = (args) => {
22
20
  const { offsetLeft, offsetTop, contentWidth, width } = args;
@@ -256,12 +254,11 @@ export const Legend = (props) => {
256
254
  let left = 0;
257
255
  switch (legend.justifyContent) {
258
256
  case 'center': {
259
- const legendLinePostion = getLegendItemPosition({
257
+ left = getLegendItemLeftPosition({
260
258
  align: legend.align,
261
259
  width: config.maxWidth,
262
260
  contentWidth,
263
261
  });
264
- left = legendLinePostion.left;
265
262
  legendWidth = config.maxWidth;
266
263
  break;
267
264
  }
@@ -296,11 +293,21 @@ export const Legend = (props) => {
296
293
  legendTop = top;
297
294
  }
298
295
  else {
299
- const { left } = getLegendItemPosition({
300
- align: legend.align,
301
- width: config.maxWidth,
302
- contentWidth: legend.width,
303
- });
296
+ let left = 0;
297
+ switch (legend.align) {
298
+ case 'right': {
299
+ left = config.offset.left + config.maxWidth - legend.width;
300
+ break;
301
+ }
302
+ case 'left': {
303
+ left = config.offset.left;
304
+ break;
305
+ }
306
+ case 'center': {
307
+ left = config.offset.left + config.maxWidth / 2 - legend.width / 2;
308
+ break;
309
+ }
310
+ }
304
311
  const { top } = getLegendPosition({
305
312
  width: config.maxWidth,
306
313
  contentWidth: legendWidth,
@@ -1,6 +1,8 @@
1
1
  import React from 'react';
2
2
  import type { RangeSliderProps } from '../../hooks';
3
3
  import './styles.css';
4
- declare function RangeSliderComponent(props: RangeSliderProps): React.JSX.Element;
5
- export declare const RangeSlider: React.MemoExoticComponent<typeof RangeSliderComponent>;
6
- export {};
4
+ export interface RangeSliderHandle {
5
+ getDomain: () => [number, number] | undefined;
6
+ resetState: () => void;
7
+ }
8
+ export declare const RangeSlider: React.MemoExoticComponent<React.ForwardRefExoticComponent<RangeSliderProps & React.RefAttributes<RangeSliderHandle>>>;
@@ -1,14 +1,46 @@
1
1
  import React from 'react';
2
2
  import { useUniqId } from '@gravity-ui/uikit';
3
3
  import { useBrush, useRangeSlider } from '../../hooks';
4
- import { block } from '../../utils';
4
+ import { block, isBandScale } from '../../utils';
5
+ import { getInitialRangeSliderState } from '../utils';
5
6
  import { getFramedPath } from './utils';
6
7
  import './styles.css';
7
8
  const b = block('range-slider');
8
- function RangeSliderComponent(props) {
9
+ function RangeSliderComponent(props, forwardedRef) {
10
+ const { onUpdate } = props;
9
11
  const clipPathId = useUniqId();
10
- const { brush, height, onBrushEnd, onOverlayClick, offsetLeft, offsetTop, preparedXAxis, preparedYAxis, selection, shapes, width, } = useRangeSlider(Object.assign(Object.assign({}, props), { clipPathId }));
12
+ const { brush, defaultRange, height, onBrushEnd, onOverlayClick, offsetLeft, offsetTop, preparedXAxis, preparedYAxis, selection, shapes, width, xScale, } = useRangeSlider(Object.assign(Object.assign({}, props), { clipPathId }));
11
13
  const ref = React.useRef(null);
14
+ /*
15
+ * We use useImperativeHandle to expose methods to the parent component.
16
+ *
17
+ * This is necessary due to an architectural decision: all calculations for all data series
18
+ * (without zoom) are performed only within the RangeSlider component. This approach
19
+ * was chosen to avoid degrading performance for charts that don't use the slider —
20
+ * in that case, the extra computations simply don't happen.
21
+ *
22
+ * Methods:
23
+ * - getDomain: returns the current X-axis domain (for synchronization with zoom)
24
+ * - resetState: resets the slider state to its initial value (these calculations
25
+ * are only possible within the component since it has access to the prepared series data)
26
+ */
27
+ React.useImperativeHandle(forwardedRef, () => ({
28
+ getDomain: () => {
29
+ if (!xScale || isBandScale(xScale)) {
30
+ return undefined;
31
+ }
32
+ return xScale.domain().map(Number);
33
+ },
34
+ resetState: () => {
35
+ if (xScale && !isBandScale(xScale)) {
36
+ const initialRangeSliderState = getInitialRangeSliderState({
37
+ xScale,
38
+ defaultRange,
39
+ });
40
+ onUpdate(initialRangeSliderState, false);
41
+ }
42
+ },
43
+ }), [defaultRange, onUpdate, xScale]);
12
44
  const areas = React.useMemo(() => {
13
45
  if (!preparedXAxis || !(preparedYAxis === null || preparedYAxis === void 0 ? void 0 : preparedYAxis.length)) {
14
46
  return [];
@@ -38,4 +70,4 @@ function RangeSliderComponent(props) {
38
70
  React.createElement("g", { className: b('shapes') }, shapes),
39
71
  React.createElement("rect", { height: height, className: b('clickable-overlay'), onClick: onOverlayClick, width: width, x: 0, y: 0 })));
40
72
  }
41
- export const RangeSlider = React.memo(RangeSliderComponent);
73
+ export const RangeSlider = React.memo(React.forwardRef(RangeSliderComponent));
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
2
  export const Title = (props) => {
3
- const { chartWidth, text, height, style } = props;
3
+ const { chartWidth, text, height, style, qa } = props;
4
4
  return (React.createElement("text", { dx: chartWidth / 2, dy: height / 2, dominantBaseline: "middle", textAnchor: "middle", style: {
5
5
  fill: style === null || style === void 0 ? void 0 : style.fontColor,
6
6
  fontSize: style === null || style === void 0 ? void 0 : style.fontSize,
7
7
  fontWeight: style === null || style === void 0 ? void 0 : style.fontWeight,
8
8
  lineHeight: `${height}px`,
9
- } },
9
+ }, "data-qa": qa },
10
10
  React.createElement("tspan", { dangerouslySetInnerHTML: { __html: text } })));
11
11
  };
@@ -0,0 +1,9 @@
1
+ import type { ChartScaleLinear, ChartScaleTime } from '../hooks';
2
+ import type { ChartAxisRangeSlider } from '../types';
3
+ export declare function getInitialRangeSliderState(args: {
4
+ xScale: ChartScaleLinear | ChartScaleTime;
5
+ defaultRange?: ChartAxisRangeSlider['defaultRange'];
6
+ }): {
7
+ min: number;
8
+ max: number;
9
+ };
@@ -0,0 +1,34 @@
1
+ import { duration } from '@gravity-ui/date-utils';
2
+ import { isTimeScale } from '../utils';
3
+ export function getInitialRangeSliderState(args) {
4
+ const { defaultRange, xScale } = args;
5
+ let minRange;
6
+ let maxRange;
7
+ if (isTimeScale(xScale)) {
8
+ const [minDomainMs, maxDomainMs] = xScale.domain().map(Number);
9
+ minRange = minDomainMs;
10
+ maxRange = maxDomainMs;
11
+ try {
12
+ if (defaultRange === null || defaultRange === void 0 ? void 0 : defaultRange.size) {
13
+ const durationMs = duration(defaultRange.size).asMilliseconds();
14
+ const minDefaultRangeMs = maxDomainMs - durationMs;
15
+ if (minDefaultRangeMs < maxDomainMs) {
16
+ minRange = minDefaultRangeMs;
17
+ }
18
+ }
19
+ }
20
+ catch (_a) { }
21
+ }
22
+ else {
23
+ const [minDomain, maxDomain] = xScale.domain();
24
+ minRange = minDomain;
25
+ maxRange = maxDomain;
26
+ if (typeof (defaultRange === null || defaultRange === void 0 ? void 0 : defaultRange.size) === 'number') {
27
+ const minDefaultRange = maxDomain - defaultRange.size;
28
+ if (minDefaultRange < maxDomain) {
29
+ minRange = minDefaultRange;
30
+ }
31
+ }
32
+ }
33
+ return { min: minRange, max: maxRange };
34
+ }
@@ -1,7 +1,7 @@
1
1
  import { DASH_STYLE } from '../line-styles';
2
2
  export const axisLabelsDefaults = {
3
3
  margin: 10,
4
- padding: 10,
4
+ padding: 4,
5
5
  fontSize: 11,
6
6
  maxWidth: 80,
7
7
  };
@@ -4,7 +4,6 @@ export * from './useAxisScales';
4
4
  export * from './useBrush';
5
5
  export * from './useBrush/types';
6
6
  export * from './useChartDimensions';
7
- export * from './useChartOptions';
8
7
  export * from './useChartOptions/types';
9
8
  export * from './useCrosshair';
10
9
  export * from './useNormalizedOriginalData';
@@ -4,7 +4,6 @@ export * from './useAxisScales';
4
4
  export * from './useBrush';
5
5
  export * from './useBrush/types';
6
6
  export * from './useChartDimensions';
7
- export * from './useChartOptions';
8
7
  export * from './useChartOptions/types';
9
8
  export * from './useCrosshair';
10
9
  export * from './useNormalizedOriginalData';
@@ -14,9 +14,19 @@ export function useAxis(props) {
14
14
  (async function () {
15
15
  const currentRun = axesStateRunRef.current;
16
16
  const seriesData = preparedSeries.filter((s) => s.visible);
17
+ const estimatedPreparedYAxis = await getPreparedYAxis({
18
+ height,
19
+ boundsHeight: height,
20
+ width,
21
+ seriesData,
22
+ yAxis,
23
+ });
24
+ const axesWidth = estimatedPreparedYAxis.reduce((acc, a) => acc + a.title.height + a.title.margin + a.labels.margin + a.labels.width, 0);
25
+ const estimatedBoundsWidth = width - (axesWidth + preparedChart.margin.left + preparedChart.margin.right);
17
26
  const preparedXAxis = await getPreparedXAxis({
18
27
  xAxis,
19
28
  width,
29
+ boundsWidth: estimatedBoundsWidth,
20
30
  seriesData,
21
31
  });
22
32
  let estimatedBoundsHeight = boundsHeight !== null && boundsHeight !== void 0 ? boundsHeight : height;
@@ -43,6 +43,9 @@ type PreparedBaseAxis = Omit<ChartAxis, 'type' | 'labels' | 'plotLines' | 'plotB
43
43
  style: BaseTextStyle;
44
44
  align: ChartAxisTitleAlignment;
45
45
  maxRowCount: number;
46
+ rotation: number;
47
+ maxWidth: number;
48
+ html: boolean;
46
49
  };
47
50
  min?: number;
48
51
  grid: {
@@ -1,7 +1,8 @@
1
1
  import type { ChartSeries, ChartXAxis } from '../../types';
2
2
  import type { PreparedXAxis } from './types';
3
- export declare const getPreparedXAxis: ({ xAxis, seriesData, width, }: {
3
+ export declare const getPreparedXAxis: ({ xAxis, seriesData, width, boundsWidth, }: {
4
4
  xAxis?: ChartXAxis;
5
5
  seriesData: ChartSeries[];
6
6
  width: number;
7
+ boundsWidth: number;
7
8
  }) => Promise<PreparedXAxis | null>;
@@ -1,6 +1,7 @@
1
1
  import get from 'lodash/get';
2
2
  import { DASH_STYLE, DEFAULT_AXIS_LABEL_FONT_SIZE, SERIES_TYPE, axisCrosshairDefaults, axisLabelsDefaults, xAxisTitleDefaults, } from '../../constants';
3
- import { calculateCos, calculateNumericProperty, formatAxisTickLabel, getAxisItems, getClosestPointsRange, getDefaultDateFormat, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getMaxTickCount, getTicksCount, hasOverlappingLabels, isAxisRelatedSeries, wrapText, } from '../../utils';
3
+ import { calculateCos, calculateNumericProperty, formatAxisTickLabel, getDefaultDateFormat, getHorizontalHtmlTextHeight, getHorizontalSvgTextHeight, getLabelsSize, getMinSpaceBetween, getTextSizeFn, isAxisRelatedSeries, wrapText, } from '../../utils';
4
+ import { getXAxisTickValues } from '../../utils/chart/axis/x-axis';
4
5
  import { createXScale } from '../useAxisScales';
5
6
  import { getPreparedRangeSlider } from './range-slider';
6
7
  import { prepareAxisPlotLabel } from './utils';
@@ -11,31 +12,31 @@ async function setLabelSettings({ axis, seriesData, width, autoRotation = true,
11
12
  axis.labels.rotation = 0;
12
13
  return;
13
14
  }
14
- const tickCount = getTicksCount({ axis, range: width });
15
- const ticks = getAxisItems({
16
- scale: scale,
17
- count: tickCount,
18
- maxCount: getMaxTickCount({ width, axis }),
19
- });
20
- const step = getClosestPointsRange(axis, ticks);
15
+ const getTextSize = getTextSizeFn({ style: axis.labels.style });
16
+ const labelLineHeight = (await getTextSize('Tmp')).height;
17
+ const tickValues = getXAxisTickValues({ axis, scale, labelLineHeight });
18
+ const tickStep = getMinSpaceBetween(tickValues, (d) => Number(d.value));
21
19
  if (axis.type === 'datetime' && !axis.labels.dateFormat) {
22
- axis.labels.dateFormat = getDefaultDateFormat(step);
20
+ axis.labels.dateFormat = getDefaultDateFormat(tickStep);
23
21
  }
24
- const labels = ticks.map((value) => {
25
- return formatAxisTickLabel({
26
- axis,
27
- value,
28
- step,
29
- });
30
- });
31
- const overlapping = axis.labels.html
32
- ? false
33
- : hasOverlappingLabels({
34
- width,
35
- labels,
36
- padding: axis.labels.padding,
37
- style: axis.labels.style,
38
- });
22
+ const labels = tickValues.map((tick) => formatAxisTickLabel({
23
+ axis,
24
+ value: tick.value,
25
+ step: tickStep,
26
+ }));
27
+ const labelMaxWidth = tickValues.length > 1
28
+ ? getMinSpaceBetween(tickValues, (d) => d.x) - axis.labels.padding * 2
29
+ : width;
30
+ const hasOverlappingLabels = async () => {
31
+ for (let i = 0; i < labels.length; i++) {
32
+ const size = await getTextSize(labels[i]);
33
+ if (size.width > labelMaxWidth) {
34
+ return true;
35
+ }
36
+ }
37
+ return false;
38
+ };
39
+ const overlapping = axis.labels.html ? false : await hasOverlappingLabels();
39
40
  const defaultRotation = overlapping && autoRotation ? -45 : 0;
40
41
  const rotation = axis.labels.html ? 0 : axis.labels.rotation || defaultRotation;
41
42
  const labelsHeight = rotation || axis.labels.html
@@ -56,7 +57,7 @@ function getMaxPaddingBySeries({ series }) {
56
57
  }
57
58
  return 0.01;
58
59
  }
59
- export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
60
+ export const getPreparedXAxis = async ({ xAxis, seriesData, width, boundsWidth, }) => {
60
61
  var _a, _b, _c, _d, _e;
61
62
  const hasAxisRelatedSeries = seriesData.some(isAxisRelatedSeries);
62
63
  if (!hasAxisRelatedSeries) {
@@ -82,6 +83,10 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
82
83
  ? getHorizontalHtmlTextHeight({ text: 'Tmp', style: labelsStyle })
83
84
  : getHorizontalSvgTextHeight({ text: 'Tmp', style: labelsStyle });
84
85
  const shouldHideGrid = seriesData.some((s) => s.type === SERIES_TYPE.Heatmap);
86
+ const preparedRangeSlider = getPreparedRangeSlider({ xAxis });
87
+ const maxPadding = preparedRangeSlider.enabled
88
+ ? 0
89
+ : get(xAxis, 'maxPadding', getMaxPaddingBySeries({ series: seriesData }));
85
90
  const preparedXAxis = {
86
91
  type: get(xAxis, 'type', 'linear'),
87
92
  labels: {
@@ -109,10 +114,13 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
109
114
  width: titleSize.maxWidth,
110
115
  align: get(xAxis, 'title.align', xAxisTitleDefaults.align),
111
116
  maxRowCount: get(xAxis, 'title.maxRowCount', xAxisTitleDefaults.maxRowCount),
117
+ rotation: 0,
118
+ maxWidth: Infinity,
119
+ html: false,
112
120
  },
113
121
  min: get(xAxis, 'min'),
114
122
  max: get(xAxis, 'max'),
115
- maxPadding: get(xAxis, 'maxPadding', getMaxPaddingBySeries({ series: seriesData })),
123
+ maxPadding,
116
124
  grid: {
117
125
  enabled: shouldHideGrid ? false : get(xAxis, 'grid.enabled', true),
118
126
  },
@@ -154,12 +162,12 @@ export const getPreparedXAxis = async ({ xAxis, seriesData, width, }) => {
154
162
  },
155
163
  visible: get(xAxis, 'visible', true),
156
164
  order: xAxis === null || xAxis === void 0 ? void 0 : xAxis.order,
157
- rangeSlider: getPreparedRangeSlider({ xAxis }),
165
+ rangeSlider: preparedRangeSlider,
158
166
  };
159
167
  await setLabelSettings({
160
168
  axis: preparedXAxis,
161
169
  seriesData,
162
- width,
170
+ width: boundsWidth,
163
171
  autoRotation: (_e = xAxis === null || xAxis === void 0 ? void 0 : xAxis.labels) === null || _e === void 0 ? void 0 : _e.autoRotation,
164
172
  });
165
173
  return preparedXAxis;
@@ -43,6 +43,13 @@ function getMaxPaddingBySeries({ series }) {
43
43
  }
44
44
  return 0.05;
45
45
  }
46
+ const VALID_Y_AXIS_TITLE_ROTATION = [0, -90, 90];
47
+ function getAxisTitleRotation(value, axisPosition) {
48
+ if (typeof value !== 'undefined' && VALID_Y_AXIS_TITLE_ROTATION.includes(value)) {
49
+ return value;
50
+ }
51
+ return axisPosition === 'left' ? -90 : 90;
52
+ }
46
53
  export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxis, }) => {
47
54
  const axisByPlot = [];
48
55
  const axisItems = yAxis || [{}];
@@ -50,8 +57,10 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxi
50
57
  if (!hasAxisRelatedSeries) {
51
58
  return Promise.resolve([]);
52
59
  }
53
- return Promise.all(axisItems.map(async (axisItem, axisIndex) => {
54
- var _a, _b, _c, _d, _e, _f, _g;
60
+ return Promise.all(
61
+ // eslint-disable-next-line complexity
62
+ axisItems.map(async (axisItem, axisIndex) => {
63
+ var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q;
55
64
  const plotIndex = get(axisItem, 'plotIndex', 0);
56
65
  const firstPlotAxis = !axisByPlot[plotIndex];
57
66
  if (firstPlotAxis) {
@@ -59,6 +68,7 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxi
59
68
  }
60
69
  axisByPlot[plotIndex].push(axisItem);
61
70
  const defaultAxisPosition = firstPlotAxis ? 'left' : 'right';
71
+ const axisPosition = get(axisItem, 'position', defaultAxisPosition);
62
72
  const axisSeriesData = seriesData.filter((s) => get(s, 'yAxis', 0) === axisIndex);
63
73
  const labelsEnabled = get(axisItem, 'labels.enabled', true);
64
74
  const labelsStyle = {
@@ -76,7 +86,21 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxi
76
86
  style: titleStyle,
77
87
  width: height,
78
88
  })).slice(0, titleMaxRowsCount);
79
- const titleSize = await getLabelsSize({ labels: [titleText], style: titleStyle });
89
+ const titleRotation = getAxisTitleRotation((_a = axisItem.title) === null || _a === void 0 ? void 0 : _a.rotation, axisPosition);
90
+ const titleMaxWidth = titleRotation === 0
91
+ ? calculateNumericProperty({
92
+ value: (_c = (_b = axisItem.title) === null || _b === void 0 ? void 0 : _b.maxWidth) !== null && _c !== void 0 ? _c : '20%',
93
+ base: width,
94
+ })
95
+ : calculateNumericProperty({
96
+ value: (_e = (_d = axisItem.title) === null || _d === void 0 ? void 0 : _d.maxWidth) !== null && _e !== void 0 ? _e : '100%',
97
+ base: height,
98
+ });
99
+ const titleSize = await getLabelsSize({
100
+ labels: [titleText],
101
+ style: titleStyle,
102
+ html: (_f = axisItem.title) === null || _f === void 0 ? void 0 : _f.html,
103
+ });
80
104
  const axisType = get(axisItem, 'type', DEFAULT_AXIS_TYPE);
81
105
  const shouldHideGrid = axisItem.visible === false ||
82
106
  axisSeriesData.some((s) => s.type === SERIES_TYPE.Heatmap);
@@ -95,7 +119,7 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxi
95
119
  width: 0,
96
120
  height: 0,
97
121
  lineHeight: labelsLineHeight,
98
- maxWidth: (_b = calculateNumericProperty({ base: width, value: (_a = axisItem.labels) === null || _a === void 0 ? void 0 : _a.maxWidth })) !== null && _b !== void 0 ? _b : axisLabelsDefaults.maxWidth,
122
+ maxWidth: (_h = calculateNumericProperty({ base: width, value: (_g = axisItem.labels) === null || _g === void 0 ? void 0 : _g.maxWidth })) !== null && _h !== void 0 ? _h : axisLabelsDefaults.maxWidth,
99
123
  html: labelsHtml,
100
124
  },
101
125
  lineColor: get(axisItem, 'lineColor'),
@@ -105,29 +129,32 @@ export const getPreparedYAxis = ({ height, boundsHeight, width, seriesData, yAxi
105
129
  text: titleText,
106
130
  margin: get(axisItem, 'title.margin', yAxisTitleDefaults.margin),
107
131
  style: titleStyle,
108
- width: titleSize.maxWidth,
132
+ width: Math.min(titleSize.maxWidth, titleMaxWidth !== null && titleMaxWidth !== void 0 ? titleMaxWidth : Infinity),
109
133
  height: titleSize.maxHeight * estimatedTitleRows.length,
110
134
  align: get(axisItem, 'title.align', yAxisTitleDefaults.align),
111
135
  maxRowCount: titleMaxRowsCount,
136
+ html: (_k = (_j = axisItem.title) === null || _j === void 0 ? void 0 : _j.html) !== null && _k !== void 0 ? _k : false,
137
+ maxWidth: titleMaxWidth !== null && titleMaxWidth !== void 0 ? titleMaxWidth : Infinity,
138
+ rotation: titleRotation,
112
139
  },
113
- min: (_c = get(axisItem, 'min')) !== null && _c !== void 0 ? _c : getDefaultMinYAxisValue(axisSeriesData),
140
+ min: (_l = get(axisItem, 'min')) !== null && _l !== void 0 ? _l : getDefaultMinYAxisValue(axisSeriesData),
114
141
  max: get(axisItem, 'max'),
115
142
  maxPadding: get(axisItem, 'maxPadding', getMaxPaddingBySeries({ series: axisSeriesData })),
116
143
  grid: {
117
144
  enabled: shouldHideGrid
118
145
  ? false
119
146
  : get(axisItem, 'grid.enabled', firstPlotAxis ||
120
- (!firstPlotAxis && !((_d = axisByPlot[plotIndex][0].visible) !== null && _d !== void 0 ? _d : true))),
147
+ (!firstPlotAxis && !((_m = axisByPlot[plotIndex][0].visible) !== null && _m !== void 0 ? _m : true))),
121
148
  },
122
149
  ticks: {
123
- pixelInterval: ((_e = axisItem.ticks) === null || _e === void 0 ? void 0 : _e.interval)
150
+ pixelInterval: ((_o = axisItem.ticks) === null || _o === void 0 ? void 0 : _o.interval)
124
151
  ? calculateNumericProperty({
125
152
  base: height,
126
- value: (_f = axisItem.ticks) === null || _f === void 0 ? void 0 : _f.interval,
153
+ value: (_p = axisItem.ticks) === null || _p === void 0 ? void 0 : _p.interval,
127
154
  })
128
- : (_g = axisItem.ticks) === null || _g === void 0 ? void 0 : _g.pixelInterval,
155
+ : (_q = axisItem.ticks) === null || _q === void 0 ? void 0 : _q.pixelInterval,
129
156
  },
130
- position: get(axisItem, 'position', defaultAxisPosition),
157
+ position: axisPosition,
131
158
  plotIndex: get(axisItem, 'plotIndex', 0),
132
159
  plotLines: get(axisItem, 'plotLines', []).map((d) => ({
133
160
  value: get(d, 'value', 0),
@@ -1,7 +1,10 @@
1
1
  import type { ScaleBand, ScaleLinear, ScaleTime } from 'd3';
2
2
  import type { PreparedAxis, PreparedSeries, PreparedSplit, RangeSliderState, ZoomState } from '../../hooks';
3
3
  import type { ChartAxis, ChartSeries } from '../../types';
4
- export type ChartScale = ScaleLinear<number, number> | ScaleBand<string> | ScaleTime<number, number>;
4
+ type ChartScaleBand = ScaleBand<string>;
5
+ export type ChartScaleLinear = ScaleLinear<number, number>;
6
+ export type ChartScaleTime = ScaleTime<number, number>;
7
+ export type ChartScale = ChartScaleBand | ChartScaleLinear | ChartScaleTime;
5
8
  type Args = {
6
9
  boundsWidth: number;
7
10
  boundsHeight: number;
@@ -4,7 +4,7 @@ import get from 'lodash/get';
4
4
  import { DEFAULT_AXIS_TYPE, SERIES_TYPE } from '../../constants';
5
5
  import { CHART_SERIES_WITH_VOLUME_ON_Y_AXIS, getAxisCategories, getAxisHeight, getDataCategoryValue, getDefaultMaxXAxisValue, getDefaultMinXAxisValue, getDomainDataXBySeries, getDomainDataYBySeries, getOnlyVisibleSeries, isAxisRelatedSeries, isSeriesWithCategoryValues, } from '../../utils';
6
6
  import { getBandSize } from '../utils/get-band-size';
7
- import { checkIsPointDomain, getMinMaxPropsOrState, hasOnlyMarkerSeries } from './utils';
7
+ import { checkIsPointDomain, getMinMaxPropsOrState, getXMaxDomainResult, hasOnlyMarkerSeries, } from './utils';
8
8
  const X_AXIS_ZOOM_PADDING = 0.02;
9
9
  function validateArrayData(data) {
10
10
  let hasNumberAndNullValues;
@@ -242,7 +242,7 @@ export function createXScale(args) {
242
242
  order: axis.order,
243
243
  });
244
244
  }
245
- const maxPadding = rangeSliderState ? 0 : get(axis, 'maxPadding', 0);
245
+ const maxPadding = get(axis, 'maxPadding', 0);
246
246
  const xAxisMaxPadding = boundsWidth * maxPadding + calculateXAxisPadding(series);
247
247
  const range = getXScaleRange({
248
248
  boundsWidth,
@@ -350,11 +350,12 @@ export function createXScale(args) {
350
350
  !isPointDomain
351
351
  ? xMinPropsOrState
352
352
  : xMinTimestamp;
353
- const xMax = typeof xMaxPropsOrState === 'number' &&
354
- xMaxPropsOrState < xMaxTimestamp &&
355
- !isPointDomain
356
- ? xMaxPropsOrState
357
- : xMaxTimestamp;
353
+ const xMax = getXMaxDomainResult({
354
+ xMaxDomain: xMaxTimestamp,
355
+ xMaxProps: get(axis, 'max'),
356
+ xMaxRangeSlider: rangeSliderState === null || rangeSliderState === void 0 ? void 0 : rangeSliderState.max,
357
+ xMaxZoom: zoomStateX === null || zoomStateX === void 0 ? void 0 : zoomStateX[1],
358
+ });
358
359
  domain = [xMin, xMax];
359
360
  const scale = scaleUtc().domain(domain).range(range);
360
361
  let offsetMin = 0;
@@ -16,4 +16,10 @@ export declare function getMinMaxPropsOrState(args: {
16
16
  */
17
17
  export declare function checkIsPointDomain(domain: [number, number]): boolean;
18
18
  export declare function hasOnlyMarkerSeries(series: (PreparedSeries | ChartSeries)[]): boolean;
19
+ export declare function getXMaxDomainResult(args: {
20
+ xMaxDomain: number;
21
+ xMaxProps?: number;
22
+ xMaxRangeSlider?: number;
23
+ xMaxZoom?: number;
24
+ }): number;
19
25
  export {};
@@ -32,3 +32,20 @@ export function checkIsPointDomain(domain) {
32
32
  export function hasOnlyMarkerSeries(series) {
33
33
  return series.every((s) => MARKER_SERIES_TYPES.includes(s.type));
34
34
  }
35
+ export function getXMaxDomainResult(args) {
36
+ const { xMaxDomain, xMaxProps, xMaxRangeSlider, xMaxZoom } = args;
37
+ let xMaxDomainResult = xMaxDomain;
38
+ // When xMaxRangeSlider is provided, we use it directly without considering xMaxDomain.
39
+ // This is intentional: the range slider needs to display the chart's maxPadding area,
40
+ // which would be clipped if we constrained it to xMaxDomain.
41
+ if (typeof xMaxRangeSlider === 'number') {
42
+ xMaxDomainResult = xMaxRangeSlider;
43
+ }
44
+ else if (typeof xMaxZoom === 'number' && xMaxZoom < xMaxDomain) {
45
+ xMaxDomainResult = xMaxZoom;
46
+ }
47
+ else if (typeof xMaxProps === 'number' && xMaxProps < xMaxDomain) {
48
+ xMaxDomainResult = xMaxProps;
49
+ }
50
+ return xMaxDomainResult;
51
+ }
@@ -11,7 +11,13 @@ export function getYAxisWidth(axis) {
11
11
  }
12
12
  let result = 0;
13
13
  if (axis === null || axis === void 0 ? void 0 : axis.title.text) {
14
- result += axis.title.height + axis.title.margin;
14
+ result += axis.title.margin;
15
+ if (axis.title.rotation === 0) {
16
+ result += axis.title.width;
17
+ }
18
+ else {
19
+ result += axis.title.height;
20
+ }
15
21
  }
16
22
  if (axis === null || axis === void 0 ? void 0 : axis.labels.enabled) {
17
23
  result += axis.labels.margin + axis.labels.width;
@@ -14,13 +14,7 @@ export declare function useNormalizedOriginalData(props: UseOriginalDataProps):
14
14
  type?: import("../..").ChartAxisType;
15
15
  labels?: import("../..").ChartAxisLabels;
16
16
  lineColor?: string;
17
- title?: {
18
- text?: string;
19
- style?: Partial<import("../..").BaseTextStyle>;
20
- margin?: number;
21
- align?: import("../..").ChartAxisTitleAlignment;
22
- maxRowCount?: number;
23
- };
17
+ title?: import("../..").ChartAxisTitle;
24
18
  min?: number;
25
19
  max?: number;
26
20
  grid?: {
@@ -17,7 +17,7 @@ const CLIP_PATH_BY_SERIES_TYPE = {
17
17
  [SERIES_TYPE.Scatter]: true,
18
18
  };
19
19
  export function useRangeSlider(props) {
20
- const { boundsWidth, boundsOffsetLeft, clipPathId, height, htmlLayout, onUpdate, preparedChart, preparedLegend, preparedSeries, preparedSeriesOptions, preparedRangeSlider, rangeSliderState, width, xAxis, yAxis, zoomState, } = props;
20
+ const { boundsWidth, boundsOffsetLeft, clipPathId, height, htmlLayout, onUpdate, preparedChart, preparedLegend, preparedSeries, preparedSeriesOptions, preparedRangeSlider, rangeSliderState, width, xAxis, yAxis, } = props;
21
21
  const filteredPreparedSeries = React.useMemo(() => {
22
22
  return preparedSeries.filter((s) => {
23
23
  if ('rangeSlider' in s && !s.rangeSlider.visible) {
@@ -44,7 +44,6 @@ export function useRangeSlider(props) {
44
44
  split: EMPTY_PREPARED_SPLIT,
45
45
  xAxis: preparedXAxis,
46
46
  yAxis: preparedYAxis,
47
- zoomState,
48
47
  });
49
48
  const { shapes } = useShapes({
50
49
  boundsHeight: preparedRangeSlider.height,
@@ -4,7 +4,6 @@ import type { ChartScale } from '../useAxisScales';
4
4
  import type { BrushSelection, UseBrushProps } from '../useBrush/types';
5
5
  import type { PreparedChart } from '../useChartOptions/types';
6
6
  import type { PreparedLegend, PreparedSeries, PreparedSeriesOptions } from '../useSeries/types';
7
- import type { ZoomState } from '../useZoom/types';
8
7
  export type RangeSliderState = {
9
8
  max: number;
10
9
  min: number;
@@ -14,7 +13,7 @@ export interface RangeSliderProps {
14
13
  boundsWidth: number;
15
14
  height: number;
16
15
  htmlLayout: HTMLElement | null;
17
- onUpdate: (nextRangeSliderState?: RangeSliderState) => void;
16
+ onUpdate: (nextRangeSliderState?: RangeSliderState, syncZoom?: boolean) => void;
18
17
  preparedChart: PreparedChart;
19
18
  preparedLegend: PreparedLegend | null;
20
19
  preparedRangeSlider: PreparedRangeSlider;
@@ -24,7 +23,6 @@ export interface RangeSliderProps {
24
23
  rangeSliderState?: RangeSliderState;
25
24
  xAxis?: ChartXAxis;
26
25
  yAxis?: ChartYAxis[];
27
- zoomState?: Partial<ZoomState>;
28
26
  }
29
27
  export interface UseRangeSliderProps extends RangeSliderProps {
30
28
  clipPathId: string;