basalt-ui 1.30.2 → 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.
- package/AGENTS.md +1 -1
- package/CHANGELOG.md +31 -0
- package/MIGRATING.md +172 -12
- package/agent/rules/basalt-controls.md +19 -19
- package/agent/rules/basalt-mantine.md +1 -1
- package/configs/oxlint-plugin.js +254 -4
- package/configs/oxlint-plugin.test.ts +145 -2
- package/configs/oxlint.json +2 -1
- package/dist/agent-chat/composer.d.ts.map +1 -1
- package/dist/agent-chat/composer.js +41 -43
- package/dist/agent-chat/thread-workspace.d.ts +2 -2
- package/dist/agent-chat/thread-workspace.d.ts.map +1 -1
- package/dist/agent-chat/thread-workspace.js +3 -4
- package/dist/agent-chat/use-keyboard-inset.d.ts +2 -0
- package/dist/agent-chat/use-keyboard-inset.d.ts.map +1 -0
- package/dist/agent-chat/use-keyboard-inset.js +34 -0
- package/dist/charts/cursor/touch-pin.d.ts +45 -0
- package/dist/charts/cursor/touch-pin.d.ts.map +1 -0
- package/dist/charts/cursor/touch-pin.js +79 -0
- package/dist/charts/hooks/use-discrete-cursor.d.ts +66 -0
- package/dist/charts/hooks/use-discrete-cursor.d.ts.map +1 -0
- package/dist/charts/hooks/use-discrete-cursor.js +198 -0
- package/dist/charts/hooks/use-hysteresis.d.ts +18 -0
- package/dist/charts/hooks/use-hysteresis.d.ts.map +1 -0
- package/dist/charts/hooks/use-hysteresis.js +12 -0
- package/dist/charts/hooks/useBandPlot.d.ts +7 -0
- package/dist/charts/hooks/useBandPlot.d.ts.map +1 -1
- package/dist/charts/hooks/useBandPlot.js +62 -18
- package/dist/charts/hooks/useChartCursor.d.ts +40 -11
- package/dist/charts/hooks/useChartCursor.d.ts.map +1 -1
- package/dist/charts/hooks/useChartCursor.js +102 -11
- package/dist/charts/index.d.ts +0 -1
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +0 -10
- package/dist/charts/kinds/BandStrip.d.ts.map +1 -1
- package/dist/charts/kinds/BandStrip.js +54 -41
- package/dist/charts/kinds/Donut.d.ts +9 -3
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +110 -48
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +145 -131
- package/dist/charts/kinds/Heatmap.d.ts +7 -0
- package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
- package/dist/charts/kinds/Heatmap.js +37 -16
- package/dist/charts/kinds/MirroredBars.d.ts.map +1 -1
- package/dist/charts/kinds/MirroredBars.js +83 -68
- package/dist/charts/kinds/donut-layout.d.ts +47 -0
- package/dist/charts/kinds/donut-layout.d.ts.map +1 -0
- package/dist/charts/kinds/donut-layout.js +52 -0
- package/dist/charts/layout/auto-margin.d.ts +7 -2
- package/dist/charts/layout/auto-margin.d.ts.map +1 -1
- package/dist/charts/layout/auto-margin.js +12 -3
- package/dist/charts/layout/end-labels.d.ts +64 -0
- package/dist/charts/layout/end-labels.d.ts.map +1 -0
- package/dist/charts/layout/end-labels.js +76 -0
- package/dist/charts/primitives/Axes.d.ts +32 -4
- package/dist/charts/primitives/Axes.d.ts.map +1 -1
- package/dist/charts/primitives/Axes.js +62 -16
- package/dist/charts/primitives/CartesianChart.d.ts +7 -6
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -1
- package/dist/charts/primitives/CartesianChart.js +271 -125
- package/dist/charts/primitives/ChartCard.d.ts +0 -14
- package/dist/charts/primitives/ChartCard.d.ts.map +1 -1
- package/dist/charts/primitives/ChartCard.js +74 -21
- package/dist/charts/primitives/ChartFrame.d.ts +28 -22
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +133 -34
- package/dist/charts/primitives/ChartLegend.d.ts +19 -15
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +212 -71
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +4 -3
- package/dist/charts/primitives/Crosshair.js +2 -2
- package/dist/charts/primitives/HoverOverlay.d.ts +6 -1
- package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
- package/dist/charts/primitives/HoverOverlay.js +37 -26
- package/dist/charts/primitives/HoverOverlay.module.css +13 -0
- package/dist/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
- package/dist/charts/primitives/chart-card-context.d.ts +29 -0
- package/dist/charts/primitives/chart-card-context.d.ts.map +1 -0
- package/dist/charts/primitives/chart-card-context.js +13 -0
- package/dist/charts/primitives/chart-frame-layout.d.ts +93 -113
- package/dist/charts/primitives/chart-frame-layout.d.ts.map +1 -1
- package/dist/charts/primitives/chart-frame-layout.js +76 -29
- package/dist/charts/primitives/chart-layout.d.ts +112 -0
- package/dist/charts/primitives/chart-layout.d.ts.map +1 -0
- package/dist/charts/primitives/chart-layout.js +175 -0
- package/dist/charts/primitives/chart-tier.d.ts +13 -11
- package/dist/charts/primitives/chart-tier.d.ts.map +1 -1
- package/dist/charts/primitives/chart-tier.js +42 -13
- package/dist/charts/series.d.ts +4 -1
- package/dist/charts/series.d.ts.map +1 -1
- package/dist/charts/utils/ticks.d.ts +27 -8
- package/dist/charts/utils/ticks.d.ts.map +1 -1
- package/dist/charts/utils/ticks.js +35 -11
- package/dist/common/errors.d.ts +11 -1
- package/dist/common/errors.d.ts.map +1 -1
- package/dist/common/errors.js +16 -2
- package/dist/common/hit-floor.d.ts +10 -0
- package/dist/common/hit-floor.d.ts.map +1 -0
- package/dist/common/hit-floor.js +6 -0
- package/dist/{shell → common}/isomorphic-layout-effect.d.ts +4 -3
- package/dist/common/isomorphic-layout-effect.d.ts.map +1 -0
- package/dist/common/use-measured-widths.d.ts +37 -0
- package/dist/common/use-measured-widths.d.ts.map +1 -0
- package/dist/common/use-measured-widths.js +51 -0
- package/dist/common/use-media-query.d.ts +6 -0
- package/dist/common/use-media-query.d.ts.map +1 -0
- package/dist/common/use-media-query.js +40 -0
- package/dist/common/validate.d.ts +9 -0
- package/dist/common/validate.d.ts.map +1 -1
- package/dist/common/validate.js +9 -4
- package/dist/content/article-layout.d.ts +4 -2
- package/dist/content/article-layout.d.ts.map +1 -1
- package/dist/content/article-layout.js +43 -3
- package/dist/content/article-layout.module.css +45 -4
- package/dist/content/article-layout.module.css.d.ts +3 -0
- package/dist/content/code-block.d.ts.map +1 -1
- package/dist/content/code-block.js +40 -2
- package/dist/content/code-block.module.css +32 -2
- package/dist/content/prose.module.css +13 -8
- package/dist/controls/actions.d.ts +29 -6
- package/dist/controls/actions.d.ts.map +1 -1
- package/dist/controls/actions.js +158 -27
- package/dist/controls/actions.module.css +14 -1
- package/dist/controls/actions.module.css.d.ts +1 -0
- package/dist/controls/controls.module.css +8 -31
- package/dist/controls/enum-filter.js +2 -2
- package/dist/controls/filter-context.d.ts +10 -0
- package/dist/controls/filter-context.d.ts.map +1 -1
- package/dist/controls/filter-context.js +2 -0
- package/dist/controls/filter-pill.d.ts.map +1 -1
- package/dist/controls/filter-pill.js +1 -0
- package/dist/controls/index.d.ts +1 -1
- package/dist/controls/index.d.ts.map +1 -1
- package/dist/controls/index.js +0 -2
- package/dist/controls/multi-select-filter.js +2 -2
- package/dist/controls/sync-button.module.css +1 -1
- package/dist/dashboard/section.d.ts.map +1 -1
- package/dist/dashboard/section.js +1 -0
- package/dist/dashboard/section.module.css +1 -26
- package/dist/dashboard/stat-card.d.ts +1 -1
- package/dist/dashboard/stat-card.d.ts.map +1 -1
- package/dist/dashboard/stat-card.js +4 -1
- package/dist/dashboard/stat-card.module.css +6 -9
- package/dist/dashboard/stat-group.module.css +16 -4
- package/dist/dashboard/widget-grid.d.ts.map +1 -1
- package/dist/dashboard/widget-header.d.ts +14 -4
- package/dist/dashboard/widget-header.d.ts.map +1 -1
- package/dist/dashboard/widget-header.js +23 -6
- package/dist/dashboard/widget-header.module.css +145 -133
- package/dist/dashboard/widget-header.module.css.d.ts +2 -0
- package/dist/data/data-table.d.ts +23 -0
- package/dist/data/data-table.d.ts.map +1 -1
- package/dist/data/data-table.js +339 -141
- package/dist/data/data-table.module.css +94 -0
- package/dist/data/data-table.module.css.d.ts +11 -0
- package/dist/data/virtual-list.d.ts.map +1 -1
- package/dist/data/virtual-list.js +10 -0
- package/dist/forms/form-layout.d.ts.map +1 -1
- package/dist/forms/form-layout.js +3 -3
- package/dist/forms/form-layout.module.css +30 -5
- package/dist/forms/form-layout.module.css.d.ts +1 -0
- package/dist/guard/index.d.ts +8 -2
- package/dist/guard/index.d.ts.map +1 -1
- package/dist/guard/index.js +67 -2
- package/dist/guard/types.d.ts +3 -3
- package/dist/guard/types.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/provider/index.d.ts +20 -1
- package/dist/provider/index.d.ts.map +1 -1
- package/dist/provider/index.js +22 -2
- package/dist/shell/app-brand.d.ts.map +1 -1
- package/dist/shell/app-brand.js +1 -0
- package/dist/shell/app-brand.module.css +2 -2
- package/dist/shell/app-header.module.css +18 -8
- package/dist/shell/app-main.module.css +54 -3
- package/dist/shell/app-main.module.css.d.ts +1 -0
- package/dist/shell/app-mobile-nav.module.css +16 -4
- package/dist/shell/app-sidebar.module.css +4 -6
- package/dist/shell/index.d.ts +12 -0
- package/dist/shell/index.d.ts.map +1 -1
- package/dist/shell/index.js +57 -16
- package/dist/shell/mobile-nav-model.d.ts +1 -1
- package/dist/shell/page-aside.d.ts +22 -3
- package/dist/shell/page-aside.d.ts.map +1 -1
- package/dist/shell/page-aside.js +27 -13
- package/dist/shell/page-bar.d.ts.map +1 -1
- package/dist/shell/page-bar.js +10 -5
- package/dist/shell/page-bar.module.css +10 -34
- package/dist/shell/use-breakpoint.d.ts +3 -4
- package/dist/shell/use-breakpoint.d.ts.map +1 -1
- package/dist/shell/use-breakpoint.js +3 -25
- package/dist/shell/use-size-class.d.ts +6 -0
- package/dist/shell/use-size-class.d.ts.map +1 -0
- package/dist/shell/use-size-class.js +19 -0
- package/dist/state/persisted-or-local.d.ts +4 -1
- package/dist/state/persisted-or-local.d.ts.map +1 -1
- package/dist/state/persisted-or-local.js +4 -3
- package/dist/state/persisted.d.ts +10 -0
- package/dist/state/persisted.d.ts.map +1 -1
- package/dist/state/persisted.js +44 -14
- package/dist/styles.css +41 -0
- package/dist/surfaces.d.ts +4 -4
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +11 -3
- package/dist/theme/controls.module.css +7 -0
- package/dist/theme/ctl-theme.d.ts +1 -0
- package/dist/theme/ctl-theme.d.ts.map +1 -1
- package/dist/theme/ctl-theme.js +12 -4
- package/dist/theme/floating.module.css +36 -0
- package/dist/theme/floating.module.css.d.ts +3 -0
- package/dist/theme/index.d.ts +2 -2
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +34 -5
- package/dist/theme/nav-link.module.css +7 -0
- package/dist/tokens/index.d.ts +7 -16
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +23 -28
- package/dist/tokens/palette.d.ts +5 -27
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +8 -30
- package/dist/tokens/size-classes.d.ts +40 -0
- package/dist/tokens/size-classes.d.ts.map +1 -0
- package/dist/tokens/size-classes.js +29 -0
- package/dist/tokens.css +8 -4
- package/llms.txt +3 -3
- package/package.json +17 -17
- package/src/agent-chat/composer.tsx +74 -47
- package/src/agent-chat/thread-workspace.tsx +8 -11
- package/src/agent-chat/use-keyboard-inset.ts +68 -0
- package/src/charts/cursor/touch-pin.ts +138 -0
- package/src/charts/hooks/use-discrete-cursor.ts +335 -0
- package/src/charts/hooks/use-hysteresis.ts +26 -0
- package/src/charts/hooks/useBandPlot.ts +90 -27
- package/src/charts/hooks/useChartCursor.ts +211 -26
- package/src/charts/index.ts +0 -8
- package/src/charts/kinds/BandStrip.tsx +11 -1
- package/src/charts/kinds/Donut.tsx +153 -62
- package/src/charts/kinds/DualPanel.tsx +19 -4
- package/src/charts/kinds/Heatmap.tsx +61 -22
- package/src/charts/kinds/MirroredBars.tsx +15 -3
- package/src/charts/kinds/donut-layout.ts +82 -0
- package/src/charts/layout/auto-margin.ts +24 -5
- package/src/charts/layout/end-labels.ts +135 -0
- package/src/charts/primitives/Axes.tsx +112 -21
- package/src/charts/primitives/CartesianChart.tsx +282 -68
- package/src/charts/primitives/ChartCard.tsx +141 -48
- package/src/charts/primitives/ChartFrame.tsx +243 -60
- package/src/charts/primitives/ChartLegend.tsx +257 -73
- package/src/charts/primitives/ChartTooltip.tsx +8 -3
- package/src/charts/primitives/Crosshair.tsx +2 -2
- package/src/charts/primitives/HoverOverlay.module.css +13 -0
- package/src/charts/primitives/HoverOverlay.module.css.d.ts +8 -0
- package/src/charts/primitives/HoverOverlay.tsx +15 -4
- package/src/charts/primitives/chart-card-context.ts +37 -0
- package/src/charts/primitives/chart-frame-layout.ts +214 -177
- package/src/charts/primitives/chart-layout.ts +318 -0
- package/src/charts/primitives/chart-tier.tsx +66 -21
- package/src/charts/series.ts +4 -1
- package/src/charts/utils/ticks.ts +95 -19
- package/src/common/errors.ts +25 -1
- package/src/common/hit-floor.ts +9 -0
- package/src/{shell → common}/isomorphic-layout-effect.ts +4 -3
- package/src/common/use-measured-widths.ts +88 -0
- package/src/common/use-media-query.ts +56 -0
- package/src/common/validate.ts +17 -4
- package/src/content/article-layout.module.css +45 -4
- package/src/content/article-layout.module.css.d.ts +3 -0
- package/src/content/article-layout.tsx +60 -5
- package/src/content/code-block.module.css +32 -2
- package/src/content/code-block.tsx +47 -3
- package/src/content/prose.module.css +13 -8
- package/src/controls/actions.module.css +14 -1
- package/src/controls/actions.module.css.d.ts +1 -0
- package/src/controls/actions.tsx +223 -75
- package/src/controls/controls.module.css +8 -31
- package/src/controls/enum-filter.tsx +2 -2
- package/src/controls/filter-context.tsx +11 -0
- package/src/controls/filter-pill.tsx +1 -0
- package/src/controls/index.ts +0 -1
- package/src/controls/multi-select-filter.tsx +2 -2
- package/src/controls/sync-button.module.css +1 -1
- package/src/dashboard/section.module.css +1 -26
- package/src/dashboard/section.tsx +1 -0
- package/src/dashboard/stat-card.module.css +6 -9
- package/src/dashboard/stat-card.tsx +6 -3
- package/src/dashboard/stat-group.module.css +16 -4
- package/src/dashboard/widget-grid.tsx +7 -6
- package/src/dashboard/widget-header.module.css +145 -133
- package/src/dashboard/widget-header.module.css.d.ts +2 -0
- package/src/dashboard/widget-header.tsx +45 -11
- package/src/data/data-table.module.css +94 -0
- package/src/data/data-table.module.css.d.ts +11 -0
- package/src/data/data-table.tsx +503 -164
- package/src/data/virtual-list.tsx +31 -0
- package/src/forms/form-layout.module.css +30 -5
- package/src/forms/form-layout.module.css.d.ts +1 -0
- package/src/forms/form-layout.tsx +12 -10
- package/src/guard/index.ts +183 -3
- package/src/guard/types.ts +3 -2
- package/src/index.ts +2 -0
- package/src/provider/index.tsx +53 -1
- package/src/shell/app-brand.module.css +2 -2
- package/src/shell/app-brand.tsx +1 -0
- package/src/shell/app-header.module.css +18 -8
- package/src/shell/app-main.module.css +54 -3
- package/src/shell/app-main.module.css.d.ts +1 -0
- package/src/shell/app-mobile-nav.module.css +16 -4
- package/src/shell/app-sidebar.module.css +4 -6
- package/src/shell/index.tsx +200 -129
- package/src/shell/mobile-nav-model.ts +1 -1
- package/src/shell/page-aside.tsx +70 -21
- package/src/shell/page-bar.module.css +10 -34
- package/src/shell/page-bar.tsx +10 -5
- package/src/shell/use-breakpoint.ts +9 -45
- package/src/shell/use-size-class.ts +31 -0
- package/src/state/persisted-or-local.ts +10 -4
- package/src/state/persisted.ts +76 -17
- package/src/styles.css +41 -0
- package/src/surfaces.ts +9 -1
- package/src/theme/controls.module.css +7 -0
- package/src/theme/ctl-theme.tsx +15 -3
- package/src/theme/floating.module.css +36 -0
- package/src/theme/floating.module.css.d.ts +3 -0
- package/src/theme/index.ts +46 -8
- package/src/theme/nav-link.module.css +7 -0
- package/src/tokens/index.ts +20 -30
- package/src/tokens/palette.ts +6 -28
- package/src/tokens/size-classes.ts +46 -0
- package/dist/shell/isomorphic-layout-effect.d.ts.map +0 -1
- /package/dist/{shell → common}/isomorphic-layout-effect.js +0 -0
|
@@ -4,21 +4,33 @@ import { scaleLinear, scaleLog, scalePoint } from '@visx/scale'
|
|
|
4
4
|
import { useMemo, useRef, useState } from 'react'
|
|
5
5
|
import type { ReactNode } from 'react'
|
|
6
6
|
import type { BasaltProps } from '../../common/props'
|
|
7
|
-
import { VX } from '../../tokens'
|
|
7
|
+
import { VX, alpha } from '../../tokens'
|
|
8
8
|
import type { ChartMargin } from '../../tokens'
|
|
9
|
+
import { classifyDomain } from '../cursor/resolve'
|
|
9
10
|
import type { CursorResolution } from '../cursor/resolve'
|
|
10
11
|
import { useChartCursor } from '../hooks/useChartCursor'
|
|
11
12
|
import { autoMargin, probeAxisLabels } from '../layout/auto-margin'
|
|
13
|
+
import type { AutoMarginInput } from '../layout/auto-margin'
|
|
14
|
+
import { END_LABEL_GAP, planEndLabels } from '../layout/end-labels'
|
|
12
15
|
import { logTickValues, niceLogDomain } from '../layout/log-ticks'
|
|
13
16
|
import { deriveLegend, deriveTooltipRows } from '../series'
|
|
14
17
|
import type { ChartLegendConfig, ChartSeries } from '../series'
|
|
15
18
|
import { padAutoLower, padAutoUpper } from '../utils/domain'
|
|
16
19
|
import { fmtAxisDate } from '../utils/format'
|
|
17
|
-
import {
|
|
18
|
-
import { AxisBottomDate, AxisLeftNumeric, AxisRightNumeric } from './Axes'
|
|
20
|
+
import { rotatedXLabelPx, smartTicks, smartTicksEvery, xLabelPxFor } from '../utils/ticks'
|
|
21
|
+
import { AxisBottomDate, AxisLeftNumeric, AxisRightNumeric, TICK_FONT_FAMILY } from './Axes'
|
|
19
22
|
import { ChartFrame, resolveLegend } from './ChartFrame'
|
|
20
23
|
import type { ResponsiveChartHeight } from './ChartFrame'
|
|
21
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
compactNumber,
|
|
26
|
+
isTightClass,
|
|
27
|
+
planXLabels,
|
|
28
|
+
resolveMarginFloor,
|
|
29
|
+
resolveYPlacement,
|
|
30
|
+
shouldCompactYLabels,
|
|
31
|
+
yTickCount,
|
|
32
|
+
} from './chart-layout'
|
|
33
|
+
import { useChartContainerClass, useChartMetrics } from './chart-tier'
|
|
22
34
|
import type { ChartState } from './ChartPending'
|
|
23
35
|
import { ChartTooltipFloat, TooltipBody, TooltipHeader, TooltipRow } from './ChartTooltip'
|
|
24
36
|
import { Crosshair, SeriesDot } from './Crosshair'
|
|
@@ -35,9 +47,11 @@ import type { ZoneSpec } from './ZoneRects'
|
|
|
35
47
|
type ContinuousScale = ReturnType<typeof scaleLinear<number>> | ReturnType<typeof scaleLog<number>>
|
|
36
48
|
type PointScale = ReturnType<typeof scalePoint<string>>
|
|
37
49
|
|
|
38
|
-
const DEFAULT_TICKS = 5
|
|
39
50
|
const DEFAULT_AUTO_PAD = 1.1
|
|
40
51
|
|
|
52
|
+
/** Faint tint a dual-axis chart lays over its tick labels: the series colour, kept quiet. */
|
|
53
|
+
const AXIS_TINT_ALPHA = 0.8
|
|
54
|
+
|
|
41
55
|
/** One y-axis, fully described. Collapses the removed `yDomain` / `yAutoMaxFloor` / `yAutoMinCeil` /
|
|
42
56
|
* `yAutoPad` / `numTicksY` / `formatYTick` prop soup into a single object per axis. */
|
|
43
57
|
export type AxisConfig<T> = {
|
|
@@ -195,14 +209,15 @@ export type CartesianChartProps<T> = BasaltProps & {
|
|
|
195
209
|
* bottom gutter deepens by the rotated label's projected height, measured like every other side
|
|
196
210
|
* (`docs/CHARTS-SPEC.md` §1), so nothing clips.
|
|
197
211
|
*
|
|
198
|
-
* This is the phone answer to a
|
|
199
|
-
* the axis would otherwise have to thin away — which is
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
212
|
+
* This is the phone answer to a **categorical** axis whose labels are too wide to repeat
|
|
213
|
+
* horizontally: rotating keeps every tick the axis would otherwise have to thin away — which is
|
|
214
|
+
* why, LEFT UNSET, it is now the phone tier's DEFAULT for a band domain, below
|
|
215
|
+
* `CONTAINER_CLASSES.regular` (`planXLabels`). A time or number domain never auto-rotates (or
|
|
216
|
+
* wraps) — it thins instead, however narrow the plot. Nothing changes at desktop width, and
|
|
217
|
+
* nothing changes for a chart that already passes a value.
|
|
203
218
|
*
|
|
204
219
|
* **`0` is the opt-out** — "never rotate, thin the axis instead" — and is the only way to get
|
|
205
|
-
* the pre-tier behaviour back on a phone-width chart with very wide labels.
|
|
220
|
+
* the pre-tier behaviour back on a phone-width categorical chart with very wide labels.
|
|
206
221
|
*/
|
|
207
222
|
xLabelRotate?: 0 | 45 | 90
|
|
208
223
|
/** Value-range bands on the left scale, drawn behind the marks. */
|
|
@@ -321,6 +336,41 @@ export function resolveAxisDomain<T>(
|
|
|
321
336
|
return [lower, upper]
|
|
322
337
|
}
|
|
323
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Probe one y axis' labels. With no consumer `format`, labels over 4 chars switch to the compact
|
|
341
|
+
* number format (`12500` -> `12.5k`, ladder step 1); an explicit `format` is never touched.
|
|
342
|
+
*/
|
|
343
|
+
function probeYAxis(opts: {
|
|
344
|
+
domain: [number, number]
|
|
345
|
+
ticks: number
|
|
346
|
+
nice: boolean
|
|
347
|
+
scale: 'linear' | 'log' | undefined
|
|
348
|
+
format: ((v: number) => string) | undefined
|
|
349
|
+
}): { labels: string[]; format: (v: number) => string } {
|
|
350
|
+
const { domain, ticks, nice, scale, format } = opts
|
|
351
|
+
const base = probeAxisLabels({
|
|
352
|
+
domain,
|
|
353
|
+
ticks,
|
|
354
|
+
nice,
|
|
355
|
+
...(scale !== undefined && { scale }),
|
|
356
|
+
...(format !== undefined && { format }),
|
|
357
|
+
})
|
|
358
|
+
if (format !== undefined || !shouldCompactYLabels(base.labels)) {
|
|
359
|
+
return base
|
|
360
|
+
}
|
|
361
|
+
const compact = (v: number): string => compactNumber(v) ?? base.format(v)
|
|
362
|
+
return {
|
|
363
|
+
labels: probeAxisLabels({
|
|
364
|
+
domain,
|
|
365
|
+
ticks,
|
|
366
|
+
nice,
|
|
367
|
+
format: compact,
|
|
368
|
+
...(scale !== undefined && { scale }),
|
|
369
|
+
}).labels,
|
|
370
|
+
format: compact,
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
324
374
|
/**
|
|
325
375
|
* One y-axis' real scale, built from its resolved domain — shared by the left and right axis so
|
|
326
376
|
* the log branch (`scaleLog` + `nice` + the 1-2-5 tick override) is written and commented exactly
|
|
@@ -354,6 +404,49 @@ function buildYScale<T>(
|
|
|
354
404
|
})
|
|
355
405
|
}
|
|
356
406
|
|
|
407
|
+
/**
|
|
408
|
+
* `tickLabelPx`'s resolver, split out of a two-deep ternary: a 45° label projects wider than its
|
|
409
|
+
* flat glyph box, so it measures through {@link rotatedXLabelPx} instead of the flat/wrapped
|
|
410
|
+
* numbers every other rotation uses; a wrapped flat label (`wrapWidth` set) uses ITS width, an
|
|
411
|
+
* unwrapped one the plain flat width. `rotate === 90` falls through to the flat/wrapped branch
|
|
412
|
+
* unchanged — `wrapWidth` is only ever set for `rotate === 0` (see its own definition), so a 90°
|
|
413
|
+
* axis always resolves to `flatLabelPx` here, matching the pre-split behaviour exactly.
|
|
414
|
+
*/
|
|
415
|
+
function resolveTickLabelPx(input: {
|
|
416
|
+
rotate: 0 | 45 | 90
|
|
417
|
+
wrapWidth: number | undefined
|
|
418
|
+
xLabels: string[]
|
|
419
|
+
axisFont: number
|
|
420
|
+
flatLabelPx: number
|
|
421
|
+
wrappedLabelPx: number
|
|
422
|
+
}): number {
|
|
423
|
+
const { rotate, wrapWidth, xLabels, axisFont, flatLabelPx, wrappedLabelPx } = input
|
|
424
|
+
if (rotate === 45) return rotatedXLabelPx(xLabels, axisFont)
|
|
425
|
+
return wrapWidth === undefined ? flatLabelPx : wrappedLabelPx
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* `baseMargin`'s resolver, split out of the same two-deep ternary: an unrotated, unwrapped axis
|
|
430
|
+
* keeps the already-measured flat margin; wrapping re-measures for the extra line(s)
|
|
431
|
+
* (`bottomLines`); a 45° rotation reuses its own pre-measured margin ({@link rotated45Margin}); a
|
|
432
|
+
* 90° one measures fresh (no pre-measured twin exists for it, unlike 45°).
|
|
433
|
+
*/
|
|
434
|
+
function resolveBaseMargin(input: {
|
|
435
|
+
rotate: 0 | 45 | 90
|
|
436
|
+
wrapWidth: number | undefined
|
|
437
|
+
flatMargin: ChartMargin
|
|
438
|
+
rotated45Margin: ChartMargin
|
|
439
|
+
marginInput: AutoMarginInput
|
|
440
|
+
xPlanLines: number
|
|
441
|
+
}): ChartMargin {
|
|
442
|
+
const { rotate, wrapWidth, flatMargin, rotated45Margin, marginInput, xPlanLines } = input
|
|
443
|
+
if (rotate === 45) return rotated45Margin
|
|
444
|
+
if (rotate === 90) return autoMargin({ ...marginInput, rotate })
|
|
445
|
+
return wrapWidth === undefined
|
|
446
|
+
? flatMargin
|
|
447
|
+
: autoMargin({ ...marginInput, bottomLines: xPlanLines })
|
|
448
|
+
}
|
|
449
|
+
|
|
357
450
|
/**
|
|
358
451
|
* The cartesian chart primitive — the rung that was missing between `ChartFrame` and raw visx
|
|
359
452
|
* (`docs/CHARTS-SPEC.md` §2).
|
|
@@ -458,7 +551,7 @@ type CartesianPlotProps<T> = Omit<
|
|
|
458
551
|
| 'className'
|
|
459
552
|
| 'style'
|
|
460
553
|
> & {
|
|
461
|
-
plot: { width: number; height: number; hidden: ReadonlySet<string
|
|
554
|
+
plot: { width: number; height: number; hidden: ReadonlySet<string>; legendInHeader: boolean }
|
|
462
555
|
highlighted: string | null
|
|
463
556
|
marginOverride?: Partial<ChartMargin>
|
|
464
557
|
}
|
|
@@ -492,7 +585,9 @@ function CartesianPlot<T>({
|
|
|
492
585
|
// The tier `ChartFrame` resolved from its own MEASURED width. Everything sized by it here is
|
|
493
586
|
// the half a hook cannot reach: the tick font the margins are MEASURED at, and the margin
|
|
494
587
|
// floors themselves (`docs/CHARTS-SPEC.md` §8).
|
|
495
|
-
const tier =
|
|
588
|
+
const tier = useChartMetrics()
|
|
589
|
+
const containerClass = useChartContainerClass()
|
|
590
|
+
const tight = isTightClass(containerClass)
|
|
496
591
|
|
|
497
592
|
const visible = useMemo(() => series.filter((s) => !hidden.has(s.key)), [series, hidden])
|
|
498
593
|
const leftSeries = useMemo(() => visible.filter((s) => s.axis !== 'right'), [visible])
|
|
@@ -526,17 +621,21 @@ function CartesianPlot<T>({
|
|
|
526
621
|
[y2, data, rightSeries, rightBounds],
|
|
527
622
|
)
|
|
528
623
|
|
|
529
|
-
|
|
530
|
-
|
|
624
|
+
// Tick count is a law of the plot height (one per ~44px, 2 under `VX.minPlotHeight`), estimated
|
|
625
|
+
// from the token floors because the real margins depend on the labels these ticks produce. A
|
|
626
|
+
// consumer's `ticks` always wins.
|
|
627
|
+
const estimatedPlotHeight = plot.height - tier.margin.top - tier.margin.bottom
|
|
628
|
+
const leftTicks = y?.ticks ?? yTickCount(estimatedPlotHeight)
|
|
629
|
+
const rightTicks = y2?.ticks ?? yTickCount(estimatedPlotHeight)
|
|
531
630
|
|
|
532
631
|
const { labels: leftLabels, format: leftFormat } = useMemo(
|
|
533
632
|
() =>
|
|
534
|
-
|
|
633
|
+
probeYAxis({
|
|
535
634
|
domain: leftDomain,
|
|
536
635
|
ticks: leftTicks,
|
|
537
636
|
nice: y?.nice ?? false,
|
|
538
|
-
|
|
539
|
-
|
|
637
|
+
scale: y?.scale,
|
|
638
|
+
format: y?.format,
|
|
540
639
|
}),
|
|
541
640
|
[leftDomain, leftTicks, y?.format, y?.nice, y?.scale],
|
|
542
641
|
)
|
|
@@ -545,18 +644,27 @@ function CartesianPlot<T>({
|
|
|
545
644
|
() =>
|
|
546
645
|
rightDomain === null
|
|
547
646
|
? { labels: [], format: (v: number) => String(v) }
|
|
548
|
-
:
|
|
647
|
+
: probeYAxis({
|
|
549
648
|
domain: rightDomain,
|
|
550
649
|
ticks: rightTicks,
|
|
551
650
|
nice: y2?.nice ?? false,
|
|
552
|
-
|
|
553
|
-
|
|
651
|
+
scale: y2?.scale,
|
|
652
|
+
format: y2?.format,
|
|
554
653
|
}),
|
|
555
654
|
[rightDomain, rightTicks, y2?.format, y2?.nice, y2?.scale],
|
|
556
655
|
)
|
|
557
656
|
|
|
558
657
|
const xLabels = useMemo(() => keys.map(formatX), [keys, formatX])
|
|
559
658
|
|
|
659
|
+
// Ladder step 5: at compact/micro the y labels sit inside the plot (micro: no y axis), so the
|
|
660
|
+
// left gutter drops to its floor. An explicit `margin.left` means the consumer laid out an
|
|
661
|
+
// outside gutter — it keeps the outside axis. `CartesianChart` always has a left axis.
|
|
662
|
+
const yPlacement = resolveYPlacement(containerClass, { marginOverrideLeft: marginOverride?.left })
|
|
663
|
+
// A whitespace test misclassified any spaced date format ("Mar 01") as categorical, which then
|
|
664
|
+
// wrapped or rotated a time axis that should only ever thin (§ ladder step 4). Domain kind is
|
|
665
|
+
// the actual law: only a band (category) domain wraps or rotates; time and number domains thin.
|
|
666
|
+
const categorical = tight && classifyDomain(keys) === 'band'
|
|
667
|
+
|
|
560
668
|
/** The horizontal room one x tick label needs: the widest string that could be painted, plus
|
|
561
669
|
* breathing space to its neighbour. Feeds `smartTicks`, which otherwise thinned the axis by a
|
|
562
670
|
* constant that knew nothing about the label. Measured at the tick font the axis will actually
|
|
@@ -567,14 +675,26 @@ function CartesianPlot<T>({
|
|
|
567
675
|
* measured label can never differ between the one that DECIDES and the one that PAINTS. */
|
|
568
676
|
const marginInput = useMemo(
|
|
569
677
|
() => ({
|
|
570
|
-
left: leftLabels,
|
|
678
|
+
left: yPlacement === 'outside' ? leftLabels : [],
|
|
571
679
|
right: rightLabels,
|
|
572
680
|
bottom: xLabels,
|
|
573
681
|
fontPx: tier.axisFont,
|
|
574
|
-
|
|
682
|
+
// An inside-placed y label paints IN the plot, so the left gutter it used to reserve
|
|
683
|
+
// (the phone tier's own margin floor) is dead space — only the inside-label floor applies.
|
|
684
|
+
floor: resolveMarginFloor(yPlacement, tier.margin),
|
|
685
|
+
anchorTerminals: tight,
|
|
575
686
|
...(marginOverride !== undefined && { override: marginOverride }),
|
|
576
687
|
}),
|
|
577
|
-
[
|
|
688
|
+
[
|
|
689
|
+
leftLabels,
|
|
690
|
+
rightLabels,
|
|
691
|
+
xLabels,
|
|
692
|
+
tier.axisFont,
|
|
693
|
+
tier.margin,
|
|
694
|
+
marginOverride,
|
|
695
|
+
tight,
|
|
696
|
+
yPlacement,
|
|
697
|
+
],
|
|
578
698
|
)
|
|
579
699
|
|
|
580
700
|
/** The margin an UNROTATED axis resolves to. It is the final margin in every case except an
|
|
@@ -582,73 +702,122 @@ function CartesianPlot<T>({
|
|
|
582
702
|
* deciding needs an `xMax`, and `xMax` needs a margin, so the unrotated pass breaks the loop. */
|
|
583
703
|
const flatMargin = useMemo(() => autoMargin(marginInput), [marginInput])
|
|
584
704
|
|
|
585
|
-
/** And the margin a 45° axis would resolve to — the OTHER half of the decision
|
|
586
|
-
*
|
|
587
|
-
*
|
|
588
|
-
* (f1) at 320 used to rotate itself into a clipped axis (§8). */
|
|
705
|
+
/** And the margin a 45° axis would resolve to — the OTHER half of the decision, needed only for
|
|
706
|
+
* a categorical (band) domain that still overflows once wrapped (`planXLabels`'s own rotate
|
|
707
|
+
* case). A rotated label reaches into the left gutter, so rotating spends plot width. */
|
|
589
708
|
const rotated45Margin = useMemo(() => autoMargin({ ...marginInput, rotate: 45 }), [marginInput])
|
|
590
709
|
|
|
591
710
|
// An explicit `xLabelRotate` always wins — including `0`, which is the documented "never rotate"
|
|
592
|
-
// opt-out. Only an unset one falls through to the
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
711
|
+
// opt-out. Only an unset one falls through to the domain-driven default: a categorical (band)
|
|
712
|
+
// axis wraps before it rotates, and rotates only when even wrapped it doesn't fit
|
|
713
|
+
// (`planXLabels`). A time or number domain never wraps or rotates — it only thins.
|
|
714
|
+
const flatXMax = Math.max(plot.width - flatMargin.left - flatMargin.right, 0)
|
|
715
|
+
const xPlan = useMemo(
|
|
716
|
+
() => planXLabels({ labels: xLabels, plotWidth: flatXMax, fontPx: tier.axisFont, categorical }),
|
|
717
|
+
[xLabels, flatXMax, tier.axisFont, categorical],
|
|
718
|
+
)
|
|
719
|
+
const rotate = xLabelRotate ?? (categorical ? xPlan.rotate : 0)
|
|
720
|
+
const wrapWidth = rotate === 0 && xPlan.wrap ? xPlan.wrapPx : undefined
|
|
721
|
+
// A 90° label is one line box wide, so the constant floor governs it; a 45° one projects wider.
|
|
722
|
+
const tickLabelPx = resolveTickLabelPx({
|
|
723
|
+
rotate,
|
|
724
|
+
wrapWidth,
|
|
725
|
+
xLabels,
|
|
726
|
+
axisFont: tier.axisFont,
|
|
727
|
+
flatLabelPx: xLabelPx,
|
|
728
|
+
wrappedLabelPx: xPlan.labelPx,
|
|
729
|
+
})
|
|
730
|
+
|
|
731
|
+
const baseMargin = useMemo(
|
|
732
|
+
() =>
|
|
733
|
+
resolveBaseMargin({
|
|
734
|
+
rotate,
|
|
735
|
+
wrapWidth,
|
|
736
|
+
flatMargin,
|
|
737
|
+
rotated45Margin,
|
|
738
|
+
marginInput,
|
|
739
|
+
xPlanLines: xPlan.lines,
|
|
740
|
+
}),
|
|
741
|
+
[flatMargin, rotated45Margin, marginInput, rotate, wrapWidth, xPlan.lines],
|
|
742
|
+
)
|
|
601
743
|
|
|
744
|
+
// End-of-line labels are planned FIRST, against the base margin: the right gutter is reserved
|
|
745
|
+
// only when the labels will actually draw. `yScale` depends on the plot height alone, so it is
|
|
746
|
+
// safe to build before the right margin is settled.
|
|
747
|
+
const yMax = Math.max(plot.height - baseMargin.top - baseMargin.bottom, 0)
|
|
748
|
+
const yScale = useMemo(
|
|
749
|
+
() => buildYScale(y, leftDomain, yMax, leftTicks),
|
|
750
|
+
[y, leftDomain, yMax, leftTicks],
|
|
751
|
+
)
|
|
752
|
+
const endPlan = useMemo(
|
|
753
|
+
() =>
|
|
754
|
+
planEndLabels({
|
|
755
|
+
containerClass,
|
|
756
|
+
hasY2: y2 !== undefined,
|
|
757
|
+
hasMarginRight: marginOverride?.right !== undefined,
|
|
758
|
+
hasHeaderLegend: plot.legendInHeader,
|
|
759
|
+
visible,
|
|
760
|
+
data,
|
|
761
|
+
yScale,
|
|
762
|
+
yMax,
|
|
763
|
+
fontPx: tier.axisFont,
|
|
764
|
+
plotWidth: plot.width - baseMargin.left - baseMargin.right,
|
|
765
|
+
}),
|
|
766
|
+
[
|
|
767
|
+
containerClass,
|
|
768
|
+
y2,
|
|
769
|
+
marginOverride?.right,
|
|
770
|
+
plot.legendInHeader,
|
|
771
|
+
visible,
|
|
772
|
+
data,
|
|
773
|
+
yScale,
|
|
774
|
+
yMax,
|
|
775
|
+
tier.axisFont,
|
|
776
|
+
plot.width,
|
|
777
|
+
baseMargin.left,
|
|
778
|
+
baseMargin.right,
|
|
779
|
+
],
|
|
780
|
+
)
|
|
781
|
+
const endLabels = endPlan?.labels ?? []
|
|
602
782
|
const margin = useMemo(
|
|
603
783
|
() =>
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
: rotate === 45
|
|
607
|
-
? rotated45Margin
|
|
608
|
-
: autoMargin({ ...marginInput, rotate }),
|
|
609
|
-
[flatMargin, rotated45Margin, marginInput, rotate],
|
|
784
|
+
endPlan === null ? baseMargin : { ...baseMargin, right: baseMargin.right + endPlan.gutter },
|
|
785
|
+
[baseMargin, endPlan],
|
|
610
786
|
)
|
|
611
787
|
|
|
612
788
|
// ── Pass 2: the real scales, now that the plot rect is known ────────────────────────────────
|
|
613
789
|
const xMax = Math.max(plot.width - margin.left - margin.right, 0)
|
|
614
|
-
const yMax = Math.max(plot.height - margin.top - margin.bottom, 0)
|
|
615
790
|
|
|
616
791
|
const xScale = useMemo(
|
|
617
792
|
() => scalePoint<string>({ domain: keys, range: [0, xMax], padding: 0.5 }),
|
|
618
793
|
[keys, xMax],
|
|
619
794
|
)
|
|
620
|
-
const yScale = useMemo(
|
|
621
|
-
() => buildYScale(y, leftDomain, yMax, leftTicks),
|
|
622
|
-
[y, leftDomain, yMax, leftTicks],
|
|
623
|
-
)
|
|
624
795
|
const y2Scale = useMemo(
|
|
625
796
|
() => (rightDomain === null ? null : buildYScale(y2, rightDomain, yMax, rightTicks)),
|
|
626
797
|
[y2, rightDomain, yMax, rightTicks],
|
|
627
798
|
)
|
|
628
799
|
|
|
629
800
|
// Resolution order: explicit VALUES win, then an explicit COUNT, then as many as fit.
|
|
630
|
-
const tickValues = useMemo(
|
|
631
|
-
()
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
: smartTicksEvery(keys, xTicks),
|
|
639
|
-
[keys, xMax, xTicks, xTickValues, xLabelPx, rotate],
|
|
640
|
-
)
|
|
801
|
+
const tickValues = useMemo(() => {
|
|
802
|
+
if (xTickValues !== undefined) return [...xTickValues(keys, xMax)]
|
|
803
|
+
if (xTicks !== undefined) return smartTicksEvery(keys, xTicks)
|
|
804
|
+
// Micro: the two terminals only.
|
|
805
|
+
if (containerClass === 'micro')
|
|
806
|
+
return keys.length > 1 ? [keys[0]!, keys[keys.length - 1]!] : keys
|
|
807
|
+
return smartTicks(keys, xMax, rotate === 0 || rotate === 45 ? tickLabelPx : undefined, tight)
|
|
808
|
+
}, [keys, xMax, xTicks, xTickValues, tickLabelPx, rotate, containerClass, tight])
|
|
641
809
|
|
|
810
|
+
const svgRef = useRef<SVGSVGElement>(null)
|
|
642
811
|
const cursor = useChartCursor<T>({
|
|
643
812
|
data,
|
|
644
813
|
chartId,
|
|
645
814
|
getKey: getX,
|
|
646
815
|
xScale,
|
|
647
816
|
marginLeft: margin.left,
|
|
817
|
+
boundaryRef: svgRef,
|
|
648
818
|
...(cursorResolution !== undefined && { resolution: cursorResolution }),
|
|
649
819
|
})
|
|
650
820
|
|
|
651
|
-
const svgRef = useRef<SVGSVGElement>(null)
|
|
652
821
|
const tooltipEnabled = tooltip !== false
|
|
653
822
|
const tooltipCfg = tooltip === false ? undefined : tooltip
|
|
654
823
|
const point = cursor.point
|
|
@@ -668,6 +837,12 @@ function CartesianPlot<T>({
|
|
|
668
837
|
[visible, leftFormat, rightFormat],
|
|
669
838
|
)
|
|
670
839
|
|
|
840
|
+
// Dual axis with exactly one visible series per side: the tick labels carry that series' colour,
|
|
841
|
+
// so the axis says which line it reads. Any other split leaves both axes neutral.
|
|
842
|
+
const tintAxes = y2 !== undefined && leftSeries.length === 1 && rightSeries.length === 1
|
|
843
|
+
const leftTint = tintAxes ? alpha(leftSeries[0]!.color, AXIS_TINT_ALPHA) : undefined
|
|
844
|
+
const rightTint = tintAxes ? alpha(rightSeries[0]!.color, AXIS_TINT_ALPHA) : undefined
|
|
845
|
+
|
|
671
846
|
const rows =
|
|
672
847
|
tooltipEnabled && point !== null ? deriveTooltipRows(tooltipSeries, point, leftFormat) : []
|
|
673
848
|
|
|
@@ -679,8 +854,13 @@ function CartesianPlot<T>({
|
|
|
679
854
|
|
|
680
855
|
// Anchored mode resolves the crosshair's plot-local x into viewport space; following mode uses
|
|
681
856
|
// the pointer position `useChartCursor` already recorded. A follower render always anchors, since
|
|
682
|
-
// `follow`'s pointer-tracking path has no pointer to read on this chart.
|
|
683
|
-
|
|
857
|
+
// `follow`'s pointer-tracking path has no pointer to read on this chart. An unset `follow` also
|
|
858
|
+
// anchors on touch (spec §5) — the readout would otherwise sit under the finger; an EXPLICIT
|
|
859
|
+
// `follow` (true or false) always wins over that default.
|
|
860
|
+
const useAnchoredPosition =
|
|
861
|
+
tooltipCfg?.follow === false ||
|
|
862
|
+
(tooltipCfg?.follow === undefined && cursor.isTouch) ||
|
|
863
|
+
isFollowerRender
|
|
684
864
|
const anchorX = useAnchoredPosition && point !== null && cursorX !== null ? cursorX : null
|
|
685
865
|
const svgRect = anchorX === null ? undefined : svgRef.current?.getBoundingClientRect()
|
|
686
866
|
const tooltipAnchor =
|
|
@@ -704,7 +884,14 @@ function CartesianPlot<T>({
|
|
|
704
884
|
|
|
705
885
|
return (
|
|
706
886
|
<>
|
|
707
|
-
<svg
|
|
887
|
+
<svg
|
|
888
|
+
ref={svgRef}
|
|
889
|
+
width={plot.width}
|
|
890
|
+
height={plot.height}
|
|
891
|
+
// `touch-action` on the plot `<svg>`, not the overlay `<rect>`: Chrome ignores it on an SVG
|
|
892
|
+
// child, so a horizontal scrub was claimed as a pan and cancelled (`R2C-2`).
|
|
893
|
+
style={{ touchAction: 'pan-y' }}
|
|
894
|
+
>
|
|
708
895
|
<Group left={margin.left} top={margin.top}>
|
|
709
896
|
{(y?.grid ?? true) && (
|
|
710
897
|
<GridRows scale={yScale} width={xMax} stroke={VX.grid} numTicks={leftTicks} />
|
|
@@ -762,17 +949,22 @@ function CartesianPlot<T>({
|
|
|
762
949
|
</>
|
|
763
950
|
)}
|
|
764
951
|
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
952
|
+
{yPlacement !== 'none' && (
|
|
953
|
+
<AxisLeftNumeric
|
|
954
|
+
scale={yScale}
|
|
955
|
+
numTicks={leftTicks}
|
|
956
|
+
tickFormat={(v) => leftFormat(Number(v))}
|
|
957
|
+
inside={yPlacement === 'inside'}
|
|
958
|
+
{...(leftTint !== undefined && { tickColor: leftTint })}
|
|
959
|
+
/>
|
|
960
|
+
)}
|
|
770
961
|
{y2Scale !== null && (
|
|
771
962
|
<AxisRightNumeric
|
|
772
963
|
scale={y2Scale}
|
|
773
964
|
left={xMax}
|
|
774
965
|
numTicks={rightTicks}
|
|
775
966
|
tickFormat={(v) => rightFormat(Number(v))}
|
|
967
|
+
{...(rightTint !== undefined && { tickColor: rightTint })}
|
|
776
968
|
/>
|
|
777
969
|
)}
|
|
778
970
|
<AxisBottomDate
|
|
@@ -781,12 +973,34 @@ function CartesianPlot<T>({
|
|
|
781
973
|
tickValues={tickValues}
|
|
782
974
|
tickFormat={(v) => formatX(String(v))}
|
|
783
975
|
{...(rotate !== 0 && { rotate })}
|
|
976
|
+
anchorTerminals={tight && rotate === 0}
|
|
977
|
+
{...(wrapWidth !== undefined && { wrapWidth })}
|
|
784
978
|
/>
|
|
785
979
|
|
|
980
|
+
{endLabels.map(({ series: s, y: labelY }) => (
|
|
981
|
+
<text
|
|
982
|
+
key={`end-${s.key}`}
|
|
983
|
+
x={xMax + END_LABEL_GAP}
|
|
984
|
+
y={labelY}
|
|
985
|
+
dy="0.35em"
|
|
986
|
+
fill={s.color}
|
|
987
|
+
fontFamily={TICK_FONT_FAMILY}
|
|
988
|
+
fontSize={tier.axisFont}
|
|
989
|
+
opacity={
|
|
990
|
+
highlighted === null || s.key === highlighted || s.parent === highlighted ? 1 : 0.25
|
|
991
|
+
}
|
|
992
|
+
pointerEvents="none"
|
|
993
|
+
>
|
|
994
|
+
{s.label}
|
|
995
|
+
</text>
|
|
996
|
+
))}
|
|
997
|
+
|
|
786
998
|
<HoverOverlay
|
|
787
999
|
width={xMax}
|
|
788
1000
|
height={yMax}
|
|
789
1001
|
onMove={cursor.onPointerMove}
|
|
1002
|
+
onDown={cursor.onPointerDown}
|
|
1003
|
+
onUp={cursor.onPointerUp}
|
|
790
1004
|
onLeave={cursor.onPointerLeave}
|
|
791
1005
|
onKeyDown={cursor.onKeyDown}
|
|
792
1006
|
onBlur={cursor.onBlur}
|