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.
Files changed (122) hide show
  1. package/README.md +260 -40
  2. package/dist/assets/{workerRuntime-o5An0M-W.js → workerRuntime-5p-EI-GL.js} +1 -1
  3. package/dist/index.cjs +2 -1
  4. package/dist/index.js +3874 -2778
  5. package/dist/style.css +1 -1
  6. package/dist/types/components/WaveformChart.vue.d.ts +24 -6
  7. package/dist/types/components/annotation/WaveformAnnotationLayer.vue.d.ts +2 -0
  8. package/dist/types/components/annotation/useWaveformChartAnnotations.d.ts +0 -1
  9. package/dist/types/components/controls/WaveformToolbar.vue.d.ts +7 -0
  10. package/dist/types/components/controls/chartExportOptions.d.ts +5 -0
  11. package/dist/types/components/controls/chartSnapshot.d.ts +2 -0
  12. package/dist/types/components/controls/snapshotTitle.d.ts +2 -0
  13. package/dist/types/components/controls/toolbarRegistry.d.ts +21 -0
  14. package/dist/types/components/controls/useChartCommands.d.ts +42 -0
  15. package/dist/types/components/controls/useChartImageExport.d.ts +17 -0
  16. package/dist/types/components/controls/useControlMode.d.ts +8 -0
  17. package/dist/types/components/core/latestTaskScheduler.d.ts +1 -1
  18. package/dist/types/components/core/layout.d.ts +6 -3
  19. package/dist/types/components/core/niceScale.d.ts +34 -0
  20. package/dist/types/components/core/trackLayoutBuilder.d.ts +4 -1
  21. package/dist/types/components/core/trackPagination.d.ts +1 -1
  22. package/dist/types/components/core/types.d.ts +5 -0
  23. package/dist/types/components/core/useWaveformChartController.d.ts +24 -6
  24. package/dist/types/components/core/useWaveformChartLifecycle.d.ts +2 -2
  25. package/dist/types/components/core/useWaveformData.d.ts +2 -0
  26. package/dist/types/components/core/useWaveformLayout.d.ts +4 -1
  27. package/dist/types/components/core/useWaveformMeasurements.d.ts +2 -0
  28. package/dist/types/components/core/useWaveformRenderSampling.d.ts +1 -0
  29. package/dist/types/components/core/useWaveformViewportRestore.d.ts +5 -0
  30. package/dist/types/components/core/waveformChartTypes.d.ts +5 -0
  31. package/dist/types/components/core/waveformSamplingSession.d.ts +24 -0
  32. package/dist/types/components/core/yAxisLabelStyle.d.ts +3 -0
  33. package/dist/types/components/core/yAxisLayoutMetrics.d.ts +2 -1
  34. package/dist/types/components/core/yAxisPadding.d.ts +7 -0
  35. package/dist/types/components/core/yAxisTitleLayout.d.ts +2 -0
  36. package/dist/types/components/interaction/useExtremaTickViewport.d.ts +5 -0
  37. package/dist/types/components/interaction/useWaveformViewport.d.ts +1 -0
  38. package/dist/types/components/rendering/WaveformLegend.vue.d.ts +2 -2
  39. package/dist/types/components/rendering/WaveformTrack.vue.d.ts +2 -3
  40. package/dist/types/components/rendering/WaveformTrackAxes.vue.d.ts +1 -1
  41. package/dist/types/components/rendering/WaveformTrackBackdrop.vue.d.ts +1 -0
  42. package/dist/types/components/rendering/compactFrame.d.ts +5 -0
  43. package/dist/types/components/rendering/yAxisTickVisibility.d.ts +11 -0
  44. package/dist/types/core/lineBoundary.d.ts +4 -0
  45. package/dist/types/core/rendering.d.ts +4 -0
  46. package/dist/types/core/renderingStrategies.d.ts +2 -0
  47. package/dist/types/core/workerSampling/outputBoundaries.d.ts +4 -0
  48. package/dist/types/data/simulatedWaveforms.d.ts +1 -1
  49. package/dist/types/data/sx2Channel.d.ts +10 -0
  50. package/dist/types/demo/DemoYAxisPaddingControls.vue.d.ts +11 -0
  51. package/dist/types/demo/types.d.ts +7 -0
  52. package/dist/types/index.d.ts +2 -1
  53. package/dist/types/types/chart.d.ts +32 -8
  54. package/dist/types/types/controls.d.ts +69 -0
  55. package/dist/types/types/index.d.ts +2 -1
  56. package/dist/types/utils/formatters.d.ts +1 -0
  57. package/package.json +7 -6
  58. package/src/App.vue +61 -14
  59. package/src/components/WaveformChart.css +10 -1
  60. package/src/components/WaveformChart.vue +6 -0
  61. package/src/components/WaveformChartView.vue +27 -26
  62. package/src/components/annotation/WaveformAnnotationLayer.vue +6 -1
  63. package/src/components/annotation/useWaveformChartAnnotations.ts +7 -18
  64. package/src/components/controls/WaveformToolbar.vue +247 -0
  65. package/src/components/controls/chartExportOptions.ts +32 -0
  66. package/src/components/controls/chartSnapshot.ts +132 -0
  67. package/src/components/controls/snapshotTitle.ts +73 -0
  68. package/src/components/controls/toolbarRegistry.ts +92 -0
  69. package/src/components/controls/useChartCommands.ts +294 -0
  70. package/src/components/controls/useChartImageExport.ts +155 -0
  71. package/src/components/controls/useControlMode.ts +30 -0
  72. package/src/components/core/latestTaskScheduler.ts +14 -2
  73. package/src/components/core/layout.ts +49 -36
  74. package/src/components/core/niceScale.ts +151 -0
  75. package/src/components/core/trackLayoutBuilder.ts +41 -17
  76. package/src/components/core/trackPagination.ts +3 -3
  77. package/src/components/core/types.ts +5 -0
  78. package/src/components/core/useWaveformChartController.ts +94 -3
  79. package/src/components/core/useWaveformChartLifecycle.ts +11 -111
  80. package/src/components/core/useWaveformData.ts +4 -0
  81. package/src/components/core/useWaveformLayout.ts +25 -5
  82. package/src/components/core/useWaveformMeasurements.ts +63 -0
  83. package/src/components/core/useWaveformRenderSampling.ts +80 -59
  84. package/src/components/core/useWaveformViewportRestore.ts +105 -0
  85. package/src/components/core/useWaveformYAxisLayout.ts +3 -2
  86. package/src/components/core/waveformChartTypes.ts +5 -0
  87. package/src/components/core/waveformSamplingSession.ts +49 -0
  88. package/src/components/core/workerSamplingClient.ts +38 -6
  89. package/src/components/core/yAxisLabelStyle.ts +10 -0
  90. package/src/components/core/yAxisLayoutMetrics.ts +11 -9
  91. package/src/components/core/yAxisPadding.ts +60 -0
  92. package/src/components/core/yAxisTitleLayout.ts +11 -12
  93. package/src/components/interaction/useExtremaTickViewport.ts +43 -0
  94. package/src/components/interaction/useWaveformViewport.ts +11 -2
  95. package/src/components/rendering/WaveformTrack.css +1 -2
  96. package/src/components/rendering/WaveformTrack.vue +5 -12
  97. package/src/components/rendering/WaveformTrackAxes.vue +66 -19
  98. package/src/components/rendering/WaveformTrackBackdrop.vue +8 -3
  99. package/src/components/rendering/compactFrame.ts +31 -0
  100. package/src/components/rendering/yAxisTickVisibility.ts +156 -0
  101. package/src/core/lineBoundary.ts +24 -0
  102. package/src/core/rendering.ts +48 -25
  103. package/src/core/renderingStrategies.ts +19 -13
  104. package/src/core/waveformPointSource.ts +23 -1
  105. package/src/core/workerSampling/outputBoundaries.ts +32 -0
  106. package/src/core/workerSampling/repository.ts +26 -18
  107. package/src/core/workerSampling/repositoryDataset.ts +33 -1
  108. package/src/core/workerSampling/workerRuntime.ts +13 -3
  109. package/src/data/simulatedWaveforms.ts +67 -2
  110. package/src/data/sx2Channel.ts +315 -0
  111. package/src/demo/DemoChartHost.vue +5 -1
  112. package/src/demo/DemoGridFrameControls.vue +2 -0
  113. package/src/demo/DemoViewControls.vue +5 -2
  114. package/src/demo/DemoYAxisPaddingControls.vue +53 -0
  115. package/src/demo/embedBridge.ts +85 -0
  116. package/src/demo/types.ts +7 -0
  117. package/src/index.ts +19 -0
  118. package/src/types/chart.ts +34 -8
  119. package/src/types/controls.ts +80 -0
  120. package/src/types/index.ts +18 -0
  121. package/src/utils/formatters.ts +2 -1
  122. package/src/views/WasmSamplingDemo.vue +8 -1
@@ -1,5 +1,6 @@
1
1
  /**
2
2
  * 类型定义统一导出
3
3
  */
4
- export type { WaveformPoint, WaveformDisplayMode, WaveformUnitDisplayMode, WaveformLayoutPreset, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomIntentPayload, WaveformZoomEndPayload, WaveformPanEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformSamplingMode, WaveformSamplingStrategy, WaveformSamplingOptions, WaveformSamplingBackend, WaveformSamplingDiagnostics, WaveformSamplingError, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformZeroLineOptions, } from './chart';
4
+ export type { WaveformPoint, WaveformDisplayMode, WaveformUnitDisplayMode, WaveformLayoutPreset, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomIntentPayload, WaveformZoomEndPayload, WaveformPanEndPayload, WaveformZoomResetPayload, WaveformAnnotationStyle, WaveformAnnotation, WaveformSamplingMode, WaveformSamplingStrategy, WaveformSamplingOptions, WaveformSamplingBackend, WaveformSamplingDiagnostics, WaveformSamplingError, WaveformRenderingOptions, WaveformPlotMargin, WaveformTitleTextStyle, WaveformTitleOptions, WaveformLegendPosition, WaveformLegendOrientation, WaveformLegendOptions, WaveformFrameStyle, WaveformXAxisLabelKind, WaveformXAxisLabelFormatterContext, WaveformXAxisLabelFormatter, WaveformAxesOptions, WaveformYAxisLabelTextStyle, WaveformZeroLineOptions, } from './chart';
5
5
  export type { SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformTypedValues, TypedSampleData, TypedPointData, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './data';
6
+ export type { WaveformToolbarItem, WaveformToolbarOptions, WaveformControlTarget, WaveformControlAction, WaveformCommandMetadata, WaveformTargetStatus, WaveformViewportSnapshot, WaveformTargetResult, WaveformControlResult, WaveformControlState, WaveformImageExportOptions, WaveformImageExportErrorCode, WaveformImageExportError, WaveformChartHandle, } from './controls';
@@ -9,6 +9,7 @@ export interface ScientificAxisLabelOptions {
9
9
  axisMin?: number;
10
10
  axisMax?: number;
11
11
  topTickValue?: number;
12
+ /** Unit prefix for ordinary top ticks; scientific ticks omit it. */
12
13
  unit?: string;
13
14
  }
14
15
  /** @deprecated Use ScientificAxisLabelOptions. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "waveform-analysis",
3
- "version": "0.1.71",
3
+ "version": "0.1.73",
4
4
  "main": "./dist/index.cjs",
5
5
  "module": "./dist/index.js",
6
6
  "types": "./dist/types/index.d.ts",
@@ -55,20 +55,21 @@
55
55
  "scripts": {
56
56
  "dev": "vite",
57
57
  "build": "pnpm typecheck && pnpm build:lib && pnpm build:types && pnpm build:demo",
58
- "build:wasm": "wasm-pack build wasm --target web --release --out-dir pkg --locked",
58
+ "build:wasm": "node scripts/prepare-wasm.mjs --force",
59
59
  "build:lib": "vite build --config vite.lib.config.ts",
60
60
  "build:types": "node scripts/clean-types.mjs && vue-tsc -p tsconfig.lib.json && node scripts/prepare-package-types.mjs",
61
61
  "build:demo": "vite build",
62
62
  "preview": "vite preview --outDir dist-demo",
63
- "typecheck": "pnpm build:wasm && vue-tsc -b",
63
+ "typecheck": "pnpm prepare:wasm && vue-tsc -b",
64
64
  "check:file-length": "node scripts/check-file-length.mjs",
65
65
  "lint": "eslint . --max-warnings=0",
66
66
  "lint:oxlint": "oxlint . --deny-warnings",
67
67
  "lint:all": "pnpm lint:oxlint && pnpm lint",
68
68
  "format": "prettier --write .",
69
69
  "format:check": "prettier --check .",
70
- "test": "pnpm build:wasm && vitest run",
71
- "test:coverage": "pnpm build:wasm && vitest run --coverage",
72
- "test:package": "node scripts/test-package.mjs"
70
+ "test": "pnpm prepare:wasm && vitest run",
71
+ "test:coverage": "pnpm prepare:wasm && vitest run --coverage",
72
+ "test:package": "node scripts/test-package.mjs",
73
+ "prepare:wasm": "node scripts/prepare-wasm.mjs"
73
74
  }
74
75
  }
package/src/App.vue CHANGED
@@ -26,11 +26,23 @@ import DemoControlPanel from './demo/DemoControlPanel.vue'
26
26
  import type { DemoChartModel, DemoControlPanelModel } from './demo/types'
27
27
  import { useDemoXAxisLabelControls } from './demo/useDemoXAxisLabelControls'
28
28
 
29
- const fullChartData = createSimulatedWaveformData()
30
- const displayMode = ref<WaveformDisplayMode>('independent')
29
+ const demoPreset = new URLSearchParams(window.location.search).get('demo')
30
+ const compactNineDemo = demoPreset === 'compact-nine'
31
+ const compactFourteenDemo = demoPreset === 'compact-fourteen'
32
+ const fullChartData = createSimulatedWaveformData(compactFourteenDemo ? 14 : undefined)
33
+ if (compactNineDemo && fullChartData.kind === 'series') {
34
+ const trackIds = [...new Set(fullChartData.series.map((series) => series.trackId ?? series.id))]
35
+ const demoTrackIds = new Set(trackIds.slice(0, 9))
36
+ fullChartData.series = fullChartData.series.filter((series) =>
37
+ demoTrackIds.has(series.trackId ?? series.id),
38
+ )
39
+ }
40
+ const displayMode = ref<WaveformDisplayMode>(
41
+ compactNineDemo || compactFourteenDemo ? 'compact' : 'independent',
42
+ )
31
43
  const overlayMode = ref<WaveformOverlayMode>('single-axis')
32
- const rowCount = ref(4)
33
- const columnCount = ref(1)
44
+ const rowCount = ref(compactNineDemo ? 5 : 4)
45
+ const columnCount = ref(compactNineDemo || compactFourteenDemo ? 2 : 1)
34
46
  const frameBorderColor = ref('#1f2937')
35
47
  const frameBorderWidth = ref(2)
36
48
  const frameBorderStyle = ref<NonNullable<WaveformFrameStyle['borderStyle']>>('solid')
@@ -42,6 +54,11 @@ const verticalGridVisible = ref(true)
42
54
  const verticalGridColor = ref('#dfe5ef')
43
55
  const xAxisLineVisible = ref(false)
44
56
  const yAxisLineVisible = ref(false)
57
+ const upperPaddingEnabled = ref(true)
58
+ const lowerPaddingEnabled = ref(true)
59
+ const extremaTicksVisible = ref(true)
60
+ const upperPaddingPercent = ref(10)
61
+ const lowerPaddingPercent = ref(10)
45
62
  const { controlModel: xAxisLabelControlModel, xAxisLabelFormatter } = useDemoXAxisLabelControls()
46
63
  const annotations = ref<WaveformAnnotation[]>([])
47
64
  const annotationsVisible = ref(true)
@@ -49,13 +66,14 @@ const cleanView = ref(false)
49
66
  const presentationMode = ref(false)
50
67
  const showTooltip = ref(true)
51
68
  const integerZoom = ref(true)
52
- const plotMarginTop = ref(18)
53
- const plotMarginBottom = ref(52)
69
+ const plotMarginTop = ref(10)
70
+ const plotMarginBottom = ref(44)
54
71
  const zeroLineVisible = ref(true)
55
72
  const zeroLineColor = ref('#ff0000')
56
73
  const zeroLineWidth = ref(1)
57
74
  const zeroLineOpacity = ref(0.5)
58
75
  const zeroLineDash = ref('6 4')
76
+ const toolbarVisible = ref(true)
59
77
  const interactionMode = ref<WaveformInteractionMode>('zoom')
60
78
  const legendPosition = ref<WaveformLegendPosition>('top-right')
61
79
  const legendOrientation = ref<WaveformLegendOrientation>('auto')
@@ -65,10 +83,10 @@ const titleVisible = ref(true)
65
83
  const titleText = ref('模拟波形分析')
66
84
  const titleAlign = ref<NonNullable<WaveformTitleOptions['align']>>('center')
67
85
  const titleFontFamily = ref('"Microsoft YaHei", "微软雅黑", sans-serif')
68
- const titleFontSize = ref(14)
86
+ const titleFontSize = ref(18)
69
87
  const titleRotation = ref(0)
70
88
  const titleColor = ref('#1f2937')
71
- const titleBold = ref(false)
89
+ const titleBold = ref(true)
72
90
  const titleItalic = ref(false)
73
91
  const titleUnderline = ref(false)
74
92
  const controlsOpen = ref(false)
@@ -123,7 +141,14 @@ const axes = computed<WaveformAxesOptions>(() => ({
123
141
  lineVisible: xAxisLineVisible.value,
124
142
  ...(xAxisLabelFormatter.value ? { labelFormatter: xAxisLabelFormatter.value } : {}),
125
143
  },
126
- y: { lineVisible: yAxisLineVisible.value },
144
+ y: {
145
+ lineVisible: yAxisLineVisible.value,
146
+ upperPaddingEnabled: upperPaddingEnabled.value,
147
+ lowerPaddingEnabled: lowerPaddingEnabled.value,
148
+ extremaTicksVisible: extremaTicksVisible.value,
149
+ upperPaddingRatio: upperPaddingPercent.value / 100,
150
+ lowerPaddingRatio: lowerPaddingPercent.value / 100,
151
+ },
127
152
  }))
128
153
  const zeroLine = computed<WaveformZeroLineOptions>(() => ({
129
154
  visible: zeroLineVisible.value,
@@ -243,7 +268,17 @@ function mergeIndependentWindow(
243
268
  }
244
269
  }
245
270
 
271
+ let pendingCommandId: string | undefined
272
+ let pendingPayloads: WaveformZoomEndPayload[] = []
246
273
  async function handleZoomEnd(payload: WaveformZoomEndPayload) {
274
+ if (payload.action === 'fit') return
275
+ if (payload.commandId && payload.commandId === pendingCommandId) {
276
+ pendingPayloads.push(payload)
277
+ return
278
+ }
279
+ pendingCommandId = payload.commandId
280
+ const batch = [payload]
281
+ pendingPayloads = batch
247
282
  // Demo-only sequence number cancellation. Production code should use AbortController
248
283
  // to cancel in-flight requests when a newer zoom gesture arrives.
249
284
  const requestSequence = ++zoomRequestSequence
@@ -252,11 +287,16 @@ async function handleZoomEnd(payload: WaveformZoomEndPayload) {
252
287
 
253
288
  // Demo-only stand-in for the backend response. Production code should replace this
254
289
  // with a request using payload.start/payload.end and the optional channel metadata.
255
- const responseData = filterWaveformData(fullChartData, payload.start, payload.end)
256
- chartData.value =
257
- payload.trackIndex !== undefined && payload.seriesIds?.length
258
- ? mergeIndependentWindow(chartData.value, responseData, payload.seriesIds)
259
- : responseData
290
+ let nextData = chartData.value
291
+ batch.forEach((item) => {
292
+ const responseData = filterWaveformData(fullChartData, item.start, item.end)
293
+ nextData =
294
+ item.trackIndex !== undefined && item.seriesIds?.length
295
+ ? mergeIndependentWindow(nextData, responseData, item.seriesIds)
296
+ : responseData
297
+ })
298
+ chartData.value = nextData
299
+ pendingCommandId = undefined
260
300
  }
261
301
 
262
302
  function resetWaveformViewport() {
@@ -297,6 +337,7 @@ onMounted(() => window.addEventListener('keydown', handleWindowKeydown))
297
337
  onBeforeUnmount(() => window.removeEventListener('keydown', handleWindowKeydown))
298
338
 
299
339
  const controlPanelModel = reactive({
340
+ toolbarVisible,
300
341
  controlsOpen,
301
342
  displayMode,
302
343
  overlayMode,
@@ -324,6 +365,11 @@ const controlPanelModel = reactive({
324
365
  verticalGridColor,
325
366
  xAxisLineVisible,
326
367
  yAxisLineVisible,
368
+ upperPaddingEnabled,
369
+ lowerPaddingEnabled,
370
+ extremaTicksVisible,
371
+ upperPaddingPercent,
372
+ lowerPaddingPercent,
327
373
  ...xAxisLabelControlModel,
328
374
  frameBorderColor,
329
375
  frameBackgroundColor,
@@ -354,6 +400,7 @@ const controlPanelModel = reactive({
354
400
  }) satisfies DemoControlPanelModel
355
401
 
356
402
  const chartModel = reactive({
403
+ toolbarVisible,
357
404
  data: displayChartData,
358
405
  initialXDomain,
359
406
  displayMode,
@@ -117,8 +117,17 @@
117
117
  font-size: 12px;
118
118
  }
119
119
 
120
+ .waveform-chart--interaction-pan:not(.waveform-chart--presentation) .waveform-chart__overlay {
121
+ cursor: grab;
122
+ }
123
+ .waveform-chart--interaction-pan.waveform-chart--panning .waveform-chart__overlay {
124
+ cursor: grabbing;
125
+ }
126
+ .waveform-chart--interaction-none .waveform-chart__overlay {
127
+ cursor: default;
128
+ }
129
+
120
130
  .waveform-chart--edge-compact .waveform-chart__x-label {
121
- font-size: 18px;
122
131
  font-weight: 600;
123
132
  }
124
133
 
@@ -38,6 +38,12 @@ const emit = defineEmits<WaveformChartEmit>()
38
38
  const controller = useWaveformChartController(props as ResolvedWaveformChartProps, emit)
39
39
 
40
40
  defineExpose({
41
+ exportImage: controller.exportImage,
42
+ zoomIn: controller.zoomIn,
43
+ zoomOut: controller.zoomOut,
44
+ fitToData: controller.fitToData,
45
+ setInteractionMode: controller.setInteractionMode,
46
+ getControlState: controller.getControlState,
41
47
  resetViewport: controller.resetViewport,
42
48
  setViewportDomain: controller.setViewportDomain,
43
49
  })
@@ -1,15 +1,22 @@
1
1
  <script setup lang="ts">
2
+ import WaveformToolbar from './controls/WaveformToolbar.vue'
2
3
  import { Pagination } from 'ant-design-vue'
3
- import { toRefs } from 'vue'
4
+ import { computed, toRefs } from 'vue'
4
5
 
5
6
  import { WaveformAnnotationContextMenu, WaveformAnnotationLayer } from './annotation'
6
7
  import WaveformAnnotationEditor from './annotation/WaveformAnnotationEditor.vue'
7
8
  import type { WaveformChartController } from './core/useWaveformChartController'
8
- import type { TrackLayout } from './core/types'
9
9
  import { WaveformHoverHost } from './interaction'
10
10
  import { WaveformHoverLayer, WaveformLegend, WaveformTrack } from './rendering'
11
+ import { compactFramePath } from './rendering/compactFrame'
11
12
 
12
13
  const props = defineProps<{ controller: WaveformChartController }>()
14
+ const compactFrameInset = computed(() => {
15
+ const width = props.controller.frameStyle?.borderWidth
16
+ return props.controller.isEdgeCompact
17
+ ? (typeof width === 'number' && Number.isFinite(width) && width >= 0 ? width : 1) / 2
18
+ : 0
19
+ })
13
20
  const {
14
21
  displayMode,
15
22
  activeInteractionMode,
@@ -55,7 +62,6 @@ const {
55
62
  cancelViewportDrag,
56
63
  handlePointerLeave,
57
64
  handleAnnotationClick,
58
- handleAnnotationContextMenu,
59
65
  resolveFrameNumber,
60
66
  frameStyle,
61
67
  axes,
@@ -111,21 +117,6 @@ function handleChartPointerLeave() {
111
117
  pointerInsideChart.value = false
112
118
  handlePointerLeave.value()
113
119
  }
114
-
115
- function sharesFrameTop(track: TrackLayout): boolean {
116
- return (
117
- displayMode.value === 'compact' &&
118
- trackLayouts.value.some(
119
- (above) =>
120
- !above.isEmpty &&
121
- above !== track &&
122
- above.column === track.column &&
123
- Math.abs(above.left - track.left) < 1e-6 &&
124
- Math.abs(above.width - track.width) < 1e-6 &&
125
- Math.abs(above.top + above.height - track.top) < 1e-6,
126
- )
127
- )
128
- }
129
120
  </script>
130
121
 
131
122
  <template>
@@ -205,7 +196,7 @@ function sharesFrameTop(track: TrackLayout): boolean {
205
196
 
206
197
  <g :transform="`translate(${resolvedChartLeftMargin}, ${chartTopMargin})`">
207
198
  <g
208
- v-if="displayMode !== 'compact' && !isCleanView"
199
+ v-if="displayMode !== 'compact' && !hasWaveformData && !isCleanView"
209
200
  class="waveform-chart__grid-slots"
210
201
  aria-hidden="true"
211
202
  >
@@ -238,7 +229,6 @@ function sharesFrameTop(track: TrackLayout): boolean {
238
229
  @pointercancel="cancelViewportDrag"
239
230
  @pointerleave="handlePointerLeave"
240
231
  @click="handleAnnotationClick"
241
- @contextmenu="handleAnnotationContextMenu"
242
232
  />
243
233
 
244
234
  <!-- 轨道渲染 -->
@@ -246,21 +236,28 @@ function sharesFrameTop(track: TrackLayout): boolean {
246
236
  v-for="track in trackLayouts"
247
237
  :key="`${track.index}-${track.id}`"
248
238
  :track="track"
239
+ :frame-path="
240
+ displayMode === 'compact'
241
+ ? compactFramePath(track, trackLayouts, compactFrameInset)
242
+ : undefined
243
+ "
249
244
  :clip-path-id="clipPathId"
250
245
  :inner-width="innerWidth"
251
246
  :zoomable="zoomable"
252
247
  :interactive="!isPresentationMode"
253
248
  :display-mode="displayMode"
254
249
  :interaction-mode="activeInteractionMode"
255
- :frame-number="resolveFrameNumber(track.id)"
250
+ :frame-number="
251
+ resolveFrameNumber(track.id, (currentPage - 1) * getPageSize(gridOptions) + track.index)
252
+ "
256
253
  :frame-style="frameStyle"
257
254
  :axes="axes"
258
255
  :show-axis-units="unitDisplayMode === 'axis'"
259
- :show-label-units="unitDisplayMode === 'channel-label-or-legend'"
256
+ :show-label-units="
257
+ unitDisplayMode === 'channel-label-or-legend' || unitDisplayMode === 'channel-label'
258
+ "
260
259
  :hide-y-axis-titles="hideYAxisTitles"
261
- :contain-y-axis-endpoints="isEdgeCompact"
262
260
  :contain-frame-stroke="isEdgeCompact"
263
- :omit-frame-top="sharesFrameTop(track)"
264
261
  :clean-view="isCleanView"
265
262
  :zero-line="resolvedZeroLine"
266
263
  :time-unit="timeUnit"
@@ -271,7 +268,6 @@ function sharesFrameTop(track: TrackLayout): boolean {
271
268
  @pointer-cancel="cancelViewportDrag"
272
269
  @pointer-leave="handlePointerLeave"
273
270
  @click="handleAnnotationClick($event, track.index)"
274
- @contextmenu="handleAnnotationContextMenu($event, track.index)"
275
271
  />
276
272
 
277
273
  <WaveformHoverLayer
@@ -292,10 +288,12 @@ function sharesFrameTop(track: TrackLayout): boolean {
292
288
  />
293
289
 
294
290
  <WaveformAnnotationLayer
291
+ :cancel-key="activeInteractionMode"
292
+ :data-version="controller.data"
295
293
  v-if="!isCleanView"
296
294
  :annotations="renderedAnnotations"
297
295
  :visible="annotationsVisible"
298
- :interactive="!isPresentationMode"
296
+ :interactive="!isPresentationMode && activeInteractionMode !== 'none'"
299
297
  @contextmenu="handleExistingAnnotationContextMenu"
300
298
  @drag-start="beginAnnotationDrag"
301
299
  @move="handleAnnotationMove"
@@ -331,6 +329,7 @@ function sharesFrameTop(track: TrackLayout): boolean {
331
329
  <text
332
330
  v-if="resolvedXLabel && !isCleanView"
333
331
  class="waveform-chart__label waveform-chart__x-label"
332
+ :style="isEdgeCompact ? { fontSize: titleTextStyle.fontSize } : undefined"
334
333
  :x="plotCenterX"
335
334
  :y="xAxisTitleY"
336
335
  text-anchor="middle"
@@ -349,6 +348,8 @@ function sharesFrameTop(track: TrackLayout): boolean {
349
348
  </text>
350
349
  </svg>
351
350
 
351
+ <WaveformToolbar :controller="controller" />
352
+
352
353
  <Pagination
353
354
  v-if="paginationVisible"
354
355
  class="waveform-chart__pagination"
@@ -1,5 +1,5 @@
1
1
  <script setup lang="ts">
2
- import { computed, ref, watch } from 'vue'
2
+ import { computed, ref, watch, onBeforeUnmount } from 'vue'
3
3
 
4
4
  import type { RenderedAnnotation } from './types'
5
5
  import { ANNOTATION_TEXT_FONT, ANNOTATION_TEXT_LINE_HEIGHT } from './markup'
@@ -8,6 +8,8 @@ interface Props {
8
8
  annotations: RenderedAnnotation[]
9
9
  visible: boolean
10
10
  interactive?: boolean
11
+ cancelKey?: string
12
+ dataVersion?: unknown
11
13
  }
12
14
 
13
15
  const props = withDefaults(defineProps<Props>(), {
@@ -211,6 +213,9 @@ watch(
211
213
  },
212
214
  )
213
215
 
216
+ watch([() => props.cancelKey, () => props.dataVersion, () => props.visible], cancelActiveDrag)
217
+ onBeforeUnmount(cancelActiveDrag)
218
+
214
219
  function handleContextMenu(annotationId: string, event: MouseEvent) {
215
220
  if (!props.interactive) return
216
221
  event.preventDefault()
@@ -257,17 +257,21 @@ export function useWaveformChartAnnotations(context: AnnotationContext) {
257
257
  if (
258
258
  isPresentationMode.value ||
259
259
  !props.annotationsVisible ||
260
+ event.button !== 0 ||
260
261
  activeInteractionMode.value !== 'annotation'
261
262
  ) {
262
263
  return
263
264
  }
264
265
  const candidateContext = resolveAnnotationCandidates(event, trackIndex)
265
- if (!candidateContext) return
266
+ if (!candidateContext || !candidateContext.candidates.length) return
266
267
  const nearby = nearbyCandidates(candidateContext.candidates)
267
- if (!nearby.length) return
268
268
  event.preventDefault()
269
269
  event.stopPropagation()
270
- beginCreate(nearby[0], candidateContext.editorAnchor, candidateContext.candidates)
270
+ beginCreate(
271
+ nearby[0] ?? candidateContext.candidates[0],
272
+ candidateContext.editorAnchor,
273
+ candidateContext.candidates,
274
+ )
271
275
  }
272
276
 
273
277
  function handleNativeContextMenu(event: MouseEvent) {
@@ -281,20 +285,6 @@ export function useWaveformChartAnnotations(context: AnnotationContext) {
281
285
  event.preventDefault()
282
286
  }
283
287
 
284
- function handleAnnotationContextMenu(event: MouseEvent, trackIndex?: number) {
285
- if (isPresentationMode.value || !props.annotationsVisible) return
286
- event.preventDefault()
287
- event.stopPropagation()
288
- const candidateContext = resolveAnnotationCandidates(event, trackIndex)
289
- if (!candidateContext || !candidateContext.candidates.length) return
290
- const nearby = nearbyCandidates(candidateContext.candidates)
291
- beginCreate(
292
- nearby[0] ?? candidateContext.candidates[0],
293
- candidateContext.editorAnchor,
294
- candidateContext.candidates,
295
- )
296
- }
297
-
298
288
  function handleExistingAnnotationContextMenu(annotationId: string, event: MouseEvent) {
299
289
  if (isPresentationMode.value || !props.annotationsVisible || !container.value) return
300
290
  editorSeriesOptions.value = []
@@ -387,7 +377,6 @@ export function useWaveformChartAnnotations(context: AnnotationContext) {
387
377
  resolveTrackAtPointer,
388
378
  handleAnnotationClick,
389
379
  handleNativeContextMenu,
390
- handleAnnotationContextMenu,
391
380
  handleExistingAnnotationContextMenu,
392
381
  handleAnnotationMove,
393
382
  editContextAnnotation,