@gravity-ui/charts 1.34.0 → 1.34.2

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 (55) hide show
  1. package/dist/cjs/components/AxisX/prepare-axis-data.d.ts +6 -5
  2. package/dist/cjs/components/AxisX/prepare-axis-data.js +2 -2
  3. package/dist/cjs/components/AxisY/utils.js +3 -3
  4. package/dist/cjs/components/ChartInner/index.js +23 -10
  5. package/dist/cjs/components/ChartInner/useChartInnerProps.d.ts +1 -0
  6. package/dist/cjs/components/ChartInner/useChartInnerProps.js +3 -1
  7. package/dist/cjs/components/RangeSlider/index.js +1 -0
  8. package/dist/cjs/hooks/useAxis/x-axis.js +1 -1
  9. package/dist/cjs/hooks/useBrush/index.js +24 -5
  10. package/dist/cjs/hooks/useBrush/types.d.ts +1 -0
  11. package/dist/cjs/hooks/useBrush/utils.d.ts +4 -0
  12. package/dist/cjs/hooks/useBrush/utils.js +4 -0
  13. package/dist/cjs/hooks/useShapes/area/prepare-data.js +85 -25
  14. package/dist/cjs/hooks/useShapes/index.d.ts +3 -0
  15. package/dist/cjs/hooks/useShapes/index.js +40 -27
  16. package/dist/cjs/hooks/useShapes/utils.d.ts +10 -0
  17. package/dist/cjs/hooks/useShapes/utils.js +15 -0
  18. package/dist/cjs/setup-jsdom.d.ts +0 -0
  19. package/dist/cjs/setup-jsdom.js +19 -0
  20. package/dist/cjs/utils/chart/axis/common.d.ts +2 -2
  21. package/dist/cjs/utils/chart/axis/common.js +2 -2
  22. package/dist/cjs/utils/chart/axis/x-axis.d.ts +11 -10
  23. package/dist/cjs/utils/chart/axis/x-axis.js +24 -3
  24. package/dist/cjs/utils/chart/index.d.ts +2 -29
  25. package/dist/cjs/utils/chart/index.js +2 -19
  26. package/dist/cjs/utils/chart/series-type-guards.d.ts +30 -0
  27. package/dist/cjs/utils/chart/series-type-guards.js +19 -0
  28. package/dist/esm/components/AxisX/prepare-axis-data.d.ts +6 -5
  29. package/dist/esm/components/AxisX/prepare-axis-data.js +2 -2
  30. package/dist/esm/components/AxisY/utils.js +3 -3
  31. package/dist/esm/components/ChartInner/index.js +23 -10
  32. package/dist/esm/components/ChartInner/useChartInnerProps.d.ts +1 -0
  33. package/dist/esm/components/ChartInner/useChartInnerProps.js +3 -1
  34. package/dist/esm/components/RangeSlider/index.js +1 -0
  35. package/dist/esm/hooks/useAxis/x-axis.js +1 -1
  36. package/dist/esm/hooks/useBrush/index.js +24 -5
  37. package/dist/esm/hooks/useBrush/types.d.ts +1 -0
  38. package/dist/esm/hooks/useBrush/utils.d.ts +4 -0
  39. package/dist/esm/hooks/useBrush/utils.js +4 -0
  40. package/dist/esm/hooks/useShapes/area/prepare-data.js +85 -25
  41. package/dist/esm/hooks/useShapes/index.d.ts +3 -0
  42. package/dist/esm/hooks/useShapes/index.js +40 -27
  43. package/dist/esm/hooks/useShapes/utils.d.ts +10 -0
  44. package/dist/esm/hooks/useShapes/utils.js +15 -0
  45. package/dist/esm/setup-jsdom.d.ts +0 -0
  46. package/dist/esm/setup-jsdom.js +19 -0
  47. package/dist/esm/utils/chart/axis/common.d.ts +2 -2
  48. package/dist/esm/utils/chart/axis/common.js +2 -2
  49. package/dist/esm/utils/chart/axis/x-axis.d.ts +11 -10
  50. package/dist/esm/utils/chart/axis/x-axis.js +24 -3
  51. package/dist/esm/utils/chart/index.d.ts +2 -29
  52. package/dist/esm/utils/chart/index.js +2 -19
  53. package/dist/esm/utils/chart/series-type-guards.d.ts +30 -0
  54. package/dist/esm/utils/chart/series-type-guards.js +19 -0
  55. package/package.json +1 -1
@@ -131,3 +131,18 @@ export function getRectBorderPath(args) {
131
131
  }).toString();
132
132
  return `${outerPath} ${innerPath}`;
133
133
  }
134
+ export function getClipPathIdByBounds(args) {
135
+ const { bounds, clipPathId } = args;
136
+ return bounds ? `${clipPathId}-${bounds}` : clipPathId;
137
+ }
138
+ export function getSeriesClipPathId(args) {
139
+ const { clipPathId, yAxis, zoomState } = args;
140
+ const hasMinOrMax = yAxis.some((axis) => {
141
+ return typeof (axis === null || axis === void 0 ? void 0 : axis.min) === 'number' || typeof (axis === null || axis === void 0 ? void 0 : axis.max) === 'number';
142
+ });
143
+ const hasZoom = zoomState && Object.keys(zoomState).length > 0;
144
+ if (!hasZoom && !hasMinOrMax) {
145
+ return getClipPathIdByBounds({ clipPathId, bounds: 'horizontal' });
146
+ }
147
+ return getClipPathIdByBounds({ clipPathId });
148
+ }
File without changes
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ // Global mocks for jsdom environment.
3
+ // Guarded by typeof checks so this file is safe to run in the `node` environment too.
4
+ if (typeof document !== 'undefined') {
5
+ // jsdom does not implement document.fonts
6
+ if (!document.fonts) {
7
+ Object.defineProperty(document, 'fonts', {
8
+ value: { ready: Promise.resolve() },
9
+ configurable: true,
10
+ });
11
+ }
12
+ }
13
+ if (typeof HTMLCanvasElement !== 'undefined') {
14
+ // jsdom does not implement HTMLCanvasElement.prototype.getContext (used for text measurement)
15
+ HTMLCanvasElement.prototype.getContext = (() => ({
16
+ font: '',
17
+ measureText: () => ({ width: 0 }),
18
+ }));
19
+ }
@@ -3,9 +3,9 @@ import type { ChartScale, PreparedAxis, PreparedAxisPlotBand, PreparedSplit } fr
3
3
  import type { ChartAxis } from '../../../types';
4
4
  import type { AxisDirection } from '../types';
5
5
  type Ticks = number[] | string[] | Date[];
6
- export declare function getTicksCount({ axis, range }: {
6
+ export declare function getTicksCountByPixelInterval({ axis, axisWidth, }: {
7
7
  axis: PreparedAxis;
8
- range: number;
8
+ axisWidth: number;
9
9
  }): number | undefined;
10
10
  export declare function isBandScale(scale?: ChartScale | AxisScale<AxisDomain>): scale is ScaleBand<string>;
11
11
  export declare function isTimeScale(scale?: ChartScale | AxisScale<AxisDomain>): scale is ScaleTime<number, number>;
@@ -1,9 +1,9 @@
1
1
  import { ascending, descending, reverse, sort } from 'd3';
2
2
  import clamp from 'lodash/clamp';
3
- export function getTicksCount({ axis, range }) {
3
+ export function getTicksCountByPixelInterval({ axis, axisWidth, }) {
4
4
  let ticksCount;
5
5
  if (axis.ticks.pixelInterval) {
6
- ticksCount = Math.ceil(range / axis.ticks.pixelInterval);
6
+ ticksCount = Math.ceil(axisWidth / axis.ticks.pixelInterval);
7
7
  }
8
8
  return ticksCount;
9
9
  }
@@ -1,12 +1,13 @@
1
- import type { ChartScale, PreparedAxis } from '../../../hooks';
2
- export declare function getXAxisTickValues({ scale, axis, labelLineHeight, }: {
3
- scale: ChartScale;
1
+ import type { ChartScale, PreparedAxis, PreparedSeries } from '../../../hooks';
2
+ import type { ChartSeries } from '../../../types';
3
+ type TickValue = {
4
+ x: number;
5
+ value: number | string | Date;
6
+ };
7
+ export declare function getXAxisTickValues({ axis, labelLineHeight, scale, series, }: {
4
8
  axis: PreparedAxis;
5
9
  labelLineHeight: number;
6
- }): {
7
- x: number;
8
- value: number | Date;
9
- }[] | {
10
- x: number;
11
- value: string;
12
- }[];
10
+ scale: ChartScale;
11
+ series?: ChartSeries[] | PreparedSeries[];
12
+ }): TickValue[];
13
+ export {};
@@ -1,13 +1,34 @@
1
1
  import { getMinSpaceBetween } from '../array';
2
- import { getTicksCount, isBandScale, thinOut } from './common';
3
- export function getXAxisTickValues({ scale, axis, labelLineHeight, }) {
2
+ import { isSeriesWithNumericalXValues } from '../series-type-guards';
3
+ import { getTicksCountByPixelInterval, isBandScale, thinOut } from './common';
4
+ const DEFAULT_TICKS_COUNT = 10;
5
+ function getTicksCount(args) {
6
+ const { axis, axisWidth, series } = args;
7
+ const result = getTicksCountByPixelInterval({ axis, axisWidth });
8
+ if (typeof result === 'number') {
9
+ return result;
10
+ }
11
+ if (series) {
12
+ const xDataSet = new Set();
13
+ series === null || series === void 0 ? void 0 : series.forEach((item) => {
14
+ if (isSeriesWithNumericalXValues(item)) {
15
+ item.data.forEach((data) => {
16
+ xDataSet.add(data.x);
17
+ });
18
+ }
19
+ });
20
+ return xDataSet.size < DEFAULT_TICKS_COUNT ? xDataSet.size : DEFAULT_TICKS_COUNT;
21
+ }
22
+ return DEFAULT_TICKS_COUNT;
23
+ }
24
+ export function getXAxisTickValues({ axis, labelLineHeight, scale, series, }) {
4
25
  if ('ticks' in scale && typeof scale.ticks === 'function') {
5
26
  const range = scale.range();
6
27
  const axisWidth = Math.abs(range[0] - range[1]);
7
28
  if (!axisWidth) {
8
29
  return [];
9
30
  }
10
- const scaleTicksCount = getTicksCount({ axis, range: axisWidth });
31
+ const scaleTicksCount = getTicksCount({ axis, axisWidth, series });
11
32
  const scaleTicks = scale.ticks(scaleTicksCount);
12
33
  const originalTickValues = scaleTicks.map((t) => ({
13
34
  x: scale(t),
@@ -1,4 +1,5 @@
1
1
  import type { BaseTextStyle, ChartSeries, ChartSeriesData } from '../../types';
2
+ import type { UnknownSeries } from './series-type-guards';
2
3
  import type { AxisDirection } from './types';
3
4
  export * from './axis/common';
4
5
  export * from './array';
@@ -8,41 +9,13 @@ export * from './labels';
8
9
  export * from './legend';
9
10
  export * from './math';
10
11
  export * from './series';
12
+ export * from './series-type-guards';
11
13
  export * from './symbol';
12
14
  export * from './text';
13
15
  export * from './time';
14
16
  export * from './zoom';
15
17
  export declare const CHART_SERIES_WITH_VOLUME_ON_Y_AXIS: ChartSeries['type'][];
16
18
  export declare const CHART_SERIES_WITH_VOLUME_ON_X_AXIS: ChartSeries['type'][];
17
- type UnknownSeries = {
18
- type: ChartSeries['type'];
19
- data: unknown;
20
- };
21
- /**
22
- * Checks whether the series should be drawn with axes.
23
- *
24
- * @param series - The series object to check.
25
- * @returns `true` if the series should be drawn with axes, `false` otherwise.
26
- */
27
- export declare function isAxisRelatedSeries(series: UnknownSeries): boolean;
28
- export declare function isSeriesWithNumericalXValues(series: UnknownSeries): series is {
29
- type: ChartSeries['type'];
30
- data: {
31
- x: number;
32
- }[];
33
- };
34
- export declare function isSeriesWithNumericalYValues(series: UnknownSeries): series is {
35
- type: ChartSeries['type'];
36
- data: {
37
- y: number;
38
- }[];
39
- };
40
- export declare function isSeriesWithCategoryValues(series: UnknownSeries): series is {
41
- type: ChartSeries['type'];
42
- data: {
43
- category: string;
44
- }[];
45
- };
46
19
  export declare const getDomainDataXBySeries: (series: UnknownSeries[]) => ({} | undefined)[];
47
20
  export declare function getDefaultMaxXAxisValue(series: UnknownSeries[]): 0 | undefined;
48
21
  export declare function getDefaultMinXAxisValue(series: UnknownSeries[]): number | undefined;
@@ -5,6 +5,7 @@ import sortBy from 'lodash/sortBy';
5
5
  import { DEFAULT_AXIS_LABEL_FONT_SIZE, SERIES_TYPE } from '../../constants';
6
6
  import { getSeriesStackId } from '../../hooks/useSeries/utils';
7
7
  import { getWaterfallPointSubtotal } from './series/waterfall';
8
+ import { isSeriesWithNumericalXValues, isSeriesWithNumericalYValues } from './series-type-guards';
8
9
  export * from './axis/common';
9
10
  export * from './array';
10
11
  export * from './color';
@@ -13,35 +14,17 @@ export * from './labels';
13
14
  export * from './legend';
14
15
  export * from './math';
15
16
  export * from './series';
17
+ export * from './series-type-guards';
16
18
  export * from './symbol';
17
19
  export * from './text';
18
20
  export * from './time';
19
21
  export * from './zoom';
20
- const CHARTS_WITHOUT_AXIS = ['pie', 'treemap', 'sankey', 'radar', 'funnel'];
21
22
  export const CHART_SERIES_WITH_VOLUME_ON_Y_AXIS = [
22
23
  'bar-x',
23
24
  'area',
24
25
  'waterfall',
25
26
  ];
26
27
  export const CHART_SERIES_WITH_VOLUME_ON_X_AXIS = ['bar-y'];
27
- /**
28
- * Checks whether the series should be drawn with axes.
29
- *
30
- * @param series - The series object to check.
31
- * @returns `true` if the series should be drawn with axes, `false` otherwise.
32
- */
33
- export function isAxisRelatedSeries(series) {
34
- return !CHARTS_WITHOUT_AXIS.includes(series.type);
35
- }
36
- export function isSeriesWithNumericalXValues(series) {
37
- return isAxisRelatedSeries(series);
38
- }
39
- export function isSeriesWithNumericalYValues(series) {
40
- return isAxisRelatedSeries(series);
41
- }
42
- export function isSeriesWithCategoryValues(series) {
43
- return isAxisRelatedSeries(series);
44
- }
45
28
  function getDomainDataForStackedSeries(seriesList, keyAttr = 'x', valueAttr = 'y') {
46
29
  const acc = [];
47
30
  const stackedSeries = group(seriesList, getSeriesStackId);
@@ -0,0 +1,30 @@
1
+ import type { ChartSeries } from '../../types';
2
+ export type UnknownSeries = {
3
+ type: ChartSeries['type'];
4
+ data: unknown;
5
+ };
6
+ /**
7
+ * Checks whether the series should be drawn with axes.
8
+ *
9
+ * @param series - The series object to check.
10
+ * @returns `true` if the series should be drawn with axes, `false` otherwise.
11
+ */
12
+ export declare function isAxisRelatedSeries(series: UnknownSeries): boolean;
13
+ export declare function isSeriesWithNumericalXValues(series: UnknownSeries): series is {
14
+ type: ChartSeries['type'];
15
+ data: {
16
+ x: number;
17
+ }[];
18
+ };
19
+ export declare function isSeriesWithNumericalYValues(series: UnknownSeries): series is {
20
+ type: ChartSeries['type'];
21
+ data: {
22
+ y: number;
23
+ }[];
24
+ };
25
+ export declare function isSeriesWithCategoryValues(series: UnknownSeries): series is {
26
+ type: ChartSeries['type'];
27
+ data: {
28
+ category: string;
29
+ }[];
30
+ };
@@ -0,0 +1,19 @@
1
+ const CHARTS_WITHOUT_AXIS = ['pie', 'treemap', 'sankey', 'radar', 'funnel'];
2
+ /**
3
+ * Checks whether the series should be drawn with axes.
4
+ *
5
+ * @param series - The series object to check.
6
+ * @returns `true` if the series should be drawn with axes, `false` otherwise.
7
+ */
8
+ export function isAxisRelatedSeries(series) {
9
+ return !CHARTS_WITHOUT_AXIS.includes(series.type);
10
+ }
11
+ export function isSeriesWithNumericalXValues(series) {
12
+ return isAxisRelatedSeries(series);
13
+ }
14
+ export function isSeriesWithNumericalYValues(series) {
15
+ return isAxisRelatedSeries(series);
16
+ }
17
+ export function isSeriesWithCategoryValues(series) {
18
+ return isAxisRelatedSeries(series);
19
+ }
@@ -1,12 +1,13 @@
1
- import type { ChartScale, PreparedAxis, PreparedSplit } from '../../hooks';
1
+ import type { ChartScale, PreparedAxis, PreparedSeries, PreparedSplit } from '../../hooks';
2
2
  import type { AxisXData } from './types';
3
- export declare function prepareXAxisData({ axis, yAxis, scale, boundsWidth, boundsOffsetLeft, boundsOffsetRight, height, split, }: {
3
+ export declare function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRight, boundsWidth, height, scale, series, split, yAxis, }: {
4
4
  axis: PreparedAxis;
5
- yAxis: PreparedAxis[];
6
- scale: ChartScale;
7
- boundsWidth: number;
8
5
  boundsOffsetLeft: number;
9
6
  boundsOffsetRight: number;
7
+ boundsWidth: number;
10
8
  height: number;
9
+ scale: ChartScale;
10
+ series: PreparedSeries[];
11
11
  split: PreparedSplit;
12
+ yAxis: PreparedAxis[];
12
13
  }): Promise<AxisXData[]>;
@@ -68,7 +68,7 @@ async function getSvgAxisLabel({ getTextSize, text, axis, top, left, labelMaxWid
68
68
  return svgLabel;
69
69
  }
70
70
  // eslint-disable-next-line complexity
71
- export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, boundsOffsetLeft, boundsOffsetRight, height, split, }) {
71
+ export async function prepareXAxisData({ axis, boundsOffsetLeft, boundsOffsetRight, boundsWidth, height, scale, series, split, yAxis, }) {
72
72
  var _a, _b, _c, _d, _e, _f;
73
73
  const xAxisItems = [];
74
74
  for (let plotIndex = 0; plotIndex < split.plots.length; plotIndex++) {
@@ -95,7 +95,7 @@ export async function prepareXAxisData({ axis, yAxis, scale, boundsWidth, bounds
95
95
  const ticks = [];
96
96
  const getTextSize = getTextSizeFn({ style: axis.labels.style });
97
97
  const labelLineHeight = (await getTextSize('Tmp')).height;
98
- const values = getXAxisTickValues({ scale, axis, labelLineHeight });
98
+ const values = getXAxisTickValues({ scale, axis, labelLineHeight, series });
99
99
  const tickStep = getMinSpaceBetween(values, (d) => Number(d.value));
100
100
  const labelMaxWidth = values.length > 1
101
101
  ? Math.abs(values[0].x - values[1].x) - axis.labels.padding * 2
@@ -1,4 +1,4 @@
1
- import { getDomainDataYBySeries, getMinSpaceBetween, getTicksCount, isBandScale, thinOut, } from '../../utils';
1
+ import { getDomainDataYBySeries, getMinSpaceBetween, getTicksCountByPixelInterval, isBandScale, thinOut, } from '../../utils';
2
2
  export function getTickValues({ scale, axis, labelLineHeight, series, }) {
3
3
  if ('ticks' in scale && typeof scale.ticks === 'function') {
4
4
  const range = scale.range();
@@ -13,10 +13,10 @@ export function getTickValues({ scale, axis, labelLineHeight, series, }) {
13
13
  if (domainData.length < 3) {
14
14
  return domainData;
15
15
  }
16
- const ticksCount = (_a = getTicksCount({ axis, range: height })) !== null && _a !== void 0 ? _a : domainData.length;
16
+ const ticksCount = (_a = getTicksCountByPixelInterval({ axis, axisWidth: height })) !== null && _a !== void 0 ? _a : domainData.length;
17
17
  return scale.ticks(Math.min(ticksCount, domainData.length));
18
18
  }
19
- const ticksCount = getTicksCount({ axis, range: height });
19
+ const ticksCount = getTicksCountByPixelInterval({ axis, axisWidth: height });
20
20
  return scale.ticks(ticksCount);
21
21
  };
22
22
  const scaleTicks = getScaleTicks();
@@ -6,6 +6,7 @@ import { getPreparedRangeSlider } from '../../hooks/useAxis/range-slider';
6
6
  import { getPreparedChart } from '../../hooks/useChartOptions/chart';
7
7
  import { getPreparedTitle } from '../../hooks/useChartOptions/title';
8
8
  import { getPreparedTooltip } from '../../hooks/useChartOptions/tooltip';
9
+ import { getClipPathIdByBounds } from '../../hooks/useShapes/utils';
9
10
  import { EventType, block, getDispatcher, isBandScale } from '../../utils';
10
11
  import { AxisX } from '../AxisX/AxisX';
11
12
  import { prepareXAxisData } from '../AxisX/prepare-axis-data';
@@ -63,7 +64,7 @@ export const ChartInner = (props) => {
63
64
  preparedRangeSlider,
64
65
  tooltip: preparedTooltip,
65
66
  });
66
- const { allPreparedSeries, boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedLegend, preparedSeries, preparedSeriesOptions, preparedSplit, prevHeight, prevWidth, shapes, shapesData, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
67
+ const { allPreparedSeries, boundsHeight, boundsOffsetLeft, boundsOffsetTop, boundsWidth, handleLegendItemClick, isOutsideBounds, legendConfig, legendItems, preparedLegend, preparedSeries, preparedSeriesOptions, preparedSplit, prevHeight, prevWidth, shapes, shapesData, shapesReady, xAxis, xScale, yAxis, yScale, } = useChartInnerProps(Object.assign(Object.assign({}, props), { clipPathId,
67
68
  dispatcher,
68
69
  htmlLayout, plotNode: plotRef.current, preparedChart, rangeSliderDomain: (_a = rangeSliderRef.current) === null || _a === void 0 ? void 0 : _a.getDomain(), rangeSliderState, svgContainer: svgRef.current, updateZoomState,
69
70
  zoomState }));
@@ -159,18 +160,29 @@ export const ChartInner = (props) => {
159
160
  if (axis && scale) {
160
161
  const axisData = await prepareXAxisData({
161
162
  axis,
162
- yAxis,
163
- scale,
164
- boundsWidth,
165
163
  boundsOffsetLeft: boundsOffsetLeft,
166
164
  boundsOffsetRight: width - boundsWidth - boundsOffsetLeft,
165
+ boundsWidth,
167
166
  height: boundsHeight,
167
+ scale,
168
+ series: preparedSeries.filter((s) => s.visible),
168
169
  split: preparedSplit,
170
+ yAxis,
169
171
  });
170
172
  items.push(...axisData);
171
173
  }
172
174
  return items;
173
- }, [xAxis, xScale, yAxis, boundsWidth, boundsOffsetLeft, width, boundsHeight, preparedSplit]);
175
+ }, [
176
+ boundsHeight,
177
+ boundsOffsetLeft,
178
+ boundsWidth,
179
+ preparedSeries,
180
+ preparedSplit,
181
+ width,
182
+ xAxis,
183
+ xScale,
184
+ yAxis,
185
+ ]);
174
186
  const xAxisDataItems = useAsyncState([], setXAxisDataItems);
175
187
  React.useEffect(() => {
176
188
  if (!initialized && xScale) {
@@ -206,16 +218,17 @@ export const ChartInner = (props) => {
206
218
  updateRangeSliderState,
207
219
  xScale,
208
220
  ]);
209
- const areShapesReady = shapes.length > 0;
210
221
  React.useEffect(() => {
211
- if (areShapesReady) {
222
+ if (shapesReady) {
212
223
  onReady === null || onReady === void 0 ? void 0 : onReady({ dimensions: { width, height } });
213
224
  }
214
- }, [height, areShapesReady, onReady, width]);
225
+ }, [height, shapesReady, onReady, width]);
215
226
  const chartContent = (React.createElement(React.Fragment, null,
216
227
  React.createElement("defs", null,
217
- React.createElement("clipPath", { id: clipPathId },
218
- React.createElement("rect", { x: 0, y: 0, width: boundsWidth, height: boundsHeight }))),
228
+ React.createElement("clipPath", { id: getClipPathIdByBounds({ clipPathId }) },
229
+ React.createElement("rect", { x: 0, y: 0, width: boundsWidth, height: boundsHeight })),
230
+ React.createElement("clipPath", { id: getClipPathIdByBounds({ clipPathId, bounds: 'horizontal' }) },
231
+ React.createElement("rect", { x: 0, y: -boundsHeight, width: boundsWidth, height: boundsHeight * 3 }))),
219
232
  preparedTitle && React.createElement(Title, Object.assign({}, preparedTitle, { chartWidth: width })),
220
233
  React.createElement("g", { transform: `translate(0, ${boundsOffsetTop})` }, preparedSplit.plots.map((plot, index) => {
221
234
  return React.createElement(PlotTitle, { key: `plot-${index}`, title: plot.title });
@@ -44,6 +44,7 @@ export declare function useChartInnerProps(props: Props): {
44
44
  prevWidth: number | undefined;
45
45
  shapes: React.ReactElement<any, string | React.JSXElementConstructor<any>>[];
46
46
  shapesData: import("../../hooks").ShapeData[];
47
+ shapesReady: boolean;
47
48
  svgXPos: number | undefined;
48
49
  xAxis: import("../../hooks").PreparedXAxis | null;
49
50
  xScale: import("../../hooks").ChartScale | undefined;
@@ -111,7 +111,7 @@ export function useChartInnerProps(props) {
111
111
  const isOutsideBounds = React.useCallback((x, y) => {
112
112
  return x < 0 || x > boundsWidth || y < 0 || y > boundsHeight;
113
113
  }, [boundsHeight, boundsWidth]);
114
- const { shapes, shapesData } = useShapes({
114
+ const { shapes, shapesData, shapesReady } = useShapes({
115
115
  boundsWidth,
116
116
  boundsHeight,
117
117
  clipPathBySeriesType: CLIP_PATH_BY_SERIES_TYPE,
@@ -126,6 +126,7 @@ export function useChartInnerProps(props) {
126
126
  htmlLayout,
127
127
  clipPathId,
128
128
  isOutsideBounds,
129
+ zoomState,
129
130
  });
130
131
  const handleAttemptToSetZoomState = React.useCallback((nextZoomState) => {
131
132
  const { preparedSeries: nextZoomedSeriesData } = getZoomedSeriesData({
@@ -182,6 +183,7 @@ export function useChartInnerProps(props) {
182
183
  prevWidth,
183
184
  shapes,
184
185
  shapesData,
186
+ shapesReady,
185
187
  svgXPos: x,
186
188
  xAxis,
187
189
  xScale,
@@ -59,6 +59,7 @@ function RangeSliderComponent(props, forwardedRef) {
59
59
  brushOptions: brush,
60
60
  node: ref.current,
61
61
  onBrushEnd,
62
+ preventNullSelection: true,
62
63
  selection,
63
64
  type: 'x',
64
65
  });
@@ -15,7 +15,7 @@ async function setLabelSettings({ axis, seriesData, width, axisLabels, }) {
15
15
  }
16
16
  const getTextSize = getTextSizeFn({ style: axis.labels.style });
17
17
  const labelLineHeight = (await getTextSize('Tmp')).height;
18
- const tickValues = getXAxisTickValues({ axis, scale, labelLineHeight });
18
+ const tickValues = getXAxisTickValues({ axis, scale, labelLineHeight, series: seriesData });
19
19
  const tickStep = getMinSpaceBetween(tickValues, (d) => Number(d.value));
20
20
  if (axis.type === 'datetime' && !(axisLabels === null || axisLabels === void 0 ? void 0 : axisLabels.dateFormat)) {
21
21
  axis.labels.dateFormat = getDefaultDateFormat(tickStep);
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
- import { brush, brushX, brushY, select } from 'd3';
2
+ import { brush, brushX, brushY, pointer, select } from 'd3';
3
3
  import { block } from '../../utils';
4
- import { getNormalizedSelection, setBrushBorder, setBrushHandles } from './utils';
4
+ import { getDefaultSelection, getNormalizedSelection, setBrushBorder, setBrushHandles, } from './utils';
5
5
  import './styles.css';
6
6
  const b = block('brush');
7
7
  export function useBrush(props) {
8
- const { areas, brushOptions, disabled, node, selection, type, onBrushStart, onBrush, onBrushEnd, } = props;
8
+ const { areas, brushOptions, disabled, node, preventNullSelection = false, selection, type, onBrushStart, onBrush, onBrushEnd, } = props;
9
9
  React.useEffect(() => {
10
10
  if (!node || !areas.length || disabled) {
11
11
  return () => { };
@@ -103,7 +103,15 @@ export function useBrush(props) {
103
103
  });
104
104
  }
105
105
  if (event.sourceEvent) {
106
- onBrushEnd === null || onBrushEnd === void 0 ? void 0 : onBrushEnd.call(this, instance, event.selection);
106
+ let resultSelection = event.selection;
107
+ if (preventNullSelection && !resultSelection) {
108
+ const [pointerPositionX] = pointer(event, this);
109
+ resultSelection = getDefaultSelection({
110
+ brushWidth,
111
+ pointerPositionX,
112
+ });
113
+ }
114
+ onBrushEnd === null || onBrushEnd === void 0 ? void 0 : onBrushEnd.call(this, instance, resultSelection);
107
115
  }
108
116
  });
109
117
  groupSelection.call(instance);
@@ -129,5 +137,16 @@ export function useBrush(props) {
129
137
  groupSelection === null || groupSelection === void 0 ? void 0 : groupSelection.remove();
130
138
  });
131
139
  };
132
- }, [areas, brushOptions, disabled, node, selection, type, onBrushStart, onBrush, onBrushEnd]);
140
+ }, [
141
+ areas,
142
+ brushOptions,
143
+ disabled,
144
+ node,
145
+ preventNullSelection,
146
+ selection,
147
+ type,
148
+ onBrushStart,
149
+ onBrush,
150
+ onBrushEnd,
151
+ ]);
133
152
  }
@@ -15,6 +15,7 @@ export interface BrushArea {
15
15
  export interface UseBrushProps {
16
16
  areas: BrushArea[];
17
17
  node: SVGGElement | null;
18
+ preventNullSelection?: boolean;
18
19
  brushOptions?: DeepRequired<ChartBrush>;
19
20
  disabled?: boolean;
20
21
  onBrush?: (this: SVGGElement, brushInstance: BrushBehavior<unknown>, selection: BrushSelection) => void;
@@ -17,3 +17,7 @@ export declare function getNormalizedSelection(args: {
17
17
  selection: BrushSelection;
18
18
  width: number;
19
19
  }): [number, number] | [[number, number], [number, number]];
20
+ export declare function getDefaultSelection(args: {
21
+ brushWidth: number;
22
+ pointerPositionX: number;
23
+ }): BrushSelection;
@@ -170,3 +170,7 @@ export function getNormalizedSelection(args) {
170
170
  }
171
171
  return resultSelection;
172
172
  }
173
+ export function getDefaultSelection(args) {
174
+ const { brushWidth, pointerPositionX } = args;
175
+ return pointerPositionX < 0 ? [0, 1] : [brushWidth - 1, brushWidth];
176
+ }