waveform-analysis 0.1.71 → 0.1.73
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/README.md +260 -40
- package/dist/assets/{workerRuntime-o5An0M-W.js → workerRuntime-5p-EI-GL.js} +1 -1
- package/dist/index.cjs +2 -1
- package/dist/index.js +3874 -2778
- package/dist/style.css +1 -1
- package/dist/types/components/WaveformChart.vue.d.ts +24 -6
- package/dist/types/components/annotation/WaveformAnnotationLayer.vue.d.ts +2 -0
- package/dist/types/components/annotation/useWaveformChartAnnotations.d.ts +0 -1
- package/dist/types/components/controls/WaveformToolbar.vue.d.ts +7 -0
- package/dist/types/components/controls/chartExportOptions.d.ts +5 -0
- package/dist/types/components/controls/chartSnapshot.d.ts +2 -0
- package/dist/types/components/controls/snapshotTitle.d.ts +2 -0
- package/dist/types/components/controls/toolbarRegistry.d.ts +21 -0
- package/dist/types/components/controls/useChartCommands.d.ts +42 -0
- package/dist/types/components/controls/useChartImageExport.d.ts +17 -0
- package/dist/types/components/controls/useControlMode.d.ts +8 -0
- package/dist/types/components/core/latestTaskScheduler.d.ts +1 -1
- package/dist/types/components/core/layout.d.ts +6 -3
- package/dist/types/components/core/niceScale.d.ts +34 -0
- package/dist/types/components/core/trackLayoutBuilder.d.ts +4 -1
- package/dist/types/components/core/trackPagination.d.ts +1 -1
- package/dist/types/components/core/types.d.ts +5 -0
- package/dist/types/components/core/useWaveformChartController.d.ts +24 -6
- package/dist/types/components/core/useWaveformChartLifecycle.d.ts +2 -2
- package/dist/types/components/core/useWaveformData.d.ts +2 -0
- package/dist/types/components/core/useWaveformLayout.d.ts +4 -1
- package/dist/types/components/core/useWaveformMeasurements.d.ts +2 -0
- package/dist/types/components/core/useWaveformRenderSampling.d.ts +1 -0
- package/dist/types/components/core/useWaveformViewportRestore.d.ts +5 -0
- package/dist/types/components/core/waveformChartTypes.d.ts +5 -0
- package/dist/types/components/core/waveformSamplingSession.d.ts +24 -0
- package/dist/types/components/core/yAxisLabelStyle.d.ts +3 -0
- package/dist/types/components/core/yAxisLayoutMetrics.d.ts +2 -1
- package/dist/types/components/core/yAxisPadding.d.ts +7 -0
- package/dist/types/components/core/yAxisTitleLayout.d.ts +2 -0
- package/dist/types/components/interaction/useExtremaTickViewport.d.ts +5 -0
- package/dist/types/components/interaction/useWaveformViewport.d.ts +1 -0
- package/dist/types/components/rendering/WaveformLegend.vue.d.ts +2 -2
- package/dist/types/components/rendering/WaveformTrack.vue.d.ts +2 -3
- package/dist/types/components/rendering/WaveformTrackAxes.vue.d.ts +1 -1
- package/dist/types/components/rendering/WaveformTrackBackdrop.vue.d.ts +1 -0
- package/dist/types/components/rendering/compactFrame.d.ts +5 -0
- package/dist/types/components/rendering/yAxisTickVisibility.d.ts +11 -0
- package/dist/types/core/lineBoundary.d.ts +4 -0
- package/dist/types/core/rendering.d.ts +4 -0
- package/dist/types/core/renderingStrategies.d.ts +2 -0
- package/dist/types/core/workerSampling/outputBoundaries.d.ts +4 -0
- package/dist/types/data/simulatedWaveforms.d.ts +1 -1
- package/dist/types/data/sx2Channel.d.ts +10 -0
- package/dist/types/demo/DemoYAxisPaddingControls.vue.d.ts +11 -0
- package/dist/types/demo/types.d.ts +7 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/types/chart.d.ts +32 -8
- package/dist/types/types/controls.d.ts +69 -0
- package/dist/types/types/index.d.ts +2 -1
- package/dist/types/utils/formatters.d.ts +1 -0
- package/package.json +7 -6
- package/src/App.vue +61 -14
- package/src/components/WaveformChart.css +10 -1
- package/src/components/WaveformChart.vue +6 -0
- package/src/components/WaveformChartView.vue +27 -26
- package/src/components/annotation/WaveformAnnotationLayer.vue +6 -1
- package/src/components/annotation/useWaveformChartAnnotations.ts +7 -18
- package/src/components/controls/WaveformToolbar.vue +247 -0
- package/src/components/controls/chartExportOptions.ts +32 -0
- package/src/components/controls/chartSnapshot.ts +132 -0
- package/src/components/controls/snapshotTitle.ts +73 -0
- package/src/components/controls/toolbarRegistry.ts +92 -0
- package/src/components/controls/useChartCommands.ts +294 -0
- package/src/components/controls/useChartImageExport.ts +155 -0
- package/src/components/controls/useControlMode.ts +30 -0
- package/src/components/core/latestTaskScheduler.ts +14 -2
- package/src/components/core/layout.ts +49 -36
- package/src/components/core/niceScale.ts +151 -0
- package/src/components/core/trackLayoutBuilder.ts +41 -17
- package/src/components/core/trackPagination.ts +3 -3
- package/src/components/core/types.ts +5 -0
- package/src/components/core/useWaveformChartController.ts +94 -3
- package/src/components/core/useWaveformChartLifecycle.ts +11 -111
- package/src/components/core/useWaveformData.ts +4 -0
- package/src/components/core/useWaveformLayout.ts +25 -5
- package/src/components/core/useWaveformMeasurements.ts +63 -0
- package/src/components/core/useWaveformRenderSampling.ts +80 -59
- package/src/components/core/useWaveformViewportRestore.ts +105 -0
- package/src/components/core/useWaveformYAxisLayout.ts +3 -2
- package/src/components/core/waveformChartTypes.ts +5 -0
- package/src/components/core/waveformSamplingSession.ts +49 -0
- package/src/components/core/workerSamplingClient.ts +38 -6
- package/src/components/core/yAxisLabelStyle.ts +10 -0
- package/src/components/core/yAxisLayoutMetrics.ts +11 -9
- package/src/components/core/yAxisPadding.ts +60 -0
- package/src/components/core/yAxisTitleLayout.ts +11 -12
- package/src/components/interaction/useExtremaTickViewport.ts +43 -0
- package/src/components/interaction/useWaveformViewport.ts +11 -2
- package/src/components/rendering/WaveformTrack.css +1 -2
- package/src/components/rendering/WaveformTrack.vue +5 -12
- package/src/components/rendering/WaveformTrackAxes.vue +66 -19
- package/src/components/rendering/WaveformTrackBackdrop.vue +8 -3
- package/src/components/rendering/compactFrame.ts +31 -0
- package/src/components/rendering/yAxisTickVisibility.ts +156 -0
- package/src/core/lineBoundary.ts +24 -0
- package/src/core/rendering.ts +48 -25
- package/src/core/renderingStrategies.ts +19 -13
- package/src/core/waveformPointSource.ts +23 -1
- package/src/core/workerSampling/outputBoundaries.ts +32 -0
- package/src/core/workerSampling/repository.ts +26 -18
- package/src/core/workerSampling/repositoryDataset.ts +33 -1
- package/src/core/workerSampling/workerRuntime.ts +13 -3
- package/src/data/simulatedWaveforms.ts +67 -2
- package/src/data/sx2Channel.ts +315 -0
- package/src/demo/DemoChartHost.vue +5 -1
- package/src/demo/DemoGridFrameControls.vue +2 -0
- package/src/demo/DemoViewControls.vue +5 -2
- package/src/demo/DemoYAxisPaddingControls.vue +53 -0
- package/src/demo/embedBridge.ts +85 -0
- package/src/demo/types.ts +7 -0
- package/src/index.ts +19 -0
- package/src/types/chart.ts +34 -8
- package/src/types/controls.ts +80 -0
- package/src/types/index.ts +18 -0
- package/src/utils/formatters.ts +2 -1
- package/src/views/WasmSamplingDemo.vue +8 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ECharts-compatible Nice Number algorithm for axis tick calculation
|
|
3
|
+
* Based on "Nice Numbers for Graph Labels" from Graphics Gems
|
|
4
|
+
*
|
|
5
|
+
* References:
|
|
6
|
+
* - https://github.com/cenfun/nice-ticks
|
|
7
|
+
* - https://github.com/qianfan-Zhao/nice_number
|
|
8
|
+
* - https://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* 将数字转换为"优雅"的数字(1, 2, 5 系列)
|
|
13
|
+
* @param value - 原始值
|
|
14
|
+
* @param round - 是否向上取整
|
|
15
|
+
* @returns 优雅的数字
|
|
16
|
+
*/
|
|
17
|
+
function niceNumber(value: number, round: boolean): number {
|
|
18
|
+
if (value === 0) return 0
|
|
19
|
+
|
|
20
|
+
const exponent = Math.floor(Math.log10(Math.abs(value)))
|
|
21
|
+
const fraction = Math.abs(value) / Math.pow(10, exponent)
|
|
22
|
+
|
|
23
|
+
let niceFraction: number
|
|
24
|
+
if (round) {
|
|
25
|
+
// 向上取整到 1, 2, 5, 10
|
|
26
|
+
if (fraction < 1.5) niceFraction = 1
|
|
27
|
+
else if (fraction < 3) niceFraction = 2
|
|
28
|
+
else if (fraction < 7) niceFraction = 5
|
|
29
|
+
else niceFraction = 10
|
|
30
|
+
} else {
|
|
31
|
+
// 向下取整到 1, 2, 5, 10
|
|
32
|
+
if (fraction <= 1) niceFraction = 1
|
|
33
|
+
else if (fraction <= 2) niceFraction = 2
|
|
34
|
+
else if (fraction <= 5) niceFraction = 5
|
|
35
|
+
else niceFraction = 10
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return niceFraction * Math.pow(10, exponent) * Math.sign(value)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export interface NiceScaleResult {
|
|
42
|
+
/** 扩展后的最小值 */
|
|
43
|
+
niceMin: number
|
|
44
|
+
/** 扩展后的最大值 */
|
|
45
|
+
niceMax: number
|
|
46
|
+
/** 刻度间隔 */
|
|
47
|
+
tickSpacing: number
|
|
48
|
+
/** 刻度值数组 */
|
|
49
|
+
ticks: number[]
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* 计算 ECharts 风格的优雅刻度
|
|
54
|
+
* @param min - 数据最小值
|
|
55
|
+
* @param max - 数据最大值
|
|
56
|
+
* @param maxTicks - 期望的最大刻度数(类似 ECharts 的 splitNumber,默认 5)
|
|
57
|
+
* @returns 刻度计算结果
|
|
58
|
+
*/
|
|
59
|
+
export function calculateNiceScale(min: number, max: number, maxTicks = 5): NiceScaleResult {
|
|
60
|
+
// 处理边界情况
|
|
61
|
+
if (!Number.isFinite(min) || !Number.isFinite(max)) {
|
|
62
|
+
return { niceMin: 0, niceMax: 1, tickSpacing: 1, ticks: [0, 1] }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (min === max) {
|
|
66
|
+
if (min === 0) {
|
|
67
|
+
// 特殊处理零点
|
|
68
|
+
return {
|
|
69
|
+
niceMin: -1,
|
|
70
|
+
niceMax: 1,
|
|
71
|
+
tickSpacing: 0.5,
|
|
72
|
+
ticks: [-1, -0.5, 0, 0.5, 1],
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const value = min
|
|
76
|
+
const spacing = Math.abs(value) * 0.2
|
|
77
|
+
return {
|
|
78
|
+
niceMin: value - spacing,
|
|
79
|
+
niceMax: value + spacing,
|
|
80
|
+
tickSpacing: spacing,
|
|
81
|
+
ticks: [value - spacing, value, value + spacing],
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 确保 min < max
|
|
86
|
+
if (min > max) {
|
|
87
|
+
;[min, max] = [max, min]
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const range = max - min
|
|
91
|
+
|
|
92
|
+
// 计算优雅的刻度间隔
|
|
93
|
+
const roughTickSpacing = range / Math.max(1, maxTicks - 1)
|
|
94
|
+
const tickSpacing = niceNumber(roughTickSpacing, true)
|
|
95
|
+
|
|
96
|
+
// 计算优雅的边界
|
|
97
|
+
const niceMin = Math.floor(min / tickSpacing) * tickSpacing
|
|
98
|
+
const niceMax = Math.ceil(max / tickSpacing) * tickSpacing
|
|
99
|
+
|
|
100
|
+
// 生成刻度值
|
|
101
|
+
const ticks: number[] = []
|
|
102
|
+
const tickCount = Math.round((niceMax - niceMin) / tickSpacing) + 1
|
|
103
|
+
|
|
104
|
+
// 防止刻度过多(安全检查)
|
|
105
|
+
if (tickCount > 100) {
|
|
106
|
+
// 退回到简单的线性刻度
|
|
107
|
+
const safeTickCount = Math.min(maxTicks + 2, 10)
|
|
108
|
+
const safeSpacing = (max - min) / (safeTickCount - 1)
|
|
109
|
+
for (let i = 0; i < safeTickCount; i++) {
|
|
110
|
+
ticks.push(min + safeSpacing * i)
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
niceMin: min,
|
|
114
|
+
niceMax: max,
|
|
115
|
+
tickSpacing: safeSpacing,
|
|
116
|
+
ticks,
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 生成刻度值(处理浮点数精度问题)
|
|
121
|
+
for (let i = 0; i < tickCount; i++) {
|
|
122
|
+
const tick = niceMin + tickSpacing * i
|
|
123
|
+
// 修正浮点数误差 - 使用更智能的四舍五入
|
|
124
|
+
// 确定合适的精度(基于刻度间隔的量级)
|
|
125
|
+
const precision = Math.max(0, -Math.floor(Math.log10(Math.abs(tickSpacing))) + 1)
|
|
126
|
+
const roundedTick = Number(tick.toFixed(precision))
|
|
127
|
+
ticks.push(roundedTick)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
// Keep floating-point multiplication from shrinking the enclosing data domain.
|
|
132
|
+
niceMin: Math.min(min, ticks[0] ?? niceMin),
|
|
133
|
+
niceMax: Math.max(max, ticks.at(-1) ?? niceMax),
|
|
134
|
+
tickSpacing,
|
|
135
|
+
ticks,
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* ECharts 兼容的刻度计算(与 resolveYAxisTicks 接口兼容)
|
|
141
|
+
*/
|
|
142
|
+
export function resolveYAxisTicksECharts(
|
|
143
|
+
domain: [number, number],
|
|
144
|
+
tickCount = 5,
|
|
145
|
+
): { domain: [number, number]; values: number[] } {
|
|
146
|
+
const result = calculateNiceScale(domain[0], domain[1], tickCount)
|
|
147
|
+
return {
|
|
148
|
+
domain: [result.niceMin, result.niceMax],
|
|
149
|
+
values: result.ticks,
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { withMaximumTick, type YAxisPaddingOptions } from './yAxisPadding'
|
|
2
|
+
import { selectSeriesRenderSourcePoints } from '../../core/rendering'
|
|
3
|
+
import { pointSourceFromPoints } from '../../core/waveformPointSource'
|
|
1
4
|
import {
|
|
2
5
|
curveStep,
|
|
3
6
|
curveStepAfter,
|
|
@@ -8,10 +11,7 @@ import {
|
|
|
8
11
|
type ZoomTransform,
|
|
9
12
|
} from 'd3'
|
|
10
13
|
|
|
11
|
-
import {
|
|
12
|
-
selectSeriesRenderPoints,
|
|
13
|
-
type ResolvedWaveformRenderingOptions,
|
|
14
|
-
} from '../../core/rendering'
|
|
14
|
+
import { type ResolvedWaveformRenderingOptions } from '../../core/rendering'
|
|
15
15
|
import type {
|
|
16
16
|
WaveformDisplayMode,
|
|
17
17
|
WaveformOverlayMode,
|
|
@@ -36,6 +36,7 @@ import type { DisplayTrack, TrackLayout, WaveformYAxisLayout } from './types'
|
|
|
36
36
|
import { applyXDomainStrategy } from './xDomain'
|
|
37
37
|
import { alignLeftYAxisTitles } from './yAxisTitleLayout'
|
|
38
38
|
import { measureYAxisTextWidth } from './yAxisTextWidth'
|
|
39
|
+
import { resolveYAxisLabelBandWidth } from './yAxisLabelStyle'
|
|
39
40
|
import {
|
|
40
41
|
alignIntegerZoomDomain,
|
|
41
42
|
normalizeIntegerZoomDomain,
|
|
@@ -43,7 +44,6 @@ import {
|
|
|
43
44
|
formatIntegerZoomLabel,
|
|
44
45
|
} from '../interaction/integerZoom'
|
|
45
46
|
import {
|
|
46
|
-
Y_AXIS_LABEL_BAND_WIDTH,
|
|
47
47
|
Y_AXIS_LABEL_GAP,
|
|
48
48
|
Y_AXIS_OUTER_PADDING,
|
|
49
49
|
Y_AXIS_RIGHT_LABEL_OFFSET,
|
|
@@ -61,6 +61,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
61
61
|
overlayMode: WaveformOverlayMode
|
|
62
62
|
independentTransforms: ZoomTransform[]
|
|
63
63
|
sharedZoomDomain: [number, number]
|
|
64
|
+
effectiveXDomains?: Record<string, [number, number]>
|
|
64
65
|
initialXDomain?: [number, number]
|
|
65
66
|
initialXDomains?: Record<string, [number, number]>
|
|
66
67
|
xDomainStrategy?: WaveformXDomainStrategy
|
|
@@ -72,6 +73,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
72
73
|
xAxisLabelFormatter?: WaveformXAxisLabelFormatter
|
|
73
74
|
yAxisSplitNumber?: number
|
|
74
75
|
yAxisNice?: boolean
|
|
76
|
+
yAxisPadding?: YAxisPaddingOptions
|
|
75
77
|
yAxisUpperPaddingRatio?: number
|
|
76
78
|
rendering: ResolvedWaveformRenderingOptions
|
|
77
79
|
/** Latest sampling result for SVG lines only; source series remain complete for interaction. */
|
|
@@ -80,7 +82,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
80
82
|
yAxisLabelX: number
|
|
81
83
|
yLabel?: string
|
|
82
84
|
yAxisSlots?: readonly YAxisSlot[]
|
|
83
|
-
|
|
85
|
+
showEmptyTracks: boolean
|
|
84
86
|
useNonEmptyBottomTracks?: boolean
|
|
85
87
|
compactYAxisLayout?: boolean
|
|
86
88
|
showAxisUnits?: boolean
|
|
@@ -91,6 +93,7 @@ function resolveIndependentXDomain(
|
|
|
91
93
|
seriesId: string,
|
|
92
94
|
options: BuildTrackLayoutsOptions,
|
|
93
95
|
): [number, number] {
|
|
96
|
+
if (options.effectiveXDomains?.[track.id]) return options.effectiveXDomains[track.id]
|
|
94
97
|
const strategy = options.xDomainStrategy ?? { type: 'data' }
|
|
95
98
|
const explicitDomain =
|
|
96
99
|
options.initialXDomains?.[track.id] ??
|
|
@@ -115,13 +118,12 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
115
118
|
|
|
116
119
|
const layouts: TrackLayout[] = visibleCells.flatMap((cell, index) => {
|
|
117
120
|
const isEmpty = !cell.series || cell.series.series.length === 0
|
|
118
|
-
if (!cell.series &&
|
|
119
|
-
return []
|
|
121
|
+
if (!cell.series && !options.showEmptyTracks) return []
|
|
120
122
|
const displayTrack: DisplayTrack = cell.series ?? {
|
|
121
123
|
id: `empty-grid-slot-${cell.slotIndex}`,
|
|
122
124
|
series: [],
|
|
123
125
|
visibleSeries: [],
|
|
124
|
-
xDomain:
|
|
126
|
+
xDomain: options.initialXDomain ?? options.sharedZoomDomain,
|
|
125
127
|
yDomain: [0, 1],
|
|
126
128
|
}
|
|
127
129
|
const hasVisibleSeries = !isEmpty && displayTrack.visibleSeries.length > 0
|
|
@@ -145,7 +147,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
145
147
|
options.fixedYDomain,
|
|
146
148
|
options.fixedYDomains,
|
|
147
149
|
options.yDomains,
|
|
148
|
-
options.yAxisUpperPaddingRatio,
|
|
150
|
+
options.yAxisPadding ?? options.yAxisUpperPaddingRatio,
|
|
149
151
|
)
|
|
150
152
|
const sideIndexes = { left: 0, right: 0 }
|
|
151
153
|
const sideOffsets = { left: 0, right: 0 }
|
|
@@ -155,7 +157,19 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
155
157
|
const resolvedTicks = resolveYAxisTicks(group.domain, tickCount, options.yAxisNice !== false)
|
|
156
158
|
const scale = scaleLinear(resolvedTicks.domain, [cell.plotHeight, 0])
|
|
157
159
|
const majorTicks = resolvedTicks.values
|
|
158
|
-
const
|
|
160
|
+
const maximumTick =
|
|
161
|
+
group.maximumTick !== undefined &&
|
|
162
|
+
group.maximumTick >= resolvedTicks.domain[0] &&
|
|
163
|
+
group.maximumTick <= resolvedTicks.domain[1]
|
|
164
|
+
? group.maximumTick
|
|
165
|
+
: undefined
|
|
166
|
+
const minimumTick =
|
|
167
|
+
group.minimumTick !== undefined &&
|
|
168
|
+
group.minimumTick >= resolvedTicks.domain[0] &&
|
|
169
|
+
group.minimumTick <= resolvedTicks.domain[1]
|
|
170
|
+
? group.minimumTick
|
|
171
|
+
: undefined
|
|
172
|
+
const tickValues = withMaximumTick(majorTicks, maximumTick, minimumTick)
|
|
159
173
|
const { tickTextWidth } = axisTextMetrics(
|
|
160
174
|
scale.domain() as [number, number],
|
|
161
175
|
false,
|
|
@@ -167,7 +181,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
167
181
|
tickTextWidth +
|
|
168
182
|
Y_AXIS_TICK_PADDING +
|
|
169
183
|
Y_AXIS_LABEL_GAP +
|
|
170
|
-
|
|
184
|
+
resolveYAxisLabelBandWidth(options.yAxisPadding?.labelTextStyle) +
|
|
171
185
|
Y_AXIS_OUTER_PADDING
|
|
172
186
|
const slot = options.yAxisSlots?.find(
|
|
173
187
|
(candidate) => candidate.side === group.side && candidate.sideIndex === sideIndex,
|
|
@@ -178,7 +192,10 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
178
192
|
? -sideOffsets.left
|
|
179
193
|
: cell.width + sideOffsets.right
|
|
180
194
|
const labelDistance =
|
|
181
|
-
tickTextWidth +
|
|
195
|
+
tickTextWidth +
|
|
196
|
+
Y_AXIS_TICK_PADDING +
|
|
197
|
+
Y_AXIS_LABEL_GAP +
|
|
198
|
+
resolveYAxisLabelBandWidth(options.yAxisPadding?.labelTextStyle) / 2
|
|
182
199
|
const labelX =
|
|
183
200
|
(slot
|
|
184
201
|
? (group.side === 'left' ? 0 : cell.width) + slot.labelOffset
|
|
@@ -191,6 +208,8 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
191
208
|
x,
|
|
192
209
|
labelX,
|
|
193
210
|
scale,
|
|
211
|
+
maximumTick,
|
|
212
|
+
minimumTick,
|
|
194
213
|
majorTicks,
|
|
195
214
|
minorTicks: buildMinorTicks(majorTicks, 2),
|
|
196
215
|
tickValues,
|
|
@@ -207,7 +226,11 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
207
226
|
)
|
|
208
227
|
const xAxisLabelFormatter =
|
|
209
228
|
options.xAxisLabelFormatter ?? (options.integerZoom ? formatIntegerZoomLabel : undefined)
|
|
210
|
-
const yMajorTicks =
|
|
229
|
+
const yMajorTicks =
|
|
230
|
+
yAxes[0]?.majorTicks ??
|
|
231
|
+
(isEmpty
|
|
232
|
+
? fallbackYScale.ticks(resolveYAxisTickCount(cell.plotHeight, options.yAxisSplitNumber))
|
|
233
|
+
: [])
|
|
211
234
|
const yAxisTickValues = yAxes[0]?.tickValues ?? []
|
|
212
235
|
const domain = xScale.domain() as [number, number]
|
|
213
236
|
const endpointLabels = {
|
|
@@ -225,8 +248,8 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
225
248
|
axis.seriesList.some((series) => series.id === trackSeries.id),
|
|
226
249
|
)
|
|
227
250
|
const seriesYScale = yAxis?.scale ?? yScale
|
|
228
|
-
const renderPoints =
|
|
229
|
-
trackSeries.points,
|
|
251
|
+
const renderPoints = selectSeriesRenderSourcePoints(
|
|
252
|
+
trackSeries.source ?? pointSourceFromPoints(trackSeries.points),
|
|
230
253
|
domain,
|
|
231
254
|
cell.width,
|
|
232
255
|
options.rendering,
|
|
@@ -278,7 +301,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
278
301
|
xAxisLabelFormatter,
|
|
279
302
|
xMinorTicks: buildMinorTicks(xMajorTicks, 3, domain),
|
|
280
303
|
yMajorTicks,
|
|
281
|
-
yMinorTicks: yAxes[0]?.minorTicks ?? [],
|
|
304
|
+
yMinorTicks: yAxes[0]?.minorTicks ?? (isEmpty ? buildMinorTicks(yMajorTicks, 2) : []),
|
|
282
305
|
yAxisTickValues,
|
|
283
306
|
xAxisTickValues,
|
|
284
307
|
endpointLabels,
|
|
@@ -297,6 +320,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
297
320
|
})
|
|
298
321
|
|
|
299
322
|
alignLeftYAxisTitles(layouts, options.yLabel, {
|
|
323
|
+
labelTextStyle: options.yAxisPadding?.labelTextStyle,
|
|
300
324
|
showUnits: options.showAxisUnits,
|
|
301
325
|
compact: options.compactYAxisLayout,
|
|
302
326
|
measureTextWidth: options.compactYAxisLayout ? measureYAxisTextWidth : undefined,
|
|
@@ -27,14 +27,14 @@ export const createFrameNumberResolver =
|
|
|
27
27
|
getFrameNumbers: () => Record<string, string | number> | undefined,
|
|
28
28
|
getTracks: () => DisplayTrack[],
|
|
29
29
|
) =>
|
|
30
|
-
(trackId: string): string | number | undefined => {
|
|
30
|
+
(trackId: string, slotIndex = 0): string | number | undefined => {
|
|
31
31
|
const tracks = getTracks()
|
|
32
32
|
const trackIndex = tracks.findIndex((track) => track.id === trackId)
|
|
33
33
|
return resolveTrackFrameNumber(
|
|
34
34
|
getFrameNumber(),
|
|
35
35
|
getFrameNumbers(),
|
|
36
|
-
tracks.length,
|
|
37
|
-
trackIndex >= 0 ? trackIndex :
|
|
36
|
+
Math.max(tracks.length, slotIndex + 1),
|
|
37
|
+
trackIndex >= 0 ? trackIndex : slotIndex,
|
|
38
38
|
trackId,
|
|
39
39
|
)
|
|
40
40
|
}
|
|
@@ -27,6 +27,8 @@ export interface DisplaySeries {
|
|
|
27
27
|
points: WaveformPoint[]
|
|
28
28
|
source?: WaveformPointSource
|
|
29
29
|
xDomain: [number, number]
|
|
30
|
+
rawYMaximum?: number
|
|
31
|
+
rawYMinimum?: number
|
|
30
32
|
yDomain: [number, number]
|
|
31
33
|
hasErrorPoints: boolean
|
|
32
34
|
}
|
|
@@ -38,6 +40,7 @@ export interface DisplayTrack {
|
|
|
38
40
|
/** Series currently participating in layout, rendering, and interaction. */
|
|
39
41
|
visibleSeries: DisplaySeries[]
|
|
40
42
|
xDomain: [number, number]
|
|
43
|
+
rawYMaximum?: number
|
|
41
44
|
yDomain: [number, number]
|
|
42
45
|
}
|
|
43
46
|
|
|
@@ -51,6 +54,8 @@ export interface TrackSeriesPath {
|
|
|
51
54
|
}
|
|
52
55
|
|
|
53
56
|
export interface WaveformYAxisLayout {
|
|
57
|
+
maximumTick?: number
|
|
58
|
+
minimumTick?: number
|
|
54
59
|
index: number
|
|
55
60
|
side: 'left' | 'right'
|
|
56
61
|
x: number
|
|
@@ -1,7 +1,13 @@
|
|
|
1
|
+
import { chartExportOptions } from '../controls/chartExportOptions'
|
|
2
|
+
import { useChartImageExport } from '../controls/useChartImageExport'
|
|
3
|
+
import { useControlMode } from '../controls/useControlMode'
|
|
4
|
+
import { useChartCommands } from '../controls/useChartCommands'
|
|
1
5
|
import { zoomIdentity, type ZoomTransform } from 'd3'
|
|
2
6
|
import {
|
|
3
7
|
computed,
|
|
4
8
|
reactive,
|
|
9
|
+
nextTick,
|
|
10
|
+
onBeforeUnmount,
|
|
5
11
|
markRaw,
|
|
6
12
|
ref,
|
|
7
13
|
shallowReactive,
|
|
@@ -44,9 +50,15 @@ function assignElement<T extends Element>(
|
|
|
44
50
|
}
|
|
45
51
|
|
|
46
52
|
export function useWaveformChartController(
|
|
47
|
-
|
|
53
|
+
inputProps: ResolvedWaveformChartProps,
|
|
48
54
|
emit: WaveformChartEmit,
|
|
49
55
|
) {
|
|
56
|
+
const mode = useControlMode(inputProps, emit)
|
|
57
|
+
const props = reactive({
|
|
58
|
+
...toRefs(inputProps),
|
|
59
|
+
interactionMode: mode.mode,
|
|
60
|
+
}) as ResolvedWaveformChartProps
|
|
61
|
+
const boundaries = shallowRef<Record<string, [number, number]>>({})
|
|
50
62
|
const container = ref<HTMLDivElement>()
|
|
51
63
|
const svgElement = ref<SVGSVGElement>()
|
|
52
64
|
const titleMeasureElement = ref<HTMLSpanElement>()
|
|
@@ -137,6 +149,7 @@ export function useWaveformChartController(
|
|
|
137
149
|
independentYDomains,
|
|
138
150
|
annotationInteraction: markRaw(annotationInteraction),
|
|
139
151
|
linePointOverrides,
|
|
152
|
+
boundaries,
|
|
140
153
|
})
|
|
141
154
|
const {
|
|
142
155
|
chartSeries,
|
|
@@ -164,7 +177,7 @@ export function useWaveformChartController(
|
|
|
164
177
|
presentation.titleAreaStyle,
|
|
165
178
|
)
|
|
166
179
|
|
|
167
|
-
useWaveformRenderSampling({
|
|
180
|
+
const sampling = useWaveformRenderSampling({
|
|
168
181
|
props,
|
|
169
182
|
emit,
|
|
170
183
|
instanceId: clipPathId,
|
|
@@ -229,6 +242,7 @@ export function useWaveformChartController(
|
|
|
229
242
|
})
|
|
230
243
|
|
|
231
244
|
const viewport = useWaveformViewport({
|
|
245
|
+
clearBoundaries: (index) => commands.clearBoundaries(index),
|
|
232
246
|
props,
|
|
233
247
|
emit,
|
|
234
248
|
selection,
|
|
@@ -272,7 +286,50 @@ export function useWaveformChartController(
|
|
|
272
286
|
resolveTrackAtPointer: annotations.resolveTrackAtPointer,
|
|
273
287
|
})
|
|
274
288
|
|
|
289
|
+
const cancelControls = () => {
|
|
290
|
+
viewport.cancelViewportDrag()
|
|
291
|
+
zoom.cancelPendingZoom()
|
|
292
|
+
hover.clearHover()
|
|
293
|
+
annotations.cancelAnnotation()
|
|
294
|
+
}
|
|
295
|
+
const commands = useChartCommands({
|
|
296
|
+
props,
|
|
297
|
+
emit,
|
|
298
|
+
layout,
|
|
299
|
+
mode,
|
|
300
|
+
boundaries,
|
|
301
|
+
independentTransforms,
|
|
302
|
+
sharedTransform,
|
|
303
|
+
independentYDomains,
|
|
304
|
+
sharedYDomains,
|
|
305
|
+
cancel: cancelControls,
|
|
306
|
+
configure: zoom.configureZoom,
|
|
307
|
+
legacyReset: viewport.resetViewport,
|
|
308
|
+
legacySet: viewport.setViewportDomain,
|
|
309
|
+
})
|
|
310
|
+
watch(
|
|
311
|
+
[mode.mode, () => props.zoomable, () => props.pannable, () => props.annotationsVisible],
|
|
312
|
+
cancelControls,
|
|
313
|
+
{ flush: 'sync' },
|
|
314
|
+
)
|
|
315
|
+
watch([currentPage, () => props.displayMode], () => {
|
|
316
|
+
boundaries.value = {}
|
|
317
|
+
cancelControls()
|
|
318
|
+
})
|
|
319
|
+
watch(
|
|
320
|
+
[
|
|
321
|
+
() => props.initialXDomain,
|
|
322
|
+
() => props.initialXDomains,
|
|
323
|
+
hiddenSeriesIdSet,
|
|
324
|
+
() => props.xDomainStrategy,
|
|
325
|
+
],
|
|
326
|
+
commands.restoreInitialBoundaries,
|
|
327
|
+
{ deep: true },
|
|
328
|
+
)
|
|
329
|
+
onBeforeUnmount(cancelControls)
|
|
330
|
+
|
|
275
331
|
const lifecycle = useWaveformChartLifecycle({
|
|
332
|
+
preserveFullViewport: () => Object.keys(boundaries.value).length > 0,
|
|
276
333
|
props,
|
|
277
334
|
emit,
|
|
278
335
|
container,
|
|
@@ -317,7 +374,39 @@ export function useWaveformChartController(
|
|
|
317
374
|
clearZoomBindings: zoom.clearZoomBindings,
|
|
318
375
|
})
|
|
319
376
|
handleBeforeDataReferenceChange = lifecycle.handleBeforeDataReferenceChange
|
|
320
|
-
handleDataReferenceChange =
|
|
377
|
+
handleDataReferenceChange = () => {
|
|
378
|
+
boundaries.value = {}
|
|
379
|
+
viewport.cancelViewportDrag()
|
|
380
|
+
lifecycle.handleDataReferenceChange()
|
|
381
|
+
void nextTick(zoom.configureZoom)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const imageExport = useChartImageExport({
|
|
385
|
+
container: () => container.value,
|
|
386
|
+
svg: () => svgElement.value,
|
|
387
|
+
size: () => ({
|
|
388
|
+
width: chartWidth.value,
|
|
389
|
+
height: chartHeight.value,
|
|
390
|
+
titleHeight: titleAreaHeight.value,
|
|
391
|
+
}),
|
|
392
|
+
version: () => [
|
|
393
|
+
props.data,
|
|
394
|
+
JSON.stringify({
|
|
395
|
+
...chartExportOptions(props),
|
|
396
|
+
hidden: [...hiddenSeriesIdSet.value],
|
|
397
|
+
width: chartWidth.value,
|
|
398
|
+
height: chartHeight.value,
|
|
399
|
+
titleHeight: titleAreaHeight.value,
|
|
400
|
+
page: currentPage.value,
|
|
401
|
+
shared: sharedTransform.value,
|
|
402
|
+
independent: independentTransforms.value,
|
|
403
|
+
sharedY: sharedYDomains.value,
|
|
404
|
+
independentY: independentYDomains.value,
|
|
405
|
+
boundaries: boundaries.value,
|
|
406
|
+
}),
|
|
407
|
+
],
|
|
408
|
+
ready: sampling.ready,
|
|
409
|
+
})
|
|
321
410
|
|
|
322
411
|
return reactive({
|
|
323
412
|
...toRefs(props),
|
|
@@ -329,6 +418,8 @@ export function useWaveformChartController(
|
|
|
329
418
|
...annotations,
|
|
330
419
|
...viewport,
|
|
331
420
|
...hover,
|
|
421
|
+
...commands,
|
|
422
|
+
...imageExport,
|
|
332
423
|
margin,
|
|
333
424
|
getPageSize,
|
|
334
425
|
currentPage,
|