waveform-analysis 0.1.74 → 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.
Files changed (97) hide show
  1. package/README.md +246 -39
  2. package/dist/assets/workerRuntime-BJiRmIZD.js +2446 -0
  3. package/dist/index.cjs +1 -2
  4. package/dist/index.js +1 -7119
  5. package/dist/style.css +1 -1
  6. package/dist/types/animation/WaveformAnimationController.d.ts +30 -0
  7. package/dist/types/animation/core/Animation.d.ts +13 -0
  8. package/dist/types/animation/core/Animator.d.ts +23 -0
  9. package/dist/types/animation/core/easing.d.ts +3 -0
  10. package/dist/types/animation/core/errors.d.ts +4 -0
  11. package/dist/types/animation/core/handle.d.ts +7 -0
  12. package/dist/types/animation/core/owner.d.ts +3 -0
  13. package/dist/types/animation/core/propertyGroups.d.ts +17 -0
  14. package/dist/types/animation/core/task.d.ts +29 -0
  15. package/dist/types/animation/core/types.d.ts +74 -0
  16. package/dist/types/animation/core/validation.d.ts +30 -0
  17. package/dist/types/animation/core/values.d.ts +8 -0
  18. package/dist/types/animation/presets/annotation.d.ts +4 -0
  19. package/dist/types/animation/presets/lineDrawing.d.ts +7 -0
  20. package/dist/types/animation/presets/shared.d.ts +14 -0
  21. package/dist/types/animation/presets/transitions.d.ts +4 -0
  22. package/dist/types/animation/types.d.ts +84 -0
  23. package/dist/types/components/WaveformChart.vue.d.ts +3 -2
  24. package/dist/types/components/animation/annotationPresentation.d.ts +17 -0
  25. package/dist/types/components/animation/chartExportVersion.d.ts +2 -0
  26. package/dist/types/components/animation/config.d.ts +31 -0
  27. package/dist/types/components/animation/context.d.ts +20 -0
  28. package/dist/types/components/animation/geometry.d.ts +10 -0
  29. package/dist/types/components/animation/paginationPresentation.d.ts +13 -0
  30. package/dist/types/components/animation/pathBuffer.d.ts +4 -0
  31. package/dist/types/components/animation/trackPresentation.d.ts +35 -0
  32. package/dist/types/components/animation/useWaveformAnimations.d.ts +27 -0
  33. package/dist/types/components/annotation/WaveformAnnotationContextMenu.vue.d.ts +2 -2
  34. package/dist/types/components/annotation/types.d.ts +7 -0
  35. package/dist/types/components/controls/useChartImageExport.d.ts +2 -0
  36. package/dist/types/components/core/types.d.ts +8 -0
  37. package/dist/types/components/core/useWaveformChartController.d.ts +25 -0
  38. package/dist/types/components/core/useWaveformRenderSampling.d.ts +2 -0
  39. package/dist/types/components/core/waveformChartTypes.d.ts +2 -1
  40. package/dist/types/components/rendering/WaveformSeriesLayer.vue.d.ts +1 -0
  41. package/dist/types/components/rendering/WaveformTrack.vue.d.ts +4 -3
  42. package/dist/types/components/rendering/updateAxis.d.ts +5 -0
  43. package/dist/types/components/rendering/updateGrid.d.ts +3 -0
  44. package/dist/types/components/rendering/yAxisTickVisibility.d.ts +2 -0
  45. package/dist/types/components/utils/assignElement.d.ts +2 -0
  46. package/dist/types/index.d.ts +9 -0
  47. package/dist/types/views/AnimationDemo.vue.d.ts +3 -0
  48. package/package.json +5 -2
  49. package/src/DemoRouterApp.vue +1 -0
  50. package/src/animation/WaveformAnimationController.ts +328 -0
  51. package/src/animation/core/Animation.ts +37 -0
  52. package/src/animation/core/Animator.ts +200 -0
  53. package/src/animation/core/easing.ts +74 -0
  54. package/src/animation/core/errors.ts +24 -0
  55. package/src/animation/core/handle.ts +34 -0
  56. package/src/animation/core/owner.ts +9 -0
  57. package/src/animation/core/propertyGroups.ts +143 -0
  58. package/src/animation/core/task.ts +153 -0
  59. package/src/animation/core/types.ts +81 -0
  60. package/src/animation/core/validation.ts +173 -0
  61. package/src/animation/core/values.ts +38 -0
  62. package/src/animation/presets/annotation.ts +66 -0
  63. package/src/animation/presets/lineDrawing.ts +51 -0
  64. package/src/animation/presets/shared.ts +58 -0
  65. package/src/animation/presets/transitions.ts +77 -0
  66. package/src/animation/types.ts +81 -0
  67. package/src/components/WaveformChart.vue +1 -0
  68. package/src/components/WaveformChartView.vue +13 -4
  69. package/src/components/animation/annotationPresentation.ts +145 -0
  70. package/src/components/animation/chartExportVersion.ts +5 -0
  71. package/src/components/animation/config.ts +87 -0
  72. package/src/components/animation/context.ts +56 -0
  73. package/src/components/animation/geometry.ts +148 -0
  74. package/src/components/animation/paginationPresentation.ts +114 -0
  75. package/src/components/animation/pathBuffer.ts +61 -0
  76. package/src/components/animation/trackPresentation.ts +172 -0
  77. package/src/components/animation/useWaveformAnimations.ts +420 -0
  78. package/src/components/annotation/WaveformAnnotationLayer.vue +43 -30
  79. package/src/components/annotation/types.ts +3 -0
  80. package/src/components/controls/useChartImageExport.ts +4 -0
  81. package/src/components/core/trackLayoutBuilder.ts +9 -18
  82. package/src/components/core/types.ts +4 -0
  83. package/src/components/core/useWaveformChartController.ts +24 -20
  84. package/src/components/core/useWaveformRenderSampling.ts +23 -1
  85. package/src/components/core/waveformChartTypes.ts +2 -1
  86. package/src/components/rendering/WaveformSeriesLayer.vue +13 -1
  87. package/src/components/rendering/WaveformTrack.vue +6 -1
  88. package/src/components/rendering/WaveformTrackAxes.vue +64 -51
  89. package/src/components/rendering/WaveformTrackBackdrop.vue +35 -49
  90. package/src/components/rendering/updateAxis.ts +57 -0
  91. package/src/components/rendering/updateGrid.ts +42 -0
  92. package/src/components/rendering/yAxisTickVisibility.ts +45 -18
  93. package/src/components/utils/assignElement.ts +7 -0
  94. package/src/index.ts +52 -0
  95. package/src/router.ts +1 -0
  96. package/src/views/AnimationDemo.vue +129 -0
  97. package/dist/assets/workerRuntime-5p-EI-GL.js +0 -1
@@ -0,0 +1,148 @@
1
+ import type { ScaleLinear } from 'd3'
2
+ import type { WaveformPoint, WaveformLineType } from '@/types'
3
+ import type { TrackSeriesPath } from '../core/types'
4
+ import type { NumericGeometry } from '@/animation/types'
5
+ import { resolveWaveformPointErrors } from '@/core'
6
+ import { createPathBuffer } from './pathBuffer'
7
+
8
+ // Weak ownership releases cached SVG strings and point arrays with their numeric snapshots.
9
+ const projections = new WeakMap<
10
+ NumericGeometry,
11
+ {
12
+ path: TrackSeriesPath
13
+ x: ScaleLinear<number, number>
14
+ y: ScaleLinear<number, number>
15
+ result: TrackSeriesPath
16
+ }
17
+ >()
18
+ const dirty = new WeakSet<NumericGeometry>()
19
+ const transitions = new WeakMap<NumericGeometry, WeakMap<NumericGeometry, NumericGeometry>>()
20
+ const snapshots = new WeakMap<TrackSeriesPath, NumericGeometry>()
21
+ const pathBuffers = new WeakMap<WaveformPoint[], ReturnType<typeof createPathBuffer>>()
22
+
23
+ /** Private presentation buffer; the source/target snapshots and business points stay immutable. */
24
+ export function interpolateGeometry(from: NumericGeometry, to: NumericGeometry, p: number) {
25
+ let targets = transitions.get(from)
26
+ if (!targets) transitions.set(from, (targets = new WeakMap()))
27
+ let result = targets.get(to)
28
+ if (!result) {
29
+ result = Object.fromEntries(
30
+ Object.entries(to).map(([key, rows]) => [
31
+ key,
32
+ // oxlint-disable-next-line unicorn/no-new-array -- Allocate fixed-length interpolation output.
33
+ (rows as number[][]).map((row) => new Array<number>(row.length)),
34
+ ]),
35
+ )
36
+ targets.set(to, result)
37
+ }
38
+ for (const key of Object.keys(to)) {
39
+ const a = from[key] as number[][],
40
+ b = to[key] as number[][],
41
+ output = result[key] as number[][]
42
+ for (let i = 0; i < b.length; i++)
43
+ for (let j = 0; j < b[i].length; j++) {
44
+ const value = p === 0 ? a[i][j] : p === 1 ? b[i][j] : (1 - p) * a[i][j] + p * b[i][j]
45
+ if (!Number.isFinite(value)) throw new RangeError('Nonfinite presentation geometry')
46
+ output[i][j] = value
47
+ }
48
+ }
49
+ dirty.add(result)
50
+ return result
51
+ }
52
+
53
+ export function generateWaveformPath(
54
+ points: WaveformPoint[],
55
+ type: WaveformLineType,
56
+ x: ScaleLinear<number, number>,
57
+ y: ScaleLinear<number, number>,
58
+ ): string | null {
59
+ if (!points.length || type === 'none') return null
60
+ let buffer = pathBuffers.get(points)
61
+ if (!buffer) pathBuffers.set(points, (buffer = createPathBuffer(x, y)))
62
+ return buffer(points, type, x, y)
63
+ }
64
+ function values(points: WaveformPoint[]): number[][] {
65
+ return points.map((point) => {
66
+ const error = resolveWaveformPointErrors(point)
67
+ return [point.x, point.y, error.lower, error.upper]
68
+ })
69
+ }
70
+ export function captureGeometry(
71
+ path: TrackSeriesPath,
72
+ x: ScaleLinear<number, number>,
73
+ ): NumericGeometry {
74
+ let geometry = snapshots.get(path)
75
+ if (!geometry) {
76
+ let line: number[][] | undefined, points: number[][] | undefined, errors: number[][] | undefined
77
+ // Layout paths are immutable. Static/enter rendering uses their existing SVG and does not
78
+ // need numeric copies; materialize a private endpoint only when an update reads it.
79
+ geometry = {
80
+ get line() {
81
+ return (line ??= values(path.lineRenderPoints ?? []))
82
+ },
83
+ get points() {
84
+ return (points ??= values(path.pointRenderPoints))
85
+ },
86
+ get errors() {
87
+ return (errors ??= values(path.errorBarRenderPoints))
88
+ },
89
+ }
90
+ snapshots.set(path, geometry)
91
+ }
92
+ projections.set(geometry, { path, x, y: path.yScale, result: path })
93
+ return geometry
94
+ }
95
+ function points(values: number[][], buffer: WaveformPoint[] = []): WaveformPoint[] {
96
+ for (let i = 0; i < values.length; i++) {
97
+ // Initialize all numeric fields together; overwrite before publishing the private buffer.
98
+ const point = buffer[i] ?? (buffer[i] = { x: NaN, y: NaN, lowerError: NaN, upperError: NaN })
99
+ const row = values[i]
100
+ point.x = row[0]
101
+ point.y = row[1]
102
+ point.lowerError = row[2]
103
+ point.upperError = row[3]
104
+ }
105
+ buffer.length = values.length
106
+ return buffer
107
+ }
108
+ export function projectGeometry(
109
+ path: TrackSeriesPath,
110
+ geometry: NumericGeometry,
111
+ x: ScaleLinear<number, number>,
112
+ y: ScaleLinear<number, number>,
113
+ ): TrackSeriesPath {
114
+ const cached = projections.get(geometry)
115
+ if (!dirty.has(geometry) && cached?.path === path && cached.x === x && cached.y === y)
116
+ return cached.result
117
+ const buffer = cached?.path === path && cached.result !== path ? cached.result : undefined
118
+ const linePoints = points(geometry.line as number[][], buffer?.lineRenderPoints)
119
+ const result = {
120
+ ...path,
121
+ lineRenderPoints: linePoints,
122
+ path: generateWaveformPath(linePoints, path.series.lineType, x, y),
123
+ pointRenderPoints: points(geometry.points as number[][], buffer?.pointRenderPoints),
124
+ errorBarRenderPoints: points(geometry.errors as number[][], buffer?.errorBarRenderPoints),
125
+ yScale: y,
126
+ }
127
+ projections.set(geometry, { path, x, y, result })
128
+ dirty.delete(geometry)
129
+ return result
130
+ }
131
+ export function compatibleGeometry(
132
+ a: TrackSeriesPath,
133
+ b: TrackSeriesPath,
134
+ from: NumericGeometry,
135
+ to: NumericGeometry,
136
+ ): boolean {
137
+ if (
138
+ a.series.lineType !== b.series.lineType ||
139
+ a.series.pointType !== b.series.pointType ||
140
+ a.series.errorBar.visible !== b.series.errorBar.visible
141
+ )
142
+ return false
143
+ return ['line', 'points', 'errors'].every((key) => {
144
+ const old = from[key] as number[][],
145
+ next = to[key] as number[][]
146
+ return old.length === next.length && old.every((point, i) => point[0] === next[i][0])
147
+ })
148
+ }
@@ -0,0 +1,114 @@
1
+ import { shallowRef, type ComputedRef } from 'vue'
2
+ import type { AnimationContext } from './context'
3
+ import type { ResolvedAnimationProps } from './config'
4
+ import type { WaveformAnimationController } from '@/animation/WaveformAnimationController'
5
+ import { clampProgress } from '@/animation/presets/shared'
6
+ import { chartExportOptions } from '../controls/chartExportOptions'
7
+
8
+ /** A single owner-scoped fade; it holds no old page geometry. */
9
+ export function createPaginationPresentation(
10
+ context: AnimationContext,
11
+ controller: WaveformAnimationController,
12
+ config: ComputedRef<ResolvedAnimationProps>,
13
+ effective: ComputedRef<boolean>,
14
+ commit: () => void,
15
+ ) {
16
+ const opacity = shallowRef(1)
17
+ const key = { kind: 'custom', id: 'pagination', scope: 'update' } as const
18
+ let sequence = 0
19
+ let request: { page: number; version: number; environment: string; started: boolean } | undefined
20
+ const environment = () =>
21
+ JSON.stringify({
22
+ options: Object.fromEntries(
23
+ Object.entries(chartExportOptions(context.props)).filter(
24
+ ([name]) => !['annotations', 'annotationsVisible'].includes(name),
25
+ ),
26
+ ),
27
+ size: context.containerSize.value,
28
+ hidden: context.props.hiddenSeriesIds,
29
+ presentation: context.props.presentationMode,
30
+ })
31
+ function reset() {
32
+ request = undefined
33
+ opacity.value = 1
34
+ }
35
+ function cancel() {
36
+ controller.cancel(key)
37
+ reset()
38
+ }
39
+ function requestPage(page: number) {
40
+ cancel()
41
+ controller.finishAll()
42
+ if (!effective.value || !config.value.animationUpdate.enabled) return
43
+ request = { page, version: ++sequence, environment: environment(), started: false }
44
+ if (config.value.animationUpdate.duration > 0) opacity.value = 0
45
+ }
46
+ function reconcile(dataChanged: boolean) {
47
+ const current = request
48
+ if (!current) return false
49
+ if (
50
+ dataChanged ||
51
+ current.page !== context.currentPage.value ||
52
+ current.environment !== environment() ||
53
+ !effective.value ||
54
+ !config.value.animationUpdate.enabled
55
+ ) {
56
+ controller.finishAll()
57
+ reset()
58
+ return true
59
+ }
60
+ if (!current.started && config.value.animationUpdate.duration === 0) opacity.value = 1
61
+ commit()
62
+ if (context.sampling.state.value === 'pending' || !context.layoutReady.value) return true
63
+ if (context.sampling.state.value !== 'success') {
64
+ controller.finishAll()
65
+ reset()
66
+ return true
67
+ }
68
+ if (current.started) return true
69
+ const ids = context.tracks.value.flatMap((track) =>
70
+ track.width > 0 && track.height > 0
71
+ ? track.seriesList
72
+ .filter((series) => (series.source?.length ?? series.points.length) > 0)
73
+ .map((series) => series.id)
74
+ : [],
75
+ )
76
+ if (!ids.length) {
77
+ reset()
78
+ return true
79
+ }
80
+ current.started = true
81
+ const options = config.value.animationUpdate
82
+ const notify = (callback?: () => void) => ids.forEach(() => callback?.())
83
+ controller.animate(key, {
84
+ duration: options.duration,
85
+ easing: options.easing,
86
+ force: true,
87
+ initialize: () => {
88
+ opacity.value = options.duration > 0 ? 0 : 1
89
+ },
90
+ during: (progress, frame) => {
91
+ if (request?.version === current.version)
92
+ opacity.value = frame.terminal ? 1 : clampProgress(progress)
93
+ },
94
+ done: () => {
95
+ if (request?.version === current.version) reset()
96
+ notify(options.onComplete)
97
+ },
98
+ aborted: () => {
99
+ if (request?.version === current.version) reset()
100
+ notify(options.onAbort)
101
+ },
102
+ })
103
+ return true
104
+ }
105
+ function wrapPagination(goToPage: (page: number) => void, count: ComputedRef<number>) {
106
+ return (page: number) => {
107
+ const target = Math.min(count.value, Math.max(1, Math.floor(page)))
108
+ if (target === context.currentPage.value) return
109
+ requestPage(target)
110
+ goToPage(page)
111
+ }
112
+ }
113
+ return { opacity, reset, cancel, reconcile, wrapPagination, active: () => !!request }
114
+ }
@@ -0,0 +1,61 @@
1
+ import { line, curveLinear, curveStep, curveStepBefore, curveStepAfter } from 'd3'
2
+ import type { ScaleLinear } from 'd3'
3
+ import type { WaveformPoint, WaveformLineType } from '@/types'
4
+
5
+ let decoder: TextDecoder | undefined
6
+
7
+ /** Stable D3 accessors/context keep their feedback across frames; storage is private to points. */
8
+ export function createPathBuffer(x: ScaleLinear<number, number>, y: ScaleLinear<number, number>) {
9
+ let xScale = x,
10
+ yScale = y,
11
+ type: WaveformLineType | undefined
12
+ let bytes = new Uint8Array(1024),
13
+ length = 0
14
+ const append = (text: string) => {
15
+ if (length + text.length > bytes.length) {
16
+ const next = new Uint8Array(Math.max(bytes.length * 2, length + text.length))
17
+ next.set(bytes)
18
+ bytes = next
19
+ }
20
+ for (let i = 0; i < text.length; i++) bytes[length++] = text.charCodeAt(i)
21
+ }
22
+ const coordinate = (value: number) => append(String(Math.round(value * 1000) / 1000))
23
+ const point = (command: string, x: number, y: number) => {
24
+ append(command)
25
+ coordinate(x)
26
+ append(',')
27
+ coordinate(y)
28
+ }
29
+ const generator = line<WaveformPoint>()
30
+ .x((point) => xScale(point.x))
31
+ .y((point) => yScale(point.y))
32
+ .context({
33
+ moveTo: (x: number, y: number) => point('M', x, y),
34
+ lineTo: (x: number, y: number) => point('L', x, y),
35
+ closePath: () => append('Z'),
36
+ } as unknown as CanvasRenderingContext2D)
37
+ return (
38
+ points: WaveformPoint[],
39
+ nextType: WaveformLineType,
40
+ x: ScaleLinear<number, number>,
41
+ y: ScaleLinear<number, number>,
42
+ ): string => {
43
+ xScale = x
44
+ yScale = y
45
+ if (type !== nextType) {
46
+ generator.curve(
47
+ nextType === 'step-start'
48
+ ? curveStepBefore
49
+ : nextType === 'step-middle'
50
+ ? curveStep
51
+ : nextType === 'step-end' || nextType === 'step-after'
52
+ ? curveStepAfter
53
+ : curveLinear,
54
+ )
55
+ type = nextType
56
+ }
57
+ length = 0
58
+ generator(points)
59
+ return (decoder ??= new TextDecoder()).decode(bytes.subarray(0, length))
60
+ }
61
+ }
@@ -0,0 +1,172 @@
1
+ import type { TrackLayout, TrackSeriesPath } from '../core/types'
2
+ import type { WaveformAnnotation } from '@/types'
3
+ import type { NumericGeometry } from '@/animation/types'
4
+ import {
5
+ captureGeometry,
6
+ compatibleGeometry,
7
+ projectGeometry,
8
+ interpolateGeometry,
9
+ } from './geometry'
10
+ import { interpolateValue } from '@/animation/core/values'
11
+ import { clampProgress, validDomain } from '@/animation/presets/shared'
12
+ import type { ScaleLinear } from 'd3'
13
+
14
+ function presentationScale(scale: ScaleLinear<number, number>, domain: [number, number]) {
15
+ const current = scale.domain()
16
+ return current[0] === domain[0] && current[1] === domain[1] ? scale : scale.copy().domain(domain)
17
+ }
18
+
19
+ interface AnchorValue {
20
+ seriesId: string
21
+ annotationY: number
22
+ x: number
23
+ y: number
24
+ }
25
+ interface SeriesGeometry {
26
+ path: TrackSeriesPath
27
+ values: NumericGeometry
28
+ old?: { path: TrackSeriesPath; values: NumericGeometry }
29
+ opacity: number
30
+ }
31
+ export interface TrackPresentation {
32
+ target: TrackLayout
33
+ series: Map<string, SeriesGeometry>
34
+ xDomain: [number, number]
35
+ yDomains: [number, number][]
36
+ anchors: Map<string, AnchorValue>
37
+ clips: Map<string, { id: string; x: number; width: number }>
38
+ }
39
+ export function staticTrack(
40
+ track: TrackLayout,
41
+ annotations: WaveformAnnotation[],
42
+ previous?: TrackPresentation,
43
+ refreshAnchors = false,
44
+ ): TrackPresentation {
45
+ const anchors = new Map<string, AnchorValue>()
46
+ for (const annotation of annotations) {
47
+ const series = track.seriesList.find((series) => series.id === annotation.seriesId)
48
+ if (!series || (series.source?.length ?? series.points.length) === 0) continue
49
+ const cached = previous?.anchors.get(annotation.id)
50
+ const point = refreshAnchors ? series.source?.nearestPoint(annotation.x) : undefined
51
+ if (refreshAnchors && !point) continue
52
+ anchors.set(annotation.id, {
53
+ seriesId: series.id,
54
+ annotationY: annotation.y,
55
+ x: annotation.x,
56
+ y:
57
+ point?.y ??
58
+ (cached?.x === annotation.x && cached.annotationY === annotation.y
59
+ ? cached.y
60
+ : annotation.y),
61
+ })
62
+ }
63
+ return {
64
+ target: track,
65
+ series: new Map(
66
+ track.seriesPaths.map((path) => [
67
+ path.series.id,
68
+ { path, values: captureGeometry(path, track.xScale), opacity: 1 },
69
+ ]),
70
+ ),
71
+ xDomain: track.xScale.domain() as [number, number],
72
+ yDomains: track.yAxes.map((axis) => axis.scale.domain() as [number, number]),
73
+ anchors,
74
+ clips: new Map(),
75
+ }
76
+ }
77
+ function domain(from: [number, number], to: [number, number], p: number): [number, number] {
78
+ if (!validDomain(to)) return from
79
+ const value = interpolateValue(from, to, clampProgress(p)) as [number, number]
80
+ return validDomain(value) ? value : to
81
+ }
82
+ export function hasThirdGeometry(from: TrackPresentation): boolean {
83
+ return [...from.series.values()].some((series) => !!series.old)
84
+ }
85
+ export function transitionTrack(
86
+ from: TrackPresentation,
87
+ to: TrackPresentation,
88
+ p: number,
89
+ terminal: boolean,
90
+ ): TrackPresentation {
91
+ if (terminal) return to
92
+ const series = new Map<string, SeriesGeometry>()
93
+ for (const [id, next] of to.series) {
94
+ const previous = from.series.get(id)
95
+ if (!previous) {
96
+ series.set(id, next)
97
+ continue
98
+ }
99
+ const compatible = compatibleGeometry(previous.path, next.path, previous.values, next.values)
100
+ series.set(
101
+ id,
102
+ compatible
103
+ ? {
104
+ ...next,
105
+ values: interpolateGeometry(previous.values, next.values, p),
106
+ }
107
+ : {
108
+ ...next,
109
+ old: { path: previous.path, values: previous.values },
110
+ opacity: clampProgress(p),
111
+ },
112
+ )
113
+ }
114
+ const anchors = new Map<string, AnchorValue>()
115
+ for (const [id, next] of to.anchors) {
116
+ const previous = from.anchors.get(id)
117
+ anchors.set(
118
+ id,
119
+ previous && !series.get(next.seriesId)?.old
120
+ ? {
121
+ seriesId: next.seriesId,
122
+ annotationY: next.annotationY,
123
+ x: (1 - p) * previous.x + p * next.x,
124
+ y: (1 - p) * previous.y + p * next.y,
125
+ }
126
+ : next,
127
+ )
128
+ }
129
+ return {
130
+ ...to,
131
+ series,
132
+ anchors,
133
+ xDomain: domain(from.xDomain, to.xDomain, p),
134
+ yDomains: to.yDomains.map((target, i) => domain(from.yDomains[i] ?? target, target, p)),
135
+ clips: to.clips,
136
+ }
137
+ }
138
+ export function projectTrack(state: TrackPresentation): TrackLayout {
139
+ const target = state.target
140
+ const xScale = presentationScale(target.xScale, state.xDomain)
141
+ const projectedAxes = target.yAxes.map((axis, index) => {
142
+ const scale = presentationScale(axis.scale, state.yDomains[index])
143
+ return scale === axis.scale ? axis : { ...axis, scale }
144
+ })
145
+ const yAxes = projectedAxes.every((axis, index) => axis === target.yAxes[index])
146
+ ? target.yAxes
147
+ : projectedAxes
148
+ const seriesPaths: TrackSeriesPath[] = []
149
+ for (const [id, series] of state.series) {
150
+ const y = yAxes[series.path.yAxisIndex]?.scale ?? target.yScale
151
+ if (series.old)
152
+ seriesPaths.push({
153
+ ...projectGeometry(series.old.path, series.old.values, xScale, y),
154
+ presentationKey: `${id}:old`,
155
+ opacity: 1 - series.opacity,
156
+ })
157
+ seriesPaths.push({
158
+ ...projectGeometry(series.path, series.values, xScale, y),
159
+ presentationKey: id,
160
+ opacity: series.opacity,
161
+ animationClip: state.clips.get(id),
162
+ })
163
+ }
164
+ return {
165
+ ...target,
166
+ xScale,
167
+ yScale: yAxes[0]?.scale ?? target.yScale,
168
+ yAxes,
169
+ seriesPaths,
170
+ path: seriesPaths[0]?.path ?? null,
171
+ }
172
+ }