waveform-analysis 0.1.73 → 0.1.75
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 +256 -46
- package/dist/assets/workerRuntime-BJiRmIZD.js +2446 -0
- package/dist/index.cjs +1 -2
- package/dist/index.js +1 -7115
- package/dist/style.css +1 -1
- package/dist/types/animation/WaveformAnimationController.d.ts +30 -0
- package/dist/types/animation/core/Animation.d.ts +13 -0
- package/dist/types/animation/core/Animator.d.ts +23 -0
- package/dist/types/animation/core/easing.d.ts +3 -0
- package/dist/types/animation/core/errors.d.ts +4 -0
- package/dist/types/animation/core/handle.d.ts +7 -0
- package/dist/types/animation/core/owner.d.ts +3 -0
- package/dist/types/animation/core/propertyGroups.d.ts +17 -0
- package/dist/types/animation/core/task.d.ts +29 -0
- package/dist/types/animation/core/types.d.ts +74 -0
- package/dist/types/animation/core/validation.d.ts +30 -0
- package/dist/types/animation/core/values.d.ts +8 -0
- package/dist/types/animation/presets/annotation.d.ts +4 -0
- package/dist/types/animation/presets/lineDrawing.d.ts +7 -0
- package/dist/types/animation/presets/shared.d.ts +14 -0
- package/dist/types/animation/presets/transitions.d.ts +4 -0
- package/dist/types/animation/types.d.ts +84 -0
- package/dist/types/components/WaveformChart.vue.d.ts +3 -2
- package/dist/types/components/animation/annotationPresentation.d.ts +17 -0
- package/dist/types/components/animation/chartExportVersion.d.ts +2 -0
- package/dist/types/components/animation/config.d.ts +31 -0
- package/dist/types/components/animation/context.d.ts +20 -0
- package/dist/types/components/animation/geometry.d.ts +10 -0
- package/dist/types/components/animation/paginationPresentation.d.ts +13 -0
- package/dist/types/components/animation/pathBuffer.d.ts +4 -0
- package/dist/types/components/animation/trackPresentation.d.ts +35 -0
- package/dist/types/components/animation/useWaveformAnimations.d.ts +27 -0
- package/dist/types/components/annotation/WaveformAnnotationContextMenu.vue.d.ts +2 -2
- package/dist/types/components/annotation/types.d.ts +7 -0
- package/dist/types/components/controls/useChartImageExport.d.ts +2 -0
- package/dist/types/components/core/layout.d.ts +2 -1
- package/dist/types/components/core/types.d.ts +8 -0
- package/dist/types/components/core/useWaveformChartController.d.ts +25 -0
- package/dist/types/components/core/useWaveformRenderSampling.d.ts +2 -0
- package/dist/types/components/core/waveformChartTypes.d.ts +2 -1
- package/dist/types/components/core/yAxisTitleLayout.d.ts +1 -1
- package/dist/types/components/interaction/useExtremaTickViewport.d.ts +1 -1
- package/dist/types/components/rendering/WaveformSeriesLayer.vue.d.ts +1 -0
- package/dist/types/components/rendering/WaveformTrack.vue.d.ts +4 -3
- package/dist/types/components/rendering/updateAxis.d.ts +5 -0
- package/dist/types/components/rendering/updateGrid.d.ts +3 -0
- package/dist/types/components/rendering/yAxisTickVisibility.d.ts +2 -0
- package/dist/types/components/utils/assignElement.d.ts +2 -0
- package/dist/types/demo/types.d.ts +2 -1
- package/dist/types/index.d.ts +9 -0
- package/dist/types/types/chart.d.ts +4 -2
- package/dist/types/views/AnimationDemo.vue.d.ts +3 -0
- package/package.json +5 -2
- package/src/App.vue +6 -3
- package/src/DemoRouterApp.vue +1 -0
- package/src/animation/WaveformAnimationController.ts +328 -0
- package/src/animation/core/Animation.ts +37 -0
- package/src/animation/core/Animator.ts +200 -0
- package/src/animation/core/easing.ts +74 -0
- package/src/animation/core/errors.ts +24 -0
- package/src/animation/core/handle.ts +34 -0
- package/src/animation/core/owner.ts +9 -0
- package/src/animation/core/propertyGroups.ts +143 -0
- package/src/animation/core/task.ts +153 -0
- package/src/animation/core/types.ts +81 -0
- package/src/animation/core/validation.ts +173 -0
- package/src/animation/core/values.ts +38 -0
- package/src/animation/presets/annotation.ts +66 -0
- package/src/animation/presets/lineDrawing.ts +51 -0
- package/src/animation/presets/shared.ts +58 -0
- package/src/animation/presets/transitions.ts +77 -0
- package/src/animation/types.ts +81 -0
- package/src/components/WaveformChart.vue +1 -0
- package/src/components/WaveformChartView.vue +13 -4
- package/src/components/animation/annotationPresentation.ts +145 -0
- package/src/components/animation/chartExportVersion.ts +5 -0
- package/src/components/animation/config.ts +87 -0
- package/src/components/animation/context.ts +56 -0
- package/src/components/animation/geometry.ts +148 -0
- package/src/components/animation/paginationPresentation.ts +114 -0
- package/src/components/animation/pathBuffer.ts +61 -0
- package/src/components/animation/trackPresentation.ts +172 -0
- package/src/components/animation/useWaveformAnimations.ts +420 -0
- package/src/components/annotation/WaveformAnnotationLayer.vue +43 -30
- package/src/components/annotation/types.ts +3 -0
- package/src/components/controls/useChartImageExport.ts +4 -0
- package/src/components/core/layout.ts +36 -22
- package/src/components/core/trackLayoutBuilder.ts +17 -23
- package/src/components/core/types.ts +4 -0
- package/src/components/core/useWaveformChartController.ts +24 -20
- package/src/components/core/useWaveformLayout.ts +3 -1
- package/src/components/core/useWaveformRenderSampling.ts +23 -1
- package/src/components/core/waveformChartTypes.ts +2 -1
- package/src/components/core/yAxisLayoutMetrics.ts +24 -16
- package/src/components/core/yAxisPadding.ts +10 -5
- package/src/components/core/yAxisTitleLayout.ts +9 -48
- package/src/components/interaction/useExtremaTickViewport.ts +1 -1
- package/src/components/rendering/WaveformSeriesLayer.vue +13 -1
- package/src/components/rendering/WaveformTrack.vue +6 -1
- package/src/components/rendering/WaveformTrackAxes.vue +64 -51
- package/src/components/rendering/WaveformTrackBackdrop.vue +35 -49
- package/src/components/rendering/updateAxis.ts +57 -0
- package/src/components/rendering/updateGrid.ts +42 -0
- package/src/components/rendering/yAxisTickVisibility.ts +45 -18
- package/src/components/utils/assignElement.ts +7 -0
- package/src/demo/DemoYAxisPaddingControls.vue +11 -3
- package/src/demo/types.ts +2 -1
- package/src/index.ts +53 -1
- package/src/router.ts +1 -0
- package/src/types/chart.ts +4 -2
- package/src/views/AnimationDemo.vue +129 -0
- package/dist/assets/workerRuntime-5p-EI-GL.js +0 -1
|
@@ -1,15 +1,8 @@
|
|
|
1
|
+
import { generateWaveformPath } from '../animation/geometry'
|
|
1
2
|
import { withMaximumTick, type YAxisPaddingOptions } from './yAxisPadding'
|
|
2
3
|
import { selectSeriesRenderSourcePoints } from '../../core/rendering'
|
|
3
4
|
import { pointSourceFromPoints } from '../../core/waveformPointSource'
|
|
4
|
-
import {
|
|
5
|
-
curveStep,
|
|
6
|
-
curveStepAfter,
|
|
7
|
-
curveStepBefore,
|
|
8
|
-
line,
|
|
9
|
-
scaleLinear,
|
|
10
|
-
zoomIdentity,
|
|
11
|
-
type ZoomTransform,
|
|
12
|
-
} from 'd3'
|
|
5
|
+
import { scaleLinear, zoomIdentity, type ZoomTransform } from 'd3'
|
|
13
6
|
|
|
14
7
|
import { type ResolvedWaveformRenderingOptions } from '../../core/rendering'
|
|
15
8
|
import type {
|
|
@@ -170,7 +163,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
170
163
|
? group.minimumTick
|
|
171
164
|
: undefined
|
|
172
165
|
const tickValues = withMaximumTick(majorTicks, maximumTick, minimumTick)
|
|
173
|
-
const { tickTextWidth } = axisTextMetrics(
|
|
166
|
+
const { tickTextWidth, titleTextWidth } = axisTextMetrics(
|
|
174
167
|
scale.domain() as [number, number],
|
|
175
168
|
false,
|
|
176
169
|
tickValues,
|
|
@@ -178,10 +171,13 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
178
171
|
tickCount,
|
|
179
172
|
)
|
|
180
173
|
const clearance =
|
|
181
|
-
|
|
174
|
+
Math.max(
|
|
175
|
+
tickTextWidth,
|
|
176
|
+
titleTextWidth +
|
|
177
|
+
Y_AXIS_LABEL_GAP +
|
|
178
|
+
resolveYAxisLabelBandWidth(options.yAxisPadding?.labelTextStyle),
|
|
179
|
+
) +
|
|
182
180
|
Y_AXIS_TICK_PADDING +
|
|
183
|
-
Y_AXIS_LABEL_GAP +
|
|
184
|
-
resolveYAxisLabelBandWidth(options.yAxisPadding?.labelTextStyle) +
|
|
185
181
|
Y_AXIS_OUTER_PADDING
|
|
186
182
|
const slot = options.yAxisSlots?.find(
|
|
187
183
|
(candidate) => candidate.side === group.side && candidate.sideIndex === sideIndex,
|
|
@@ -192,7 +188,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
192
188
|
? -sideOffsets.left
|
|
193
189
|
: cell.width + sideOffsets.right
|
|
194
190
|
const labelDistance =
|
|
195
|
-
|
|
191
|
+
titleTextWidth +
|
|
196
192
|
Y_AXIS_TICK_PADDING +
|
|
197
193
|
Y_AXIS_LABEL_GAP +
|
|
198
194
|
resolveYAxisLabelBandWidth(options.yAxisPadding?.labelTextStyle) / 2
|
|
@@ -261,17 +257,15 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
261
257
|
hasErrorPoints: trackSeries.hasErrorPoints,
|
|
262
258
|
},
|
|
263
259
|
)
|
|
264
|
-
const pathGenerator = line<WaveformPoint>()
|
|
265
|
-
.x((point) => xScale(point.x))
|
|
266
|
-
.y((point) => seriesYScale(point.y))
|
|
267
|
-
if (trackSeries.lineType === 'step-start') pathGenerator.curve(curveStepBefore)
|
|
268
|
-
if (trackSeries.lineType === 'step-middle') pathGenerator.curve(curveStep)
|
|
269
|
-
if (trackSeries.lineType === 'step-end' || trackSeries.lineType === 'step-after') {
|
|
270
|
-
pathGenerator.curve(curveStepAfter)
|
|
271
|
-
}
|
|
272
260
|
return {
|
|
273
261
|
series: trackSeries,
|
|
274
|
-
path:
|
|
262
|
+
path: generateWaveformPath(
|
|
263
|
+
renderPoints.linePoints,
|
|
264
|
+
trackSeries.lineType,
|
|
265
|
+
xScale,
|
|
266
|
+
seriesYScale,
|
|
267
|
+
),
|
|
268
|
+
lineRenderPoints: renderPoints.linePoints,
|
|
275
269
|
pointRenderPoints: renderPoints.pointRenderPoints,
|
|
276
270
|
errorBarRenderPoints: renderPoints.errorBarRenderPoints,
|
|
277
271
|
yScale: seriesYScale,
|
|
@@ -47,6 +47,10 @@ export interface DisplayTrack {
|
|
|
47
47
|
export interface TrackSeriesPath {
|
|
48
48
|
series: DisplaySeries
|
|
49
49
|
path: string | null
|
|
50
|
+
lineRenderPoints?: WaveformPoint[]
|
|
51
|
+
presentationKey?: string
|
|
52
|
+
opacity?: number
|
|
53
|
+
animationClip?: { id: string; x: number; width: number }
|
|
50
54
|
pointRenderPoints: WaveformPoint[]
|
|
51
55
|
errorBarRenderPoints: WaveformPoint[]
|
|
52
56
|
yScale: ScaleLinear<number, number>
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { assignElement } from '../utils/assignElement'
|
|
2
|
+
import { chartExportVersion } from '../animation/chartExportVersion'
|
|
3
|
+
import { useWaveformAnimations } from '../animation/useWaveformAnimations'
|
|
2
4
|
import { useChartImageExport } from '../controls/useChartImageExport'
|
|
3
5
|
import { useControlMode } from '../controls/useControlMode'
|
|
4
6
|
import { useChartCommands } from '../controls/useChartCommands'
|
|
@@ -16,9 +18,7 @@ import {
|
|
|
16
18
|
watch,
|
|
17
19
|
watchEffect,
|
|
18
20
|
type ComponentPublicInstance,
|
|
19
|
-
type Ref,
|
|
20
21
|
} from 'vue'
|
|
21
|
-
|
|
22
22
|
import { useWaveformInstanceId } from '../../utils/waveformId'
|
|
23
23
|
import { useWaveformAnnotationInteraction, type AnnotationSeriesCandidate } from '../annotation'
|
|
24
24
|
import { useWaveformChartAnnotations } from '../annotation/useWaveformChartAnnotations'
|
|
@@ -41,14 +41,6 @@ import type {
|
|
|
41
41
|
ViewportSelectionState,
|
|
42
42
|
WaveformChartEmit,
|
|
43
43
|
} from './waveformChartTypes'
|
|
44
|
-
|
|
45
|
-
function assignElement<T extends Element>(
|
|
46
|
-
target: Ref<T | undefined>,
|
|
47
|
-
element: Element | ComponentPublicInstance | null,
|
|
48
|
-
) {
|
|
49
|
-
target.value = element instanceof Element ? (element as T) : undefined
|
|
50
|
-
}
|
|
51
|
-
|
|
52
44
|
export function useWaveformChartController(
|
|
53
45
|
inputProps: ResolvedWaveformChartProps,
|
|
54
46
|
emit: WaveformChartEmit,
|
|
@@ -186,7 +178,17 @@ export function useWaveformChartController(
|
|
|
186
178
|
renderingOptions,
|
|
187
179
|
linePointOverrides,
|
|
188
180
|
})
|
|
189
|
-
|
|
181
|
+
const animations = useWaveformAnimations({
|
|
182
|
+
props,
|
|
183
|
+
currentPage,
|
|
184
|
+
containerSize: computed(() => [observedWidth.value, observedHeight.value]),
|
|
185
|
+
layoutReady: computed(() => observedWidth.value > 0 && observedHeight.value > 0),
|
|
186
|
+
tracks: trackLayouts,
|
|
187
|
+
preparedSeries,
|
|
188
|
+
innerWidth,
|
|
189
|
+
innerHeight: presentation.innerHeight,
|
|
190
|
+
sampling,
|
|
191
|
+
})
|
|
190
192
|
const paginationVisible = computed(
|
|
191
193
|
() => gridOptions.value.showPagination && pageCount.value > 1 && !isCleanView.value,
|
|
192
194
|
)
|
|
@@ -389,10 +391,8 @@ export function useWaveformChartController(
|
|
|
389
391
|
height: chartHeight.value,
|
|
390
392
|
titleHeight: titleAreaHeight.value,
|
|
391
393
|
}),
|
|
392
|
-
version: () =>
|
|
393
|
-
props
|
|
394
|
-
JSON.stringify({
|
|
395
|
-
...chartExportOptions(props),
|
|
394
|
+
version: () =>
|
|
395
|
+
chartExportVersion(props, {
|
|
396
396
|
hidden: [...hiddenSeriesIdSet.value],
|
|
397
397
|
width: chartWidth.value,
|
|
398
398
|
height: chartHeight.value,
|
|
@@ -404,7 +404,8 @@ export function useWaveformChartController(
|
|
|
404
404
|
independentY: independentYDomains.value,
|
|
405
405
|
boundaries: boundaries.value,
|
|
406
406
|
}),
|
|
407
|
-
|
|
407
|
+
prepare: animations.prepareExport,
|
|
408
|
+
release: animations.releaseExport,
|
|
408
409
|
ready: sampling.ready,
|
|
409
410
|
})
|
|
410
411
|
|
|
@@ -418,7 +419,11 @@ export function useWaveformChartController(
|
|
|
418
419
|
...annotations,
|
|
419
420
|
...viewport,
|
|
420
421
|
...hover,
|
|
421
|
-
...commands,
|
|
422
|
+
...animations.wrap(commands),
|
|
423
|
+
presentationTracks: animations.presentationTracks,
|
|
424
|
+
presentationAnnotations: animations.presentationAnnotations,
|
|
425
|
+
paginationOpacity: animations.paginationOpacity,
|
|
426
|
+
stabilizeAnimations: animations.stabilizeAnimations,
|
|
422
427
|
...imageExport,
|
|
423
428
|
margin,
|
|
424
429
|
getPageSize,
|
|
@@ -431,7 +436,7 @@ export function useWaveformChartController(
|
|
|
431
436
|
contextMenu: annotationInteraction.contextMenu,
|
|
432
437
|
editorSeriesOptions,
|
|
433
438
|
clipPathId,
|
|
434
|
-
goToPage: lifecycle.goToPage,
|
|
439
|
+
goToPage: animations.wrapPagination(lifecycle.goToPage, pageCount),
|
|
435
440
|
setContainer: (element: Element | ComponentPublicInstance | null) =>
|
|
436
441
|
assignElement(container, element),
|
|
437
442
|
setSvgElement: (element: Element | ComponentPublicInstance | null) =>
|
|
@@ -442,5 +447,4 @@ export function useWaveformChartController(
|
|
|
442
447
|
assignElement(sharedOverlayElement, element),
|
|
443
448
|
})
|
|
444
449
|
}
|
|
445
|
-
|
|
446
450
|
export type WaveformChartController = ReturnType<typeof useWaveformChartController>
|
|
@@ -281,7 +281,9 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
281
281
|
}),
|
|
282
282
|
)
|
|
283
283
|
useExtremaTickViewport(
|
|
284
|
-
() =>
|
|
284
|
+
() =>
|
|
285
|
+
Number(props.axes?.y?.maximumTickVisible !== false) * 2 +
|
|
286
|
+
Number(props.axes?.y?.minimumTickVisible !== false),
|
|
285
287
|
innerWidth,
|
|
286
288
|
trackLayouts,
|
|
287
289
|
sharedTransform,
|
|
@@ -103,6 +103,7 @@ function errorPayload(
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export function useWaveformRenderSampling(context: SamplingContext) {
|
|
106
|
+
const resultState = shallowRef<'success' | 'fallback' | 'error'>('success')
|
|
106
107
|
const settledSignature = shallowRef<string>()
|
|
107
108
|
const dataEpoch = shallowRef(0)
|
|
108
109
|
const session = createWaveformSamplingSession()
|
|
@@ -292,10 +293,14 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
292
293
|
requestId: ++session.requestId,
|
|
293
294
|
series: batch,
|
|
294
295
|
})
|
|
295
|
-
if (result.type !== 'sample-viewport-response')
|
|
296
|
+
if (result.type !== 'sample-viewport-response') {
|
|
297
|
+
if (samplingScheduler.isCurrent(token)) resultState.value = 'error'
|
|
298
|
+
return
|
|
299
|
+
}
|
|
296
300
|
response = result
|
|
297
301
|
} catch (error) {
|
|
298
302
|
if (!samplingScheduler.isCurrent(token)) return
|
|
303
|
+
resultState.value = 'error'
|
|
299
304
|
emitErrorOnce(
|
|
300
305
|
errorPayload(
|
|
301
306
|
error instanceof Error ? error.message : String(error),
|
|
@@ -310,8 +315,18 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
310
315
|
return
|
|
311
316
|
}
|
|
312
317
|
if (!samplingScheduler.isCurrent(token)) return
|
|
318
|
+
if (response.workerError || client.workerFailureReason) resultState.value = 'fallback'
|
|
313
319
|
const sourceById = new Map(sampledTargets.map((target) => [target.series.id, target.source]))
|
|
320
|
+
if (
|
|
321
|
+
sampledTargets.some(
|
|
322
|
+
(target) =>
|
|
323
|
+
!response.results.some((result) => result.seriesId === target.series.id && result.output),
|
|
324
|
+
)
|
|
325
|
+
)
|
|
326
|
+
resultState.value = 'error'
|
|
314
327
|
response.results.forEach((result) => {
|
|
328
|
+
if (result.status !== 'ok' && resultState.value !== 'error')
|
|
329
|
+
resultState.value = result.output ? 'fallback' : 'error'
|
|
315
330
|
const source = sourceById.get(result.seriesId)
|
|
316
331
|
const selected = source ? sourcePoints(result, source) : undefined
|
|
317
332
|
if (selected && (result.status === 'ok' || result.status === 'wasm-unavailable')) {
|
|
@@ -369,6 +384,7 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
369
384
|
const scheduleSampling = (hysteresis: boolean) => {
|
|
370
385
|
settledSignature.value = undefined
|
|
371
386
|
const signature = targetSignature.value
|
|
387
|
+
resultState.value = 'success'
|
|
372
388
|
samplingScheduler.schedule(async (token) => {
|
|
373
389
|
try {
|
|
374
390
|
await runSampling(token, hysteresis)
|
|
@@ -410,6 +426,12 @@ export function useWaveformRenderSampling(context: SamplingContext) {
|
|
|
410
426
|
onScopeDispose(() => session.dispose())
|
|
411
427
|
|
|
412
428
|
return {
|
|
429
|
+
requestSignature: targetSignature,
|
|
430
|
+
state: computed(() =>
|
|
431
|
+
settledSignature.value === targetSignature.value && !session.autoModeSettleTimer
|
|
432
|
+
? resultState.value
|
|
433
|
+
: 'pending',
|
|
434
|
+
),
|
|
413
435
|
linePointOverrides: context.linePointOverrides,
|
|
414
436
|
ready: () => settledSignature.value === targetSignature.value && !session.autoModeSettleTimer,
|
|
415
437
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WaveformAnimationProps } from '../../animation/types'
|
|
1
2
|
import type { WaveformToolbarOptions, WaveformControlState } from '../../types/controls'
|
|
2
3
|
import type {
|
|
3
4
|
WaveformAnnotation,
|
|
@@ -26,7 +27,7 @@ import type {
|
|
|
26
27
|
} from '../data/types'
|
|
27
28
|
import type { WaveformGridOptions } from './grid'
|
|
28
29
|
|
|
29
|
-
export interface WaveformChartProps {
|
|
30
|
+
export interface WaveformChartProps extends WaveformAnimationProps {
|
|
30
31
|
toolbar?: boolean | WaveformToolbarOptions
|
|
31
32
|
data: WaveformData
|
|
32
33
|
displayMode?: WaveformDisplayMode
|
|
@@ -60,29 +60,37 @@ export function resolveYAxisLayoutMetrics(
|
|
|
60
60
|
padding,
|
|
61
61
|
),
|
|
62
62
|
)
|
|
63
|
-
.map(
|
|
64
|
-
(
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
).tickTextWidth,
|
|
63
|
+
.map((group) =>
|
|
64
|
+
axisTextMetrics(
|
|
65
|
+
group.domain,
|
|
66
|
+
nice,
|
|
67
|
+
undefined,
|
|
68
|
+
showUnits ? group.seriesList[0]?.unit : undefined,
|
|
69
|
+
tickCount,
|
|
70
|
+
compact,
|
|
71
|
+
measureTextWidth,
|
|
72
|
+
group.maximumTick,
|
|
73
|
+
group.minimumTick,
|
|
74
|
+
),
|
|
76
75
|
)
|
|
77
|
-
const tickTextWidth = Math.max(
|
|
76
|
+
const tickTextWidth = Math.max(
|
|
77
|
+
Y_AXIS_CHARACTER_WIDTH,
|
|
78
|
+
...axisText.map((text) => text.tickTextWidth),
|
|
79
|
+
)
|
|
80
|
+
const titleTextWidth = Math.max(
|
|
81
|
+
Y_AXIS_CHARACTER_WIDTH,
|
|
82
|
+
...axisText.map((text) => text.titleTextWidth),
|
|
83
|
+
)
|
|
78
84
|
const tickClearance = tickTextWidth + Y_AXIS_TICK_PADDING + Y_AXIS_OUTER_PADDING
|
|
79
85
|
const labelCenterX = -(
|
|
80
86
|
Y_AXIS_TICK_PADDING +
|
|
81
|
-
|
|
87
|
+
titleTextWidth +
|
|
82
88
|
Y_AXIS_LABEL_GAP +
|
|
83
89
|
labelBandWidth / 2
|
|
84
90
|
)
|
|
85
91
|
const fullClearance =
|
|
86
|
-
tickTextWidth
|
|
92
|
+
Math.max(tickTextWidth, titleTextWidth + Y_AXIS_LABEL_GAP + labelBandWidth) +
|
|
93
|
+
Y_AXIS_TICK_PADDING +
|
|
94
|
+
Y_AXIS_OUTER_PADDING
|
|
87
95
|
return { tickClearance, fullClearance, labelCenterX }
|
|
88
96
|
}
|
|
@@ -15,7 +15,7 @@ export function applyYAxisPadding(
|
|
|
15
15
|
options: YAxisPaddingOptions | number,
|
|
16
16
|
): YAxisSeriesGroup {
|
|
17
17
|
const config = typeof options === 'number' ? { upperPaddingRatio: options } : options
|
|
18
|
-
if (group.fixed) return withYAxisExtremaTicks(group, config
|
|
18
|
+
if (group.fixed) return withYAxisExtremaTicks(group, config)
|
|
19
19
|
const upper = paddingRatio(config?.upperPaddingEnabled, config?.upperPaddingRatio)
|
|
20
20
|
const lower = paddingRatio(config?.lowerPaddingEnabled, config?.lowerPaddingRatio)
|
|
21
21
|
const [a, b] = group.domain
|
|
@@ -27,11 +27,14 @@ export function applyYAxisPadding(
|
|
|
27
27
|
...group,
|
|
28
28
|
domain: viewport ?? [Number.isFinite(min) ? min : a, Number.isFinite(max) ? max : b],
|
|
29
29
|
},
|
|
30
|
-
config
|
|
30
|
+
config,
|
|
31
31
|
)
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
function withYAxisExtremaTicks(
|
|
34
|
+
function withYAxisExtremaTicks(
|
|
35
|
+
group: YAxisSeriesGroup,
|
|
36
|
+
options: YAxisPaddingOptions,
|
|
37
|
+
): YAxisSeriesGroup {
|
|
35
38
|
const maxima = group.seriesList.flatMap((series) =>
|
|
36
39
|
Number.isFinite(series.rawYMaximum) ? [series.rawYMaximum!] : [],
|
|
37
40
|
)
|
|
@@ -40,8 +43,10 @@ function withYAxisExtremaTicks(group: YAxisSeriesGroup, visible = true): YAxisSe
|
|
|
40
43
|
)
|
|
41
44
|
return {
|
|
42
45
|
...group,
|
|
43
|
-
maximumTick:
|
|
44
|
-
|
|
46
|
+
maximumTick:
|
|
47
|
+
options?.maximumTickVisible !== false && maxima.length ? Math.max(...maxima) : undefined,
|
|
48
|
+
minimumTick:
|
|
49
|
+
options?.minimumTickVisible !== false && minima.length ? Math.min(...minima) : undefined,
|
|
45
50
|
}
|
|
46
51
|
}
|
|
47
52
|
|
|
@@ -1,13 +1,9 @@
|
|
|
1
1
|
import { formatYAxisTickLayoutLabel } from './layout'
|
|
2
2
|
import type { TrackLayout, WaveformYAxisLayout } from './types'
|
|
3
3
|
import type { WaveformYAxisLabelTextStyle } from '../../types'
|
|
4
|
-
import { resolveYAxisLabelBandWidth
|
|
4
|
+
import { resolveYAxisLabelBandWidth } from './yAxisLabelStyle'
|
|
5
5
|
import { Y_AXIS_CHARACTER_WIDTH, Y_AXIS_TICK_PADDING } from './yAxisConstants'
|
|
6
6
|
|
|
7
|
-
// Match the 11px ticks in WaveformTrack.css.
|
|
8
|
-
const TICK_FONT_SIZE = 11
|
|
9
|
-
const TEXT_GAP = 2
|
|
10
|
-
|
|
11
7
|
interface TitleLayoutOptions {
|
|
12
8
|
labelTextStyle?: WaveformYAxisLabelTextStyle
|
|
13
9
|
showUnits?: boolean
|
|
@@ -15,52 +11,20 @@ interface TitleLayoutOptions {
|
|
|
15
11
|
measureTextWidth?: (text: string) => number
|
|
16
12
|
}
|
|
17
13
|
|
|
18
|
-
function estimateTitleWidth(title: string, fontSize: number): number {
|
|
19
|
-
return Array.from(title).reduce((width, character) => {
|
|
20
|
-
// Latin labels average well below one em; full-width characters occupy one em.
|
|
21
|
-
const em = /[\u2e80-\u9fff\uf900-\ufaff\uff01-\uff60]/u.test(character)
|
|
22
|
-
? 1
|
|
23
|
-
: /[MW@%]/u.test(character)
|
|
24
|
-
? 0.95
|
|
25
|
-
: /[ilI1., :;|'!]/u.test(character)
|
|
26
|
-
? 0.4
|
|
27
|
-
: 0.65
|
|
28
|
-
return width + em * fontSize
|
|
29
|
-
}, 0)
|
|
30
|
-
}
|
|
31
|
-
|
|
32
14
|
type TitleTrack = Pick<TrackLayout, 'height' | 'column' | 'yAxes'>
|
|
33
15
|
|
|
34
|
-
function titleDistance(
|
|
35
|
-
track: TitleTrack,
|
|
36
|
-
axis: WaveformYAxisLayout,
|
|
37
|
-
yLabel: string | undefined,
|
|
38
|
-
options: TitleLayoutOptions,
|
|
39
|
-
): number {
|
|
16
|
+
function titleDistance(axis: WaveformYAxisLayout, options: TitleLayoutOptions): number {
|
|
40
17
|
const series = axis.seriesList[0]
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
const titleTop = track.height / 2 - halfTitleHeight - TEXT_GAP
|
|
45
|
-
const titleBottom = track.height / 2 + halfTitleHeight + TEXT_GAP
|
|
46
|
-
const lowestTick = Math.min(...axis.tickValues)
|
|
47
|
-
const highestTick = Math.max(...axis.tickValues)
|
|
48
|
-
const tickWidths = axis.tickValues.flatMap((value) => {
|
|
49
|
-
// D3 places ticks at scale(value) + 0.5. The lowest label is bottom-aligned;
|
|
50
|
-
// all other labels are vertically centered using dy="0.32em".
|
|
51
|
-
const y = axis.scale(value) + 0.5
|
|
52
|
-
const isTopAligned = options.compact && value === highestTick
|
|
53
|
-
const top = y - (value === lowestTick ? TICK_FONT_SIZE : isTopAligned ? 0 : TICK_FONT_SIZE / 2)
|
|
54
|
-
const bottom =
|
|
55
|
-
value === lowestTick ? y : y + (isTopAligned ? TICK_FONT_SIZE : TICK_FONT_SIZE / 2)
|
|
56
|
-
if (bottom < titleTop || top > titleBottom) return []
|
|
18
|
+
// Reserve the widest tick, including extrema, regardless of the frame height.
|
|
19
|
+
// The layout formatter excludes scientific multipliers but retains the numeric label.
|
|
20
|
+
const tickWidths = axis.tickValues.map((value) => {
|
|
57
21
|
const text = formatYAxisTickLayoutLabel(
|
|
58
22
|
value,
|
|
59
23
|
axis.scale.domain() as [number, number],
|
|
60
24
|
axis.tickValues,
|
|
61
25
|
options.showUnits === false ? undefined : series?.unit,
|
|
62
26
|
)
|
|
63
|
-
return
|
|
27
|
+
return options.measureTextWidth?.(text) ?? text.length * Y_AXIS_CHARACTER_WIDTH
|
|
64
28
|
})
|
|
65
29
|
// The title band leaves 4px beside each glyph.
|
|
66
30
|
return (
|
|
@@ -72,7 +36,7 @@ function titleDistance(
|
|
|
72
36
|
|
|
73
37
|
export function alignLeftYAxisTitles(
|
|
74
38
|
layouts: TitleTrack[],
|
|
75
|
-
|
|
39
|
+
_yLabel?: string,
|
|
76
40
|
options: TitleLayoutOptions = {},
|
|
77
41
|
): void {
|
|
78
42
|
const distances = new Map<string, number>()
|
|
@@ -81,11 +45,8 @@ export function alignLeftYAxisTitles(
|
|
|
81
45
|
for (const axis of track.yAxes) {
|
|
82
46
|
if (axis.side !== 'left') continue
|
|
83
47
|
const key = `${track.column}:${leftIndex++}`
|
|
84
|
-
const distance = titleDistance(
|
|
85
|
-
|
|
86
|
-
// alignment can never push them beyond the space reserved at the chart edge.
|
|
87
|
-
const boundedDistance = options.compact ? Math.min(axis.x - axis.labelX, distance) : distance
|
|
88
|
-
distances.set(key, Math.max(distances.get(key) ?? 0, boundedDistance))
|
|
48
|
+
const distance = titleDistance(axis, options)
|
|
49
|
+
distances.set(key, Math.max(distances.get(key) ?? 0, distance))
|
|
89
50
|
}
|
|
90
51
|
}
|
|
91
52
|
for (const track of layouts) {
|
|
@@ -5,7 +5,7 @@ import type { TrackLayout } from '../core/types'
|
|
|
5
5
|
|
|
6
6
|
/** Keep X coordinates stable when toggling extrema changes the reserved Y-label width. */
|
|
7
7
|
export function useExtremaTickViewport(
|
|
8
|
-
visible: () =>
|
|
8
|
+
visible: () => number,
|
|
9
9
|
innerWidth: ComputedRef<number>,
|
|
10
10
|
tracks: ComputedRef<TrackLayout[]>,
|
|
11
11
|
sharedTransform: ShallowRef<ZoomTransform>,
|
|
@@ -7,6 +7,7 @@ import { waveformLineDasharray, waveformPointSeriesPath } from './seriesStyle'
|
|
|
7
7
|
|
|
8
8
|
const props = defineProps<{
|
|
9
9
|
track: TrackLayout
|
|
10
|
+
paginationOpacity?: number
|
|
10
11
|
clipPathId: string
|
|
11
12
|
}>()
|
|
12
13
|
|
|
@@ -51,11 +52,22 @@ const renderedSeriesPaths = computed<RenderedSeriesPath[]>(() =>
|
|
|
51
52
|
>
|
|
52
53
|
<g
|
|
53
54
|
v-for="seriesPath in renderedSeriesPaths"
|
|
54
|
-
:key="seriesPath.series.id"
|
|
55
|
+
:key="seriesPath.presentationKey ?? seriesPath.series.id"
|
|
56
|
+
:opacity="(seriesPath.opacity ?? 1) * (paginationOpacity ?? 1)"
|
|
57
|
+
:clip-path="seriesPath.animationClip ? `url(#${seriesPath.animationClip.id})` : undefined"
|
|
55
58
|
class="waveform-track__series-item waveform-chart__series"
|
|
56
59
|
:data-series-id="seriesPath.series.id"
|
|
57
60
|
:data-series-name="seriesPath.series.name || undefined"
|
|
58
61
|
>
|
|
62
|
+
<defs v-if="seriesPath.animationClip">
|
|
63
|
+
<clipPath :id="seriesPath.animationClip.id" clipPathUnits="userSpaceOnUse">
|
|
64
|
+
<rect
|
|
65
|
+
:x="seriesPath.animationClip.x"
|
|
66
|
+
:width="seriesPath.animationClip.width"
|
|
67
|
+
:height="track.height"
|
|
68
|
+
/>
|
|
69
|
+
</clipPath>
|
|
70
|
+
</defs>
|
|
59
71
|
<path
|
|
60
72
|
v-if="seriesPath.path"
|
|
61
73
|
class="waveform-track__line waveform-chart__line"
|
|
@@ -12,6 +12,7 @@ import WaveformTrackBackdrop from './WaveformTrackBackdrop.vue'
|
|
|
12
12
|
|
|
13
13
|
interface Props {
|
|
14
14
|
track: TrackLayout
|
|
15
|
+
paginationOpacity?: number
|
|
15
16
|
clipPathId: string
|
|
16
17
|
innerWidth: number
|
|
17
18
|
zoomable: boolean
|
|
@@ -136,7 +137,11 @@ const frameInset = computed(() =>
|
|
|
136
137
|
aria-hidden="true"
|
|
137
138
|
/>
|
|
138
139
|
|
|
139
|
-
<WaveformSeriesLayer
|
|
140
|
+
<WaveformSeriesLayer
|
|
141
|
+
:track="track"
|
|
142
|
+
:clip-path-id="clipPathId"
|
|
143
|
+
:pagination-opacity="paginationOpacity"
|
|
144
|
+
/>
|
|
140
145
|
<WaveformZeroLines
|
|
141
146
|
:track="track"
|
|
142
147
|
:clip-path-id="clipPathId"
|