waveform-analysis 0.1.75 → 0.1.77
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 +183 -42
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/style.css +1 -1
- package/dist/types/components/animation/context.d.ts +1 -0
- package/dist/types/components/animation/paginationPresentation.d.ts +7 -7
- package/dist/types/components/animation/useWaveformAnimations.d.ts +1 -18
- package/dist/types/components/core/amplitudeLimits.d.ts +6 -0
- package/dist/types/components/core/layout.d.ts +6 -9
- package/dist/types/components/core/trackLayoutBuilder.d.ts +3 -1
- package/dist/types/components/core/useWaveformChartController.d.ts +2 -19
- package/dist/types/components/core/useWaveformData.d.ts +2 -2
- package/dist/types/components/core/useWaveformMeasurements.d.ts +4 -1
- package/dist/types/components/core/waveformChartTypes.d.ts +4 -2
- package/dist/types/components/core/xAxisTicks.d.ts +2 -0
- package/dist/types/components/core/yAxisLayoutMetrics.d.ts +2 -1
- package/dist/types/components/core/yAxisTicks.d.ts +6 -0
- package/dist/types/components/data/types.d.ts +1 -1
- package/dist/types/components/interaction/integerZoom.d.ts +3 -3
- package/dist/types/components/interaction/stepZoom.d.ts +5 -0
- package/dist/types/components/interaction/useWaveformViewport.d.ts +0 -3
- package/dist/types/components/interaction/useWaveformZoom.d.ts +0 -2
- package/dist/types/components/rendering/WaveformSeriesLayer.vue.d.ts +0 -1
- package/dist/types/components/rendering/WaveformTrack.vue.d.ts +0 -1
- package/dist/types/core/data.d.ts +1 -1
- package/dist/types/demo/DemoChartHost.vue.d.ts +1 -1
- package/dist/types/demo/remoteWaveformLoader.d.ts +23 -0
- package/dist/types/demo/types.d.ts +11 -2
- package/dist/types/demo/useRemoteWaveformWindow.d.ts +22 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/types/chart.d.ts +8 -1
- package/dist/types/types/index.d.ts +1 -1
- package/dist/types/utils/formatters.d.ts +4 -4
- package/dist/types/utils/xAxisNumber.d.ts +2 -0
- package/package.json +1 -1
- package/src/App.vue +34 -100
- package/src/components/WaveformChartView.vue +0 -4
- package/src/components/animation/config.ts +1 -1
- package/src/components/animation/context.ts +9 -1
- package/src/components/animation/paginationPresentation.ts +55 -64
- package/src/components/animation/useWaveformAnimations.ts +36 -13
- package/src/components/controls/useChartCommands.ts +12 -13
- package/src/components/core/amplitudeLimits.ts +44 -0
- package/src/components/core/layout.ts +22 -34
- package/src/components/core/trackLayoutBuilder.ts +12 -5
- package/src/components/core/useWaveformChartController.ts +3 -4
- package/src/components/core/useWaveformChartLifecycle.ts +11 -2
- package/src/components/core/useWaveformData.ts +11 -5
- package/src/components/core/useWaveformLayout.ts +9 -4
- package/src/components/core/useWaveformMeasurements.ts +3 -0
- package/src/components/core/useWaveformYAxisLayout.ts +2 -0
- package/src/components/core/waveformChartTypes.ts +4 -1
- package/src/components/core/xAxisTicks.ts +69 -0
- package/src/components/core/yAxisLayoutMetrics.ts +4 -0
- package/src/components/core/yAxisTicks.ts +39 -0
- package/src/components/data/types.ts +2 -0
- package/src/components/interaction/integerZoom.ts +31 -12
- package/src/components/interaction/remoteViewport.ts +26 -4
- package/src/components/interaction/stepZoom.ts +33 -0
- package/src/components/interaction/useWaveformViewport.ts +3 -26
- package/src/components/interaction/useWaveformZoom.ts +50 -157
- package/src/components/interaction/zoomConstraints.ts +5 -5
- package/src/components/rendering/WaveformSeriesLayer.vue +1 -2
- package/src/components/rendering/WaveformTrack.vue +1 -6
- package/src/core/data.ts +5 -2
- package/src/data/simulatedWaveforms.ts +0 -10
- package/src/demo/DemoChartHost.vue +49 -4
- package/src/demo/remoteWaveformLoader.ts +114 -0
- package/src/demo/types.ts +11 -1
- package/src/demo/useRemoteWaveformWindow.ts +229 -0
- package/src/index.ts +2 -0
- package/src/types/chart.ts +10 -1
- package/src/types/index.ts +2 -0
- package/src/utils/formatters.ts +10 -5
- package/src/utils/xAxisNumber.ts +36 -0
- package/src/views/AnimationDemo.vue +14 -5
- package/dist/types/data/sx2Channel.d.ts +0 -10
- package/src/data/sx2Channel.ts +0 -315
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import { computed, onScopeDispose, shallowRef, watch, type Ref } from 'vue'
|
|
2
|
+
import type {
|
|
3
|
+
WaveformData,
|
|
4
|
+
WaveformPanEndPayload,
|
|
5
|
+
WaveformZoomEndPayload,
|
|
6
|
+
WaveformZoomIntentPayload,
|
|
7
|
+
WaveformZoomResetPayload,
|
|
8
|
+
} from '../types'
|
|
9
|
+
import {
|
|
10
|
+
createMockWindowLoader,
|
|
11
|
+
mergeWaveformWindow,
|
|
12
|
+
selectWaveformWindow,
|
|
13
|
+
waveformRecordMetadata,
|
|
14
|
+
type RemoteWindowLoader,
|
|
15
|
+
type RemoteWindowRequest,
|
|
16
|
+
} from './remoteWaveformLoader'
|
|
17
|
+
|
|
18
|
+
interface WindowState {
|
|
19
|
+
request: RemoteWindowRequest
|
|
20
|
+
version: number
|
|
21
|
+
controller?: AbortController
|
|
22
|
+
loading: boolean
|
|
23
|
+
error?: string
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
interface RemoteWindowOptions {
|
|
27
|
+
loadWindow?: RemoteWindowLoader
|
|
28
|
+
resetViewport?: (trackIndex?: number) => void
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
type WindowPayload = WaveformPanEndPayload | WaveformZoomEndPayload
|
|
32
|
+
|
|
33
|
+
export function useRemoteWaveformWindow(
|
|
34
|
+
source: Ref<WaveformData>,
|
|
35
|
+
options: RemoteWindowOptions = {},
|
|
36
|
+
) {
|
|
37
|
+
const metadata = computed(() => waveformRecordMetadata(source.value))
|
|
38
|
+
const recordDomain = computed(() => metadata.value.domain)
|
|
39
|
+
const initialXDomain = computed(() => recordDomain.value)
|
|
40
|
+
const minZoomSpan = computed(() => metadata.value.minZoomSpan)
|
|
41
|
+
const chartData = shallowRef<WaveformData>(
|
|
42
|
+
selectWaveformWindow(
|
|
43
|
+
source.value,
|
|
44
|
+
{ start: recordDomain.value[0], end: recordDomain.value[1] },
|
|
45
|
+
true,
|
|
46
|
+
),
|
|
47
|
+
)
|
|
48
|
+
const states = shallowRef(new Map<string, WindowState>())
|
|
49
|
+
const loadWindow = options.loadWindow ?? createMockWindowLoader(() => source.value)
|
|
50
|
+
let disposed = false
|
|
51
|
+
let intentTarget: string | undefined
|
|
52
|
+
let intentSequence = 0
|
|
53
|
+
const pendingCommands = new Map<string, object>()
|
|
54
|
+
|
|
55
|
+
const keyFor = (seriesIds?: string[]) =>
|
|
56
|
+
seriesIds?.length ? JSON.stringify([...seriesIds].sort()) : 'shared'
|
|
57
|
+
const publish = () => (states.value = new Map(states.value))
|
|
58
|
+
|
|
59
|
+
function cancel(key: string) {
|
|
60
|
+
const state = states.value.get(key)
|
|
61
|
+
if (!state) return
|
|
62
|
+
state.version++
|
|
63
|
+
state.controller?.abort()
|
|
64
|
+
state.controller = undefined
|
|
65
|
+
state.loading = false
|
|
66
|
+
state.error = undefined
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function cancelAll() {
|
|
70
|
+
for (const key of states.value.keys()) cancel(key)
|
|
71
|
+
publish()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function handleZoomIntent(payload: WaveformZoomIntentPayload) {
|
|
75
|
+
const key = keyFor(payload.seriesIds)
|
|
76
|
+
if (key === 'shared') pendingCommands.clear()
|
|
77
|
+
else pendingCommands.delete(key)
|
|
78
|
+
if (key === 'shared') cancelAll()
|
|
79
|
+
else {
|
|
80
|
+
cancel('shared')
|
|
81
|
+
cancel(key)
|
|
82
|
+
}
|
|
83
|
+
const previous = states.value.get(key)
|
|
84
|
+
states.value.set(key, {
|
|
85
|
+
request: { start: payload.start, end: payload.end, seriesIds: payload.seriesIds },
|
|
86
|
+
version: previous?.version ?? 0,
|
|
87
|
+
loading: false,
|
|
88
|
+
})
|
|
89
|
+
publish()
|
|
90
|
+
// Command intent/change/end arrive synchronously per target. Scope their
|
|
91
|
+
// changes so the next target cannot cancel a valid request in the same batch.
|
|
92
|
+
intentTarget = key
|
|
93
|
+
const sequence = ++intentSequence
|
|
94
|
+
queueMicrotask(() => {
|
|
95
|
+
if (sequence === intentSequence) intentTarget = undefined
|
|
96
|
+
})
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function handleZoomChange(domain: [number, number]) {
|
|
100
|
+
if (intentTarget) {
|
|
101
|
+
cancel(intentTarget)
|
|
102
|
+
publish()
|
|
103
|
+
} else {
|
|
104
|
+
cancelAll()
|
|
105
|
+
// This event has no target metadata. It must never start an unscoped
|
|
106
|
+
// request in independent mode; pan-end supplies the final target.
|
|
107
|
+
for (const state of states.value.values()) {
|
|
108
|
+
state.request = { ...state.request, start: domain[0], end: domain[1] }
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function load(payload: WindowPayload) {
|
|
114
|
+
if (disposed) return
|
|
115
|
+
const key = keyFor(payload.seriesIds)
|
|
116
|
+
if (key === 'shared') cancelAll()
|
|
117
|
+
else cancel('shared')
|
|
118
|
+
cancel(key)
|
|
119
|
+
const controller = new AbortController()
|
|
120
|
+
const state: WindowState = {
|
|
121
|
+
request: {
|
|
122
|
+
start: payload.start,
|
|
123
|
+
end: payload.end,
|
|
124
|
+
...(payload.seriesIds?.length ? { seriesIds: [...payload.seriesIds] } : {}),
|
|
125
|
+
},
|
|
126
|
+
version: (states.value.get(key)?.version ?? 0) + 1,
|
|
127
|
+
controller,
|
|
128
|
+
loading: true,
|
|
129
|
+
}
|
|
130
|
+
const version = state.version
|
|
131
|
+
const record = source.value
|
|
132
|
+
states.value.set(key, state)
|
|
133
|
+
publish()
|
|
134
|
+
const current = () =>
|
|
135
|
+
!disposed &&
|
|
136
|
+
source.value === record &&
|
|
137
|
+
!controller.signal.aborted &&
|
|
138
|
+
states.value.get(key) === state &&
|
|
139
|
+
state.version === version
|
|
140
|
+
try {
|
|
141
|
+
const response = await loadWindow(state.request, controller.signal)
|
|
142
|
+
if (!current()) return
|
|
143
|
+
chartData.value = mergeWaveformWindow(chartData.value, response, state.request.seriesIds)
|
|
144
|
+
} catch (error) {
|
|
145
|
+
if (!current()) return
|
|
146
|
+
state.error = error instanceof Error ? error.message : '数据加载失败'
|
|
147
|
+
} finally {
|
|
148
|
+
if (current()) {
|
|
149
|
+
state.loading = false
|
|
150
|
+
state.controller = undefined
|
|
151
|
+
publish()
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function resetWaveformViewport(payload?: WaveformZoomResetPayload) {
|
|
157
|
+
pendingCommands.clear()
|
|
158
|
+
if (!payload?.seriesIds?.length) {
|
|
159
|
+
cancelAll()
|
|
160
|
+
states.value = new Map()
|
|
161
|
+
}
|
|
162
|
+
options.resetViewport?.(payload?.trackIndex)
|
|
163
|
+
return load({
|
|
164
|
+
start: initialXDomain.value[0],
|
|
165
|
+
end: initialXDomain.value[1],
|
|
166
|
+
seriesIds: payload?.seriesIds,
|
|
167
|
+
})
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function retry() {
|
|
171
|
+
return Promise.all(
|
|
172
|
+
[...states.value.values()].filter((state) => state.error).map((state) => load(state.request)),
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
async function handleZoomEnd(payload: WaveformZoomEndPayload) {
|
|
177
|
+
if (!payload.commandId) return load(payload)
|
|
178
|
+
const key = keyFor(payload.seriesIds)
|
|
179
|
+
const pending = {}
|
|
180
|
+
const record = source.value
|
|
181
|
+
pendingCommands.set(key, pending)
|
|
182
|
+
// Collect the synchronous command batch before starting requests. Fit has
|
|
183
|
+
// no intent event, so its unscoped changes would otherwise cancel siblings.
|
|
184
|
+
await Promise.resolve()
|
|
185
|
+
if (disposed || source.value !== record || pendingCommands.get(key) !== pending) return
|
|
186
|
+
pendingCommands.delete(key)
|
|
187
|
+
return load(payload)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function handlePanEnd(payload: WaveformPanEndPayload) {
|
|
191
|
+
pendingCommands.clear()
|
|
192
|
+
return load(payload)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
watch(source, () => {
|
|
196
|
+
pendingCommands.clear()
|
|
197
|
+
cancelAll()
|
|
198
|
+
states.value = new Map()
|
|
199
|
+
intentTarget = undefined
|
|
200
|
+
chartData.value = selectWaveformWindow(
|
|
201
|
+
source.value,
|
|
202
|
+
{ start: recordDomain.value[0], end: recordDomain.value[1] },
|
|
203
|
+
true,
|
|
204
|
+
)
|
|
205
|
+
void resetWaveformViewport()
|
|
206
|
+
})
|
|
207
|
+
onScopeDispose(() => {
|
|
208
|
+
disposed = true
|
|
209
|
+
pendingCommands.clear()
|
|
210
|
+
cancelAll()
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
chartData,
|
|
215
|
+
recordDomain,
|
|
216
|
+
initialXDomain,
|
|
217
|
+
minZoomSpan,
|
|
218
|
+
loading: computed(() => [...states.value.values()].some((state) => state.loading)),
|
|
219
|
+
errors: computed(() =>
|
|
220
|
+
[...states.value.values()].flatMap((state) => (state.error ? [state.error] : [])),
|
|
221
|
+
),
|
|
222
|
+
handleZoomIntent,
|
|
223
|
+
handleZoomChange,
|
|
224
|
+
handleZoomEnd,
|
|
225
|
+
handlePanEnd,
|
|
226
|
+
resetWaveformViewport,
|
|
227
|
+
retry,
|
|
228
|
+
}
|
|
229
|
+
}
|
package/src/index.ts
CHANGED
package/src/types/chart.ts
CHANGED
|
@@ -81,7 +81,7 @@ export interface WaveformZoomIntentPayload extends WaveformCommandMetadata {
|
|
|
81
81
|
seriesIds?: string[]
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
/** Identifies the viewport reset by a
|
|
84
|
+
/** Identifies the viewport reset by a toolbar or API command. */
|
|
85
85
|
export interface WaveformZoomResetPayload extends WaveformCommandMetadata {
|
|
86
86
|
trackIndex?: number
|
|
87
87
|
seriesIds?: string[]
|
|
@@ -319,3 +319,12 @@ export interface WaveformZeroLineOptions {
|
|
|
319
319
|
/** Hide within this fraction of a Y-axis tick interval from either boundary. Defaults to 0.02. */
|
|
320
320
|
boundaryThreshold?: number
|
|
321
321
|
}
|
|
322
|
+
|
|
323
|
+
/** Strict amplitude display bounds, in the series' original Y units. */
|
|
324
|
+
export interface WaveformAmplitudeLimit {
|
|
325
|
+
lower?: number
|
|
326
|
+
upper?: number
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/** Keyed by stable normalized series IDs (not track IDs). */
|
|
330
|
+
export type WaveformAmplitudeLimits = Record<string, WaveformAmplitudeLimit>
|
package/src/types/index.ts
CHANGED
package/src/utils/formatters.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { WaveformXAxisLabelFormatter, WaveformXAxisLabelKind } from '../types'
|
|
2
|
+
import { formatXAxisNumber } from './xAxisNumber'
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* 时间单位类型
|
|
@@ -147,7 +148,11 @@ export function formatXAxisLabel(
|
|
|
147
148
|
formatter?: WaveformXAxisLabelFormatter,
|
|
148
149
|
): string {
|
|
149
150
|
const value = displayTime(rawValue, timeUnit)
|
|
150
|
-
if (!formatter)
|
|
151
|
+
if (!formatter)
|
|
152
|
+
return formatXAxisNumber(value, [
|
|
153
|
+
displayTime(domain[0], timeUnit),
|
|
154
|
+
displayTime(domain[1], timeUnit),
|
|
155
|
+
])
|
|
151
156
|
|
|
152
157
|
return formatter(value, {
|
|
153
158
|
kind,
|
|
@@ -173,9 +178,9 @@ export function endpointFractionDigits(domain: [number, number], timeUnit: TimeU
|
|
|
173
178
|
}
|
|
174
179
|
|
|
175
180
|
/**
|
|
176
|
-
* 将 X
|
|
181
|
+
* 将 X 轴端点时间格式化为当前显示单位下的自适应精度文本
|
|
177
182
|
* @param value 时间值(秒)
|
|
178
|
-
* @param _domain
|
|
183
|
+
* @param _domain 用于计算显示精度的可视域
|
|
179
184
|
* @param timeUnit 时间单位
|
|
180
185
|
* @returns 格式化的时间字符串
|
|
181
186
|
*/
|
|
@@ -188,10 +193,10 @@ export function formatEndpointTime(
|
|
|
188
193
|
}
|
|
189
194
|
|
|
190
195
|
/**
|
|
191
|
-
* 将 X
|
|
196
|
+
* 将 X 轴时间刻度格式化为当前显示单位下的自适应精度文本
|
|
192
197
|
* @param value 时间值(秒)
|
|
193
198
|
* @param timeUnit 时间单位
|
|
194
|
-
* @param _domain
|
|
199
|
+
* @param _domain 用于计算显示精度的可视域
|
|
195
200
|
* @returns 格式化的时间字符串
|
|
196
201
|
*/
|
|
197
202
|
export function formatAxisTime(
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/** Compact labels share precision so even extremely close endpoints stay distinct. */
|
|
2
|
+
export function formatXAxisNumber(value: number, domain: [number, number]): string {
|
|
3
|
+
if (!Number.isFinite(value)) return String(value)
|
|
4
|
+
const span = Math.abs(domain[1] - domain[0])
|
|
5
|
+
const digits =
|
|
6
|
+
Number.isFinite(span) && span > 0 ? Math.max(0, Math.ceil(-Math.log10(span)) + 1) : 0
|
|
7
|
+
const clean = (number: number) => Number(number.toPrecision(15))
|
|
8
|
+
const fractionalDigits = (number: number) => {
|
|
9
|
+
const text = String(clean(number))
|
|
10
|
+
return text.includes('e') ? 6 : (text.split('.')[1]?.length ?? 0)
|
|
11
|
+
}
|
|
12
|
+
// Wide windows can still have fractional boundaries; do not label 2.76 as 3.
|
|
13
|
+
const precision =
|
|
14
|
+
span >= 1 || span === 0 ? Math.max(digits, Math.min(6, fractionalDigits(value))) : digits
|
|
15
|
+
const fixed = (number: number) => {
|
|
16
|
+
const rounded = Number(clean(number).toFixed(Math.min(6, precision)))
|
|
17
|
+
return Math.abs(rounded) >= 1e21
|
|
18
|
+
? rounded.toLocaleString('en-US', { useGrouping: false, maximumFractionDigits: 6 })
|
|
19
|
+
: String(rounded)
|
|
20
|
+
}
|
|
21
|
+
if (
|
|
22
|
+
precision <= 6 &&
|
|
23
|
+
(value === 0 || Number(fixed(value)) !== 0) &&
|
|
24
|
+
(span === 0 || fixed(domain[0]) !== fixed(domain[1]))
|
|
25
|
+
) {
|
|
26
|
+
return fixed(value)
|
|
27
|
+
}
|
|
28
|
+
for (let significant = 2; significant <= 17; significant++) {
|
|
29
|
+
const scientific = (number: number) => {
|
|
30
|
+
const [mantissa, exponent] = number.toExponential(significant - 1).split('e')
|
|
31
|
+
return `${Number(mantissa)}e${exponent}`
|
|
32
|
+
}
|
|
33
|
+
if (span === 0 || scientific(domain[0]) !== scientific(domain[1])) return scientific(value)
|
|
34
|
+
}
|
|
35
|
+
return value.toExponential(16)
|
|
36
|
+
}
|
|
@@ -13,6 +13,7 @@ const enter = ref(true)
|
|
|
13
13
|
const update = ref(true)
|
|
14
14
|
const annotationEnabled = ref(true)
|
|
15
15
|
const effect = ref<AnnotationAnimationType>('scale')
|
|
16
|
+
const gridShape = ref('1x1')
|
|
16
17
|
const direction = ref<LineDrawingDirection>('left-to-right')
|
|
17
18
|
let generation = 0
|
|
18
19
|
function generate(count = 500): WaveformData {
|
|
@@ -55,13 +56,13 @@ function toggleAnnotation() {
|
|
|
55
56
|
<main class="animation-demo">
|
|
56
57
|
<h1>波形动画</h1>
|
|
57
58
|
<p>
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
分别控制入场、更新和标注效果。分页与网格调整重播波形绘制,标注等待对应系列完成,
|
|
60
|
+
坐标轴立即更新。拖动、缩放与导出会提交最终画面。
|
|
60
61
|
</p>
|
|
61
62
|
<div class="animation-demo__controls">
|
|
62
63
|
<label><input v-model="enabled" type="checkbox" /> 动画</label>
|
|
63
|
-
<label><input v-model="enter" type="checkbox" />
|
|
64
|
-
<label><input v-model="update" type="checkbox" />
|
|
64
|
+
<label><input v-model="enter" type="checkbox" /> 入场 / 切页 / 网格</label>
|
|
65
|
+
<label><input v-model="update" type="checkbox" /> 更新</label>
|
|
65
66
|
<label><input v-model="annotationEnabled" type="checkbox" /> 标注</label>
|
|
66
67
|
<label
|
|
67
68
|
>入场方向
|
|
@@ -80,6 +81,14 @@ function toggleAnnotation() {
|
|
|
80
81
|
<option value="bounce">弹跳</option>
|
|
81
82
|
</select></label
|
|
82
83
|
>
|
|
84
|
+
<label
|
|
85
|
+
>网格
|
|
86
|
+
<select v-model="gridShape" aria-label="网格">
|
|
87
|
+
<option value="1x1">1×1</option>
|
|
88
|
+
<option value="1x2">1×2</option>
|
|
89
|
+
<option value="2x2">2×2</option>
|
|
90
|
+
</select>
|
|
91
|
+
</label>
|
|
83
92
|
<button @click="key++">重新入场</button>
|
|
84
93
|
<button @click="replace(true)">更新振幅</button>
|
|
85
94
|
<button @click="replace(false)">改变点数</button>
|
|
@@ -89,7 +98,7 @@ function toggleAnnotation() {
|
|
|
89
98
|
:key="key"
|
|
90
99
|
:data="data"
|
|
91
100
|
:height="360"
|
|
92
|
-
:grid="{ rowCount:
|
|
101
|
+
:grid="{ rowCount: Number(gridShape[0]), columnCount: Number(gridShape[2]) }"
|
|
93
102
|
:animation-enabled="enabled"
|
|
94
103
|
:animation="{ enabled: enter, direction }"
|
|
95
104
|
:animation-update="{ enabled: update }"
|