waveform-analysis 0.1.66 → 0.1.68
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 +124 -3
- package/dist/index.cjs +1 -1
- package/dist/index.js +1059 -939
- package/dist/style.css +1 -1
- package/dist/types/components/WaveformChart.vue.d.ts +4 -0
- package/dist/types/components/core/grid.d.ts +1 -1
- package/dist/types/components/core/layout.d.ts +2 -2
- package/dist/types/components/core/pagination.d.ts +2 -0
- package/dist/types/components/core/title.d.ts +2 -1
- package/dist/types/components/core/trackLayoutBuilder.d.ts +4 -0
- package/dist/types/components/core/useWaveformChartController.d.ts +6 -0
- package/dist/types/components/core/useWaveformLayout.d.ts +1 -0
- package/dist/types/components/core/useWaveformPresentation.d.ts +1 -0
- package/dist/types/components/core/useWaveformYAxisLayout.d.ts +31 -0
- package/dist/types/components/core/waveformChartTypes.d.ts +9 -2
- package/dist/types/components/core/yAxisLayoutMetrics.d.ts +1 -1
- package/dist/types/components/data/types.d.ts +1 -1
- package/dist/types/components/interaction/interactionTarget.d.ts +1 -0
- package/dist/types/components/interaction/remoteViewport.d.ts +8 -0
- package/dist/types/components/interaction/useWaveformViewport.d.ts +1 -2
- package/dist/types/components/interaction/viewportDomain.d.ts +1 -2
- package/dist/types/components/interaction/viewportPan.d.ts +4 -0
- package/dist/types/components/rendering/WaveformLegend.vue.d.ts +2 -0
- package/dist/types/components/rendering/WaveformTrack.vue.d.ts +4 -0
- package/dist/types/components/rendering/WaveformTrackAxes.vue.d.ts +5 -1
- package/dist/types/data/recordedDampedWaveform.d.ts +1 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/types/chart.d.ts +13 -0
- package/dist/types/types/index.d.ts +1 -1
- package/package.json +1 -1
- package/src/components/WaveformChart.css +5 -0
- package/src/components/WaveformChart.vue +2 -0
- package/src/components/WaveformChartView.vue +14 -3
- package/src/components/core/grid.ts +4 -1
- package/src/components/core/layout.ts +16 -2
- package/src/components/core/pagination.ts +17 -0
- package/src/components/core/title.ts +7 -3
- package/src/components/core/trackLayoutBuilder.ts +14 -4
- package/src/components/core/useWaveformChartController.ts +17 -1
- package/src/components/core/useWaveformChartLifecycle.ts +10 -16
- package/src/components/core/useWaveformLayout.ts +35 -91
- package/src/components/core/useWaveformPresentation.ts +17 -3
- package/src/components/core/useWaveformYAxisLayout.ts +140 -0
- package/src/components/core/waveformChartTypes.ts +12 -0
- package/src/components/core/yAxisLayoutMetrics.ts +11 -2
- package/src/components/data/types.ts +3 -0
- package/src/components/interaction/interactionTarget.ts +10 -0
- package/src/components/interaction/remoteViewport.ts +53 -0
- package/src/components/interaction/useWaveformViewport.ts +17 -55
- package/src/components/interaction/useWaveformZoom.ts +7 -7
- package/src/components/interaction/viewportDomain.ts +10 -14
- package/src/components/interaction/viewportPan.ts +82 -0
- package/src/components/rendering/WaveformLegend.vue +10 -1
- package/src/components/rendering/WaveformTrack.vue +13 -2
- package/src/components/rendering/WaveformTrackAxes.vue +15 -4
- package/src/data/recordedDampedWaveform.ts +3 -0
- package/src/data/simulatedWaveforms.ts +12 -10
- package/src/index.ts +3 -0
- package/src/types/chart.ts +16 -0
- package/src/types/index.ts +3 -0
|
@@ -11,6 +11,10 @@ export interface WaveformPoint {
|
|
|
11
11
|
/** Error above Y; overrides `error` for the upper side. */
|
|
12
12
|
upperError?: number;
|
|
13
13
|
}
|
|
14
|
+
/** Optional compact title and time-label placement at the card edges. */
|
|
15
|
+
export type WaveformLayoutPreset = 'default' | 'edge-compact';
|
|
16
|
+
/** Controls unit placement without changing series metadata. */
|
|
17
|
+
export type WaveformUnitDisplayMode = 'axis' | 'legend-single-series';
|
|
14
18
|
/**
|
|
15
19
|
* 显示模式
|
|
16
20
|
* - independent: 每个波形独立 Y 轴和缩放
|
|
@@ -44,6 +48,13 @@ export interface WaveformZoomEndPayload {
|
|
|
44
48
|
seriesIds?: string[];
|
|
45
49
|
gesture?: 'wheel' | 'box';
|
|
46
50
|
}
|
|
51
|
+
/** Final horizontal viewport after a pan; endpoints are always seconds. */
|
|
52
|
+
export interface WaveformPanEndPayload {
|
|
53
|
+
start: number;
|
|
54
|
+
end: number;
|
|
55
|
+
trackIndex?: number;
|
|
56
|
+
seriesIds?: string[];
|
|
57
|
+
}
|
|
47
58
|
/** Describes the X-axis viewport targeted by a user zoom gesture before rendering settles. */
|
|
48
59
|
export interface WaveformZoomIntentPayload {
|
|
49
60
|
start: number;
|
|
@@ -221,6 +232,8 @@ export interface WaveformAxesOptions {
|
|
|
221
232
|
splitNumber?: number;
|
|
222
233
|
/** Expands Y-axis domains to equal, human-friendly intervals. Defaults to true. */
|
|
223
234
|
nice?: boolean;
|
|
235
|
+
/** Adds this fraction of the automatic Y span above its maximum. Defaults to 0. */
|
|
236
|
+
upperPaddingRatio?: number;
|
|
224
237
|
};
|
|
225
238
|
}
|
|
226
239
|
/** Styling and visibility options for the horizontal zero-value reference line. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 类型定义统一导出
|
|
3
3
|
*/
|
|
4
|
-
export type { WaveformPoint, WaveformDisplayMode, WaveformOverlayMode, WaveformInteractionMode, WaveformXDomainStrategy, WaveformZoomIntentPayload, WaveformZoomEndPayload, 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, WaveformZeroLineOptions, } from './chart';
|
|
5
5
|
export type { SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformTypedValues, TypedSampleData, TypedPointData, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './data';
|
package/package.json
CHANGED
|
@@ -10,6 +10,8 @@ import WaveformChartView from './WaveformChartView.vue'
|
|
|
10
10
|
const props = withDefaults(defineProps<WaveformChartProps>(), {
|
|
11
11
|
displayMode: 'independent',
|
|
12
12
|
overlayMode: 'single-axis',
|
|
13
|
+
unitDisplayMode: 'axis',
|
|
14
|
+
layoutPreset: 'default',
|
|
13
15
|
yLabel: '幅值',
|
|
14
16
|
lineColor: '#0960bd',
|
|
15
17
|
showTooltip: true,
|
|
@@ -13,10 +13,14 @@ const {
|
|
|
13
13
|
displayMode,
|
|
14
14
|
activeInteractionMode,
|
|
15
15
|
isCleanView,
|
|
16
|
+
isEdgeCompact,
|
|
17
|
+
paginationVisible,
|
|
16
18
|
isPresentationMode,
|
|
17
19
|
selection,
|
|
18
20
|
containerStyle,
|
|
19
21
|
overlayMode,
|
|
22
|
+
unitDisplayMode,
|
|
23
|
+
showLegendUnits,
|
|
20
24
|
resolvedChartLeftMargin,
|
|
21
25
|
titleAreaHeight,
|
|
22
26
|
resolvedPlotMargin,
|
|
@@ -77,7 +81,6 @@ const {
|
|
|
77
81
|
xAxisTitleY,
|
|
78
82
|
hasChartArea,
|
|
79
83
|
gridOptions,
|
|
80
|
-
pageCount,
|
|
81
84
|
currentPage,
|
|
82
85
|
getPageSize,
|
|
83
86
|
pageableTracks,
|
|
@@ -116,6 +119,7 @@ function handleChartPointerLeave() {
|
|
|
116
119
|
`waveform-chart--interaction-${activeInteractionMode}`,
|
|
117
120
|
{
|
|
118
121
|
'waveform-chart--clean': isCleanView,
|
|
122
|
+
'waveform-chart--edge-compact': isEdgeCompact,
|
|
119
123
|
'waveform-chart--presentation': isPresentationMode,
|
|
120
124
|
'waveform-chart--panning': selection?.kind === 'pan',
|
|
121
125
|
},
|
|
@@ -233,6 +237,9 @@ function handleChartPointerLeave() {
|
|
|
233
237
|
:frame-number="resolveFrameNumber(track.id)"
|
|
234
238
|
:frame-style="frameStyle"
|
|
235
239
|
:axes="axes"
|
|
240
|
+
:show-axis-units="unitDisplayMode === 'axis'"
|
|
241
|
+
:contain-y-axis-endpoints="isEdgeCompact"
|
|
242
|
+
:contain-frame-stroke="isEdgeCompact"
|
|
236
243
|
:clean-view="isCleanView"
|
|
237
244
|
:zero-line="resolvedZeroLine"
|
|
238
245
|
:time-unit="timeUnit"
|
|
@@ -284,8 +291,12 @@ function handleChartPointerLeave() {
|
|
|
284
291
|
:transform="`translate(${track.left}, ${track.top})`"
|
|
285
292
|
>
|
|
286
293
|
<WaveformLegend
|
|
287
|
-
v-if="
|
|
294
|
+
v-if="
|
|
295
|
+
!track.isEmpty &&
|
|
296
|
+
(track.legendSeries.length > 1 || unitDisplayMode === 'legend-single-series')
|
|
297
|
+
"
|
|
288
298
|
:series="track.legendSeries"
|
|
299
|
+
:show-units="showLegendUnits"
|
|
289
300
|
:position="resolveLegendPosition(track.id)"
|
|
290
301
|
:orientation="resolveLegendOrientation(resolveLegendPosition(track.id))"
|
|
291
302
|
:background-color="legendBackgroundColor"
|
|
@@ -321,7 +332,7 @@ function handleChartPointerLeave() {
|
|
|
321
332
|
</svg>
|
|
322
333
|
|
|
323
334
|
<Pagination
|
|
324
|
-
v-if="
|
|
335
|
+
v-if="paginationVisible"
|
|
325
336
|
class="waveform-chart__pagination"
|
|
326
337
|
aria-label="波形分页"
|
|
327
338
|
:current="currentPage"
|
|
@@ -134,6 +134,7 @@ export function resolveGridCellGeometry(
|
|
|
134
134
|
slotHasSeries: boolean[] = [],
|
|
135
135
|
horizontalGap?: number,
|
|
136
136
|
showXAxis = true,
|
|
137
|
+
bottomAxisBandInMargin = false,
|
|
137
138
|
): GridCellGeometry[] {
|
|
138
139
|
const pageSize = getPageSize(options)
|
|
139
140
|
const seriesCount = slotHasSeries.filter(Boolean).length
|
|
@@ -164,7 +165,9 @@ export function resolveGridCellGeometry(
|
|
|
164
165
|
}
|
|
165
166
|
}
|
|
166
167
|
const totalVerticalGap = Math.max(0, rowCount - 1) * defaultGap
|
|
167
|
-
|
|
168
|
+
// The final row's ticks may use the chart bottom margin; intermediate bands stay in-grid.
|
|
169
|
+
const bottomBandCredit = bottomAxisBandInMargin && axisRows.has(rowCount - 1) ? X_AXIS_BAND : 0
|
|
170
|
+
const totalAxisBand = axisRows.size * X_AXIS_BAND - bottomBandCredit
|
|
168
171
|
const plotHeight = Math.max(1, (innerHeight - totalVerticalGap - totalAxisBand) / rowCount)
|
|
169
172
|
|
|
170
173
|
return Array.from({ length: filledLastRow ? seriesCount : pageSize }, (_, slotIndex) => {
|
|
@@ -161,10 +161,21 @@ export function resolveRenderedYAxisSeriesGroups(
|
|
|
161
161
|
yDomain?: WaveformYDomain,
|
|
162
162
|
yDomains?: Record<string, WaveformYDomain>,
|
|
163
163
|
viewportYDomains?: Record<string, [number, number]>,
|
|
164
|
+
upperPaddingRatio = 0,
|
|
164
165
|
): YAxisSeriesGroup[] {
|
|
165
166
|
const viewportYDomain = viewportYDomains?.[track.id]
|
|
166
167
|
return resolveYAxisSeriesGroups(track, overlayMode, yDomain, yDomains).map((group) =>
|
|
167
|
-
!group.fixed && viewportYDomain
|
|
168
|
+
!group.fixed && viewportYDomain
|
|
169
|
+
? { ...group, domain: viewportYDomain }
|
|
170
|
+
: !group.fixed && Number.isFinite(upperPaddingRatio) && upperPaddingRatio > 0
|
|
171
|
+
? {
|
|
172
|
+
...group,
|
|
173
|
+
domain: [
|
|
174
|
+
group.domain[0],
|
|
175
|
+
group.domain[1] + (group.domain[1] - group.domain[0]) * upperPaddingRatio,
|
|
176
|
+
] as WaveformYDomain,
|
|
177
|
+
}
|
|
178
|
+
: group,
|
|
168
179
|
)
|
|
169
180
|
}
|
|
170
181
|
|
|
@@ -255,6 +266,8 @@ export function buildYAxisSlots(
|
|
|
255
266
|
nice = true,
|
|
256
267
|
includeWithoutLastTick = false,
|
|
257
268
|
viewportYDomains?: Record<string, [number, number]>,
|
|
269
|
+
upperPaddingRatio = 0,
|
|
270
|
+
showUnits = true,
|
|
258
271
|
): { slots: YAxisSlot[]; clearance: { left: number; right: number } } {
|
|
259
272
|
const widths = new Map<string, number>()
|
|
260
273
|
tracks.forEach((track) => {
|
|
@@ -265,6 +278,7 @@ export function buildYAxisSlots(
|
|
|
265
278
|
yDomain,
|
|
266
279
|
yDomains,
|
|
267
280
|
viewportYDomains,
|
|
281
|
+
upperPaddingRatio,
|
|
268
282
|
).forEach((group) => {
|
|
269
283
|
const sideIndex = sideIndexes[group.side]++
|
|
270
284
|
const key = `${group.side}:${sideIndex}`
|
|
@@ -272,7 +286,7 @@ export function buildYAxisSlots(
|
|
|
272
286
|
group.domain,
|
|
273
287
|
nice,
|
|
274
288
|
undefined,
|
|
275
|
-
group.seriesList[0]?.unit,
|
|
289
|
+
showUnits ? group.seriesList[0]?.unit : undefined,
|
|
276
290
|
tickCount,
|
|
277
291
|
includeWithoutLastTick,
|
|
278
292
|
).tickTextWidth
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Conservative Ant pagination sizing, including first/last pages and both ellipses. */
|
|
2
|
+
export function resolveCompactPaginationBand(
|
|
3
|
+
visible: boolean,
|
|
4
|
+
width: number,
|
|
5
|
+
pageCount: number,
|
|
6
|
+
timeLabelCenter: number,
|
|
7
|
+
timeLabel: string,
|
|
8
|
+
): 0 | 16 | 40 {
|
|
9
|
+
if (!visible) return 0
|
|
10
|
+
if (width <= 520) return 40
|
|
11
|
+
const controls = Math.min(pageCount, 9) + 2
|
|
12
|
+
const paginationWidth = controls * 32 + controls * 8
|
|
13
|
+
// A full 18px em per character also accommodates wide glyphs and Chinese labels.
|
|
14
|
+
const labelRight = timeLabelCenter + (Array.from(timeLabel).length * 18) / 2
|
|
15
|
+
const availableWidth = width - 10 - labelRight - 8
|
|
16
|
+
return availableWidth >= paginationWidth ? 16 : 40
|
|
17
|
+
}
|
|
@@ -20,6 +20,7 @@ export interface RotatedTitleLayoutOptions {
|
|
|
20
20
|
naturalHeight: number
|
|
21
21
|
availableWidth: number
|
|
22
22
|
rotation: number
|
|
23
|
+
edgeCompact?: boolean
|
|
23
24
|
}
|
|
24
25
|
|
|
25
26
|
function clampPositive(value: number): number {
|
|
@@ -31,7 +32,10 @@ export function calculateRotatedTitleLayout({
|
|
|
31
32
|
naturalHeight,
|
|
32
33
|
availableWidth,
|
|
33
34
|
rotation,
|
|
35
|
+
edgeCompact = false,
|
|
34
36
|
}: RotatedTitleLayoutOptions): RotatedTitleLayout {
|
|
37
|
+
const verticalPadding = edgeCompact ? 8 : TITLE_AREA_VERTICAL_PADDING
|
|
38
|
+
const minimumHeight = edgeCompact ? 0 : TITLE_AREA_MIN_HEIGHT
|
|
35
39
|
const safeNaturalWidth = clampPositive(naturalWidth)
|
|
36
40
|
const safeNaturalHeight = clampPositive(naturalHeight)
|
|
37
41
|
const safeAvailableWidth = clampPositive(availableWidth)
|
|
@@ -53,13 +57,13 @@ export function calculateRotatedTitleLayout({
|
|
|
53
57
|
textHeight,
|
|
54
58
|
visualWidth: textWidth,
|
|
55
59
|
visualHeight: textHeight,
|
|
56
|
-
areaHeight: Math.max(
|
|
60
|
+
areaHeight: Math.max(minimumHeight, textHeight + verticalPadding),
|
|
57
61
|
scale: 1,
|
|
58
62
|
wrapped: lineCount > 1,
|
|
59
63
|
}
|
|
60
64
|
}
|
|
61
65
|
|
|
62
|
-
const maximumVisualHeight = TITLE_AREA_MAX_HEIGHT -
|
|
66
|
+
const maximumVisualHeight = TITLE_AREA_MAX_HEIGHT - verticalPadding
|
|
63
67
|
const naturalVisualWidth = safeNaturalWidth * absoluteCosine + safeNaturalHeight * absoluteSine
|
|
64
68
|
const naturalVisualHeight = safeNaturalWidth * absoluteSine + safeNaturalHeight * absoluteCosine
|
|
65
69
|
const scale = Math.min(
|
|
@@ -71,7 +75,7 @@ export function calculateRotatedTitleLayout({
|
|
|
71
75
|
const visualHeight = naturalVisualHeight * scale
|
|
72
76
|
const areaHeight = Math.min(
|
|
73
77
|
TITLE_AREA_MAX_HEIGHT,
|
|
74
|
-
Math.max(
|
|
78
|
+
Math.max(minimumHeight, visualHeight + verticalPadding),
|
|
75
79
|
)
|
|
76
80
|
|
|
77
81
|
return {
|
|
@@ -71,6 +71,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
71
71
|
xAxisLabelFormatter?: WaveformXAxisLabelFormatter
|
|
72
72
|
yAxisSplitNumber?: number
|
|
73
73
|
yAxisNice?: boolean
|
|
74
|
+
yAxisUpperPaddingRatio?: number
|
|
74
75
|
rendering: ResolvedWaveformRenderingOptions
|
|
75
76
|
/** Latest sampling result for SVG lines only; source series remain complete for interaction. */
|
|
76
77
|
linePointOverrides?: Readonly<Record<string, WaveformPoint[]>>
|
|
@@ -79,6 +80,9 @@ export interface BuildTrackLayoutsOptions {
|
|
|
79
80
|
yLabel?: string
|
|
80
81
|
yAxisSlots?: readonly YAxisSlot[]
|
|
81
82
|
showCompactEmptyTracks: boolean
|
|
83
|
+
useNonEmptyBottomTracks?: boolean
|
|
84
|
+
compactYAxisLayout?: boolean
|
|
85
|
+
showAxisUnits?: boolean
|
|
82
86
|
}
|
|
83
87
|
|
|
84
88
|
function resolveIndependentXDomain(
|
|
@@ -100,7 +104,12 @@ function resolveIndependentXDomain(
|
|
|
100
104
|
}
|
|
101
105
|
|
|
102
106
|
export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayout[] {
|
|
103
|
-
const visibleCells = options.cells.map((cell) => ({
|
|
107
|
+
const visibleCells = options.cells.map((cell) => ({
|
|
108
|
+
...cell,
|
|
109
|
+
hasSeries: options.useNonEmptyBottomTracks
|
|
110
|
+
? Boolean(cell.series?.series.length)
|
|
111
|
+
: Boolean(cell.series),
|
|
112
|
+
}))
|
|
104
113
|
const bottomCells = getBottomRowCellIndexes(visibleCells, options.grid.columnCount)
|
|
105
114
|
|
|
106
115
|
const layouts: TrackLayout[] = visibleCells.flatMap((cell, index) => {
|
|
@@ -135,6 +144,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
135
144
|
options.fixedYDomain,
|
|
136
145
|
options.fixedYDomains,
|
|
137
146
|
options.yDomains,
|
|
147
|
+
options.yAxisUpperPaddingRatio,
|
|
138
148
|
)
|
|
139
149
|
const sideIndexes = { left: 0, right: 0 }
|
|
140
150
|
const sideOffsets = { left: 0, right: 0 }
|
|
@@ -151,7 +161,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
151
161
|
scale.domain() as [number, number],
|
|
152
162
|
false,
|
|
153
163
|
tickValues,
|
|
154
|
-
group.seriesList[0]?.unit,
|
|
164
|
+
options.showAxisUnits !== false ? group.seriesList[0]?.unit : undefined,
|
|
155
165
|
tickCount,
|
|
156
166
|
)
|
|
157
167
|
const clearance =
|
|
@@ -174,7 +184,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
174
184
|
(slot
|
|
175
185
|
? (group.side === 'left' ? 0 : cell.width) + slot.labelOffset
|
|
176
186
|
: x + (group.side === 'left' ? -labelDistance : labelDistance)) -
|
|
177
|
-
(group.side === 'right' ? Y_AXIS_RIGHT_LABEL_OFFSET : 0)
|
|
187
|
+
(group.side === 'right' && !options.compactYAxisLayout ? Y_AXIS_RIGHT_LABEL_OFFSET : 0)
|
|
178
188
|
if (!slot) sideOffsets[group.side] += clearance
|
|
179
189
|
return {
|
|
180
190
|
index: group.index,
|
|
@@ -288,6 +298,6 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
288
298
|
}
|
|
289
299
|
})
|
|
290
300
|
|
|
291
|
-
alignLeftYAxisTitles(layouts, options.yLabel)
|
|
301
|
+
if (!options.compactYAxisLayout) alignLeftYAxisTitles(layouts, options.yLabel)
|
|
292
302
|
return layouts
|
|
293
303
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { zoomIdentity, type ZoomTransform } from 'd3'
|
|
2
2
|
import {
|
|
3
|
+
computed,
|
|
3
4
|
reactive,
|
|
4
5
|
markRaw,
|
|
5
6
|
ref,
|
|
@@ -21,6 +22,7 @@ import { useWaveformZoom } from '../interaction/useWaveformZoom'
|
|
|
21
22
|
import { useAnimationFrameThrottle } from '../utils/useAnimationFrameThrottle'
|
|
22
23
|
import { margin } from './constants'
|
|
23
24
|
import { getPageSize } from './grid'
|
|
25
|
+
import { resolveCompactPaginationBand } from './pagination'
|
|
24
26
|
import type { WaveformHoverState } from './types'
|
|
25
27
|
import { useWaveformChartLifecycle } from './useWaveformChartLifecycle'
|
|
26
28
|
import { usePreparedWaveformSeries } from './useWaveformData'
|
|
@@ -165,9 +167,22 @@ export function useWaveformChartController(
|
|
|
165
167
|
linePointOverrides,
|
|
166
168
|
})
|
|
167
169
|
|
|
170
|
+
const paginationVisible = computed(
|
|
171
|
+
() => gridOptions.value.showPagination && pageCount.value > 1 && !isCleanView.value,
|
|
172
|
+
)
|
|
168
173
|
watchEffect(() => {
|
|
169
174
|
paginationBandHeight.value =
|
|
170
|
-
|
|
175
|
+
props.layoutPreset === 'edge-compact'
|
|
176
|
+
? resolveCompactPaginationBand(
|
|
177
|
+
paginationVisible.value,
|
|
178
|
+
chartWidth.value,
|
|
179
|
+
pageCount.value,
|
|
180
|
+
resolvedChartLeftMargin.value + innerWidth.value / 2,
|
|
181
|
+
layout.resolvedXLabel.value,
|
|
182
|
+
)
|
|
183
|
+
: gridOptions.value.showPagination && pageCount.value > 1 && chartWidth.value <= 520
|
|
184
|
+
? 40
|
|
185
|
+
: 0
|
|
171
186
|
})
|
|
172
187
|
|
|
173
188
|
const zoom = useWaveformZoom({
|
|
@@ -306,6 +321,7 @@ export function useWaveformChartController(
|
|
|
306
321
|
return reactive({
|
|
307
322
|
...toRefs(props),
|
|
308
323
|
...presentation,
|
|
324
|
+
paginationVisible,
|
|
309
325
|
...layout,
|
|
310
326
|
...annotations,
|
|
311
327
|
...viewport,
|
|
@@ -13,7 +13,9 @@ import type { AnnotationSeriesCandidate, useWaveformAnnotationInteraction } from
|
|
|
13
13
|
import type { NormalizedWaveformGridOptions } from './grid'
|
|
14
14
|
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
|
15
15
|
import type { ResolvedWaveformChartProps, WaveformChartEmit } from './waveformChartTypes'
|
|
16
|
-
import {
|
|
16
|
+
import { transformForDomain } from '../interaction/zoomConstraints'
|
|
17
|
+
import { remotePanBoundary, restoreViewportDomain } from '../interaction/remoteViewport'
|
|
18
|
+
import { isEditableTarget } from '../interaction/interactionTarget'
|
|
17
19
|
import { seriesIdentity } from '../interaction/zoomEventPayload'
|
|
18
20
|
|
|
19
21
|
interface LifecycleContext {
|
|
@@ -61,17 +63,6 @@ interface LifecycleContext {
|
|
|
61
63
|
clearZoomBindings: () => void
|
|
62
64
|
}
|
|
63
65
|
|
|
64
|
-
function isEditableTarget(target: EventTarget | null): boolean {
|
|
65
|
-
return (
|
|
66
|
-
target instanceof Element &&
|
|
67
|
-
Boolean(
|
|
68
|
-
target.closest(
|
|
69
|
-
'button, input, select, textarea, [contenteditable]:not([contenteditable="false"])',
|
|
70
|
-
),
|
|
71
|
-
)
|
|
72
|
-
)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
66
|
export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
76
67
|
const {
|
|
77
68
|
props,
|
|
@@ -161,7 +152,8 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
161
152
|
trackLayouts.value.flatMap((track) => {
|
|
162
153
|
const current = track.xScale.domain() as [number, number]
|
|
163
154
|
const boundary = resolveInitialTrackDomain(track)
|
|
164
|
-
return
|
|
155
|
+
return remotePanBoundary(props) ||
|
|
156
|
+
current[1] - current[0] < boundary[1] - boundary[0] - 1e-12
|
|
165
157
|
? [[seriesIdentity(track.seriesList.map((series) => series.id)), current]]
|
|
166
158
|
: []
|
|
167
159
|
}),
|
|
@@ -172,7 +164,9 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
172
164
|
const current = sharedZoomDomain.value
|
|
173
165
|
const boundary = initialXDomain.value
|
|
174
166
|
pendingSharedXDomain =
|
|
175
|
-
current[1] - current[0] < boundary[1] - boundary[0] - 1e-12
|
|
167
|
+
remotePanBoundary(props) || current[1] - current[0] < boundary[1] - boundary[0] - 1e-12
|
|
168
|
+
? [...current]
|
|
169
|
+
: undefined
|
|
176
170
|
}
|
|
177
171
|
|
|
178
172
|
function handleDataReferenceChange() {
|
|
@@ -193,7 +187,7 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
193
187
|
)
|
|
194
188
|
if (!previousDomain) return
|
|
195
189
|
const boundary = resolveInitialTrackDomain(track)
|
|
196
|
-
const domain =
|
|
190
|
+
const domain = restoreViewportDomain(previousDomain, boundary, [track.seriesList], props)
|
|
197
191
|
nextTransforms[track.index] = transformForDomain(domain, boundary, track.width)
|
|
198
192
|
})
|
|
199
193
|
independentTransforms.value = nextTransforms
|
|
@@ -203,7 +197,7 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
203
197
|
const groups = trackLayouts.value
|
|
204
198
|
.filter((track) => track.hasVisibleSeries)
|
|
205
199
|
.map((track) => track.seriesList)
|
|
206
|
-
const domain =
|
|
200
|
+
const domain = restoreViewportDomain(pendingSharedXDomain, boundary, groups, props)
|
|
207
201
|
sharedTransform.value = transformForDomain(domain, boundary, innerWidth.value)
|
|
208
202
|
pendingSharedXDomain = undefined
|
|
209
203
|
}
|
|
@@ -7,22 +7,17 @@ import {
|
|
|
7
7
|
type AnnotationSeriesInfo,
|
|
8
8
|
type AnnotationTrackLayout,
|
|
9
9
|
} from '../annotation'
|
|
10
|
-
import { channelColors
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
getPageCount,
|
|
14
|
-
normalizeGridOptions,
|
|
15
|
-
paginateSeries,
|
|
16
|
-
resolveGridCellGeometry,
|
|
17
|
-
} from './grid'
|
|
18
|
-
import { buildTrackLayouts, buildYAxisSlots, resolveYAxisTickCount } from './layout'
|
|
10
|
+
import { channelColors } from './constants'
|
|
11
|
+
import { getPageCount, normalizeGridOptions, paginateSeries, resolveGridCellGeometry } from './grid'
|
|
12
|
+
import { buildTrackLayouts, resolveYAxisTickCount } from './layout'
|
|
19
13
|
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
|
20
14
|
import { createFrameNumberResolver, resolvePageableTracks } from './trackPagination'
|
|
21
15
|
import type { PreparedWaveformSeries } from './useWaveformData'
|
|
22
16
|
import type { ResolvedWaveformChartProps } from './waveformChartTypes'
|
|
23
17
|
import { applyXDomainStrategy } from './xDomain'
|
|
24
18
|
import { alignIntegerZoomDomain, normalizeIntegerZoomDomain } from '../interaction/integerZoom'
|
|
25
|
-
import {
|
|
19
|
+
import { resolveViewportYDomains } from './yAxisLayoutMetrics'
|
|
20
|
+
import { useWaveformYAxisLayout } from './useWaveformYAxisLayout'
|
|
26
21
|
import type { useWaveformAnnotationInteraction } from '../annotation'
|
|
27
22
|
interface LayoutContext {
|
|
28
23
|
props: ResolvedWaveformChartProps
|
|
@@ -95,6 +90,11 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
95
90
|
}
|
|
96
91
|
})
|
|
97
92
|
})
|
|
93
|
+
const showLegendUnits = computed(
|
|
94
|
+
() =>
|
|
95
|
+
props.unitDisplayMode === 'legend-single-series' &&
|
|
96
|
+
!chartTracks.value.some((track) => track.series.length > 1),
|
|
97
|
+
)
|
|
98
98
|
const renderingOptions = computed(() => resolveWaveformRenderingOptions(props.rendering))
|
|
99
99
|
const pageableTracks = computed(() =>
|
|
100
100
|
resolvePageableTracks(chartTracks.value, gridOptions.value.hideEmptyTracks),
|
|
@@ -120,86 +120,21 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
120
120
|
independentYDomains.value,
|
|
121
121
|
),
|
|
122
122
|
)
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
track.visibleSeries.length === 1 &&
|
|
139
|
-
Boolean(track.visibleSeries[0]?.name.trim() || props.yLabel),
|
|
140
|
-
),
|
|
141
|
-
)
|
|
142
|
-
const hasVisibleWaveformData = computed(() =>
|
|
143
|
-
chartTracks.value.some((track) => track.visibleSeries.length > 0),
|
|
144
|
-
)
|
|
145
|
-
const chartLeftMargin = computed(() =>
|
|
146
|
-
Math.max(
|
|
147
|
-
margin.left,
|
|
148
|
-
hasYAxisLabels.value
|
|
149
|
-
? yAxisMetrics.value.fullClearance
|
|
150
|
-
: hasVisibleWaveformData.value
|
|
151
|
-
? yAxisMetrics.value.tickClearance
|
|
152
|
-
: 0,
|
|
153
|
-
),
|
|
154
|
-
)
|
|
155
|
-
const yAxisSlots = computed(() =>
|
|
156
|
-
buildYAxisSlots(
|
|
157
|
-
layoutTracks.value.filter((track) => track.visibleSeries.length > 0),
|
|
158
|
-
props.overlayMode,
|
|
159
|
-
props.yDomain,
|
|
160
|
-
props.yDomains,
|
|
161
|
-
yAxisTickCount.value,
|
|
162
|
-
props.axes?.y?.nice !== false,
|
|
163
|
-
props.displayMode === 'compact',
|
|
164
|
-
viewportYDomains.value,
|
|
165
|
-
),
|
|
166
|
-
)
|
|
167
|
-
const resolvedChartLeftMargin = computed(() =>
|
|
168
|
-
props.overlayMode === 'multi-axis'
|
|
169
|
-
? Math.max(chartLeftMargin.value, yAxisSlots.value.clearance.left)
|
|
170
|
-
: chartLeftMargin.value,
|
|
171
|
-
)
|
|
172
|
-
const chartRightMargin = computed(() =>
|
|
173
|
-
props.overlayMode === 'multi-axis'
|
|
174
|
-
? Math.max(margin.right, yAxisSlots.value.clearance.right)
|
|
175
|
-
: margin.right,
|
|
176
|
-
)
|
|
177
|
-
const innerWidth = computed(() =>
|
|
178
|
-
Math.max(0, chartWidth.value - resolvedChartLeftMargin.value - chartRightMargin.value),
|
|
179
|
-
)
|
|
180
|
-
const yAxisLayout = computed(() => {
|
|
181
|
-
const baseGap = getGridGap(props.displayMode)
|
|
182
|
-
const columnCount = gridOptions.value.columnCount
|
|
183
|
-
const hasMultipleColumns = columnCount > 1
|
|
184
|
-
const fullGap = Math.max(baseGap, yAxisMetrics.value.fullClearance)
|
|
185
|
-
const tickGap = Math.max(baseGap, yAxisMetrics.value.tickClearance)
|
|
186
|
-
const plotWidth = (innerWidth.value - fullGap * Math.max(0, columnCount - 1)) / columnCount
|
|
187
|
-
const canReserveLabelClearance = plotWidth >= MINIMUM_PLOT_WIDTH
|
|
188
|
-
return {
|
|
189
|
-
horizontalGap:
|
|
190
|
-
props.overlayMode === 'multi-axis' && hasMultipleColumns && hasVisibleWaveformData.value
|
|
191
|
-
? Math.max(baseGap, yAxisSlots.value.clearance.left + yAxisSlots.value.clearance.right)
|
|
192
|
-
: hasMultipleColumns && hasVisibleWaveformData.value
|
|
193
|
-
? hasYAxisLabels.value && canReserveLabelClearance
|
|
194
|
-
? fullGap
|
|
195
|
-
: tickGap
|
|
196
|
-
: baseGap,
|
|
197
|
-
hideSecondaryLabels:
|
|
198
|
-
props.overlayMode !== 'multi-axis' &&
|
|
199
|
-
hasMultipleColumns &&
|
|
200
|
-
hasYAxisLabels.value &&
|
|
201
|
-
!canReserveLabelClearance,
|
|
202
|
-
}
|
|
123
|
+
const {
|
|
124
|
+
yAxisMetrics,
|
|
125
|
+
yAxisSlots,
|
|
126
|
+
resolvedChartLeftMargin,
|
|
127
|
+
innerWidth,
|
|
128
|
+
yAxisLayout,
|
|
129
|
+
hasVisibleWaveformData,
|
|
130
|
+
} = useWaveformYAxisLayout({
|
|
131
|
+
props,
|
|
132
|
+
chartTracks,
|
|
133
|
+
layoutTracks,
|
|
134
|
+
chartWidth,
|
|
135
|
+
gridOptions,
|
|
136
|
+
viewportYDomains,
|
|
137
|
+
yAxisTickCount,
|
|
203
138
|
})
|
|
204
139
|
const hasWaveformData = computed(() => chartSeries.value.length > 0)
|
|
205
140
|
const hasChartArea = computed(() => innerWidth.value > 0 && innerHeight.value > 0)
|
|
@@ -279,8 +214,12 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
279
214
|
innerHeight.value,
|
|
280
215
|
gridOptions.value,
|
|
281
216
|
props.displayMode,
|
|
282
|
-
layoutTracks.value.map(
|
|
217
|
+
layoutTracks.value.map((track) =>
|
|
218
|
+
props.layoutPreset === 'edge-compact' ? track.series.length > 0 : Boolean(track),
|
|
219
|
+
),
|
|
283
220
|
yAxisLayout.value.horizontalGap,
|
|
221
|
+
true,
|
|
222
|
+
props.layoutPreset === 'edge-compact' && !isCleanView.value,
|
|
284
223
|
)
|
|
285
224
|
return cells.map((cell, index) => ({ ...cell, series: layoutTracks.value[index] }))
|
|
286
225
|
})
|
|
@@ -288,6 +227,9 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
288
227
|
buildTrackLayouts({
|
|
289
228
|
cells: gridCells.value,
|
|
290
229
|
grid: gridOptions.value,
|
|
230
|
+
useNonEmptyBottomTracks: props.layoutPreset === 'edge-compact',
|
|
231
|
+
compactYAxisLayout: props.layoutPreset === 'edge-compact',
|
|
232
|
+
showAxisUnits: props.layoutPreset !== 'edge-compact' || props.unitDisplayMode === 'axis',
|
|
291
233
|
displayMode: props.displayMode,
|
|
292
234
|
overlayMode: props.overlayMode,
|
|
293
235
|
independentTransforms: independentTransforms.value,
|
|
@@ -303,6 +245,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
303
245
|
xAxisLabelFormatter: props.axes?.x?.labelFormatter,
|
|
304
246
|
yAxisSplitNumber: props.axes?.y?.splitNumber,
|
|
305
247
|
yAxisNice: props.axes?.y?.nice,
|
|
248
|
+
yAxisUpperPaddingRatio: props.axes?.y?.upperPaddingRatio,
|
|
306
249
|
rendering: renderingOptions.value,
|
|
307
250
|
linePointOverrides: linePointOverrides?.value,
|
|
308
251
|
hideSecondaryLabels: isCleanView.value || yAxisLayout.value.hideSecondaryLabels,
|
|
@@ -358,6 +301,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
358
301
|
return {
|
|
359
302
|
chartSeries,
|
|
360
303
|
chartTracks,
|
|
304
|
+
showLegendUnits,
|
|
361
305
|
pageableTracks,
|
|
362
306
|
renderingOptions,
|
|
363
307
|
gridOptions,
|