waveform-analysis 0.1.24 → 0.1.26
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 +45 -5
- package/dist/index.cjs +1 -1
- package/dist/index.js +540 -487
- package/dist/types/App.vue.d.ts +4 -0
- package/dist/types/DemoRouterApp.vue.d.ts +3 -0
- package/dist/types/components/core/layout.d.ts +6 -3
- package/dist/types/components/core/trackLayoutBuilder.d.ts +2 -0
- package/dist/types/components/core/useWaveformChartController.d.ts +2 -0
- package/dist/types/components/core/waveformChartTypes.d.ts +2 -0
- package/dist/types/components/core/yDomain.d.ts +7 -0
- package/dist/types/demo/DemoChartHost.vue.d.ts +14 -0
- package/dist/types/demo/DemoControlPanel.vue.d.ts +11 -0
- package/dist/types/demo/DemoGridFrameControls.vue.d.ts +11 -0
- package/dist/types/demo/DemoTitleLegendControls.vue.d.ts +11 -0
- package/dist/types/demo/DemoViewControls.vue.d.ts +11 -0
- package/dist/types/demo/types.d.ts +84 -0
- package/dist/types/router.d.ts +3 -0
- package/dist/types/views/FixedYDomainDemo.vue.d.ts +3 -0
- package/package.json +2 -1
- package/src/DemoRouterApp.vue +14 -0
- package/src/components/core/fixedYDomainLayout.test.ts +116 -0
- package/src/components/core/layout.ts +43 -10
- package/src/components/core/trackLayoutBuilder.ts +17 -7
- package/src/components/core/useWaveformChartController.ts +10 -0
- package/src/components/core/useWaveformLayout.ts +20 -4
- package/src/components/core/waveformChartTypes.ts +2 -0
- package/src/components/core/yDomain.test.ts +56 -0
- package/src/components/core/yDomain.ts +50 -0
- package/src/components/interaction/useWaveformViewport.ts +6 -1
- package/src/components/rendering/WaveformTrack.vue +1 -1
- package/src/components/rendering/WaveformTrackAxes.vue +5 -7
- package/src/components/waveformChartCases/layoutAndDomains.test.ts +25 -0
- package/src/components/waveformChartCases/presentationAndAxes.test.ts +26 -6
- package/src/components/waveformChartCases/viewportBoxAndPan.test.ts +50 -0
- package/src/demoRoutes.css +197 -0
- package/src/main.ts +4 -2
- package/src/router.ts +21 -0
- package/src/styles.css +1 -2
- package/src/views/FixedYDomainDemo.test.ts +44 -0
- package/src/views/FixedYDomainDemo.vue +149 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import 'vue3-colorpicker/style.css';
|
|
2
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
3
|
+
declare const _default: typeof __VLS_export;
|
|
4
|
+
export default _default;
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
|
@@ -1,20 +1,23 @@
|
|
|
1
1
|
import type { WaveformOverlayMode } from '../../types';
|
|
2
2
|
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types';
|
|
3
|
+
import { type WaveformYDomain } from './yDomain';
|
|
3
4
|
export { MAX_MULTI_Y_AXIS_COUNT, Y_AXIS_EXPONENT_GAP } from './constants';
|
|
4
|
-
interface YAxisSeriesGroup {
|
|
5
|
+
export interface YAxisSeriesGroup {
|
|
5
6
|
index: number;
|
|
6
7
|
side: 'left' | 'right';
|
|
7
8
|
seriesList: DisplaySeries[];
|
|
8
9
|
domain: [number, number];
|
|
10
|
+
fixed: boolean;
|
|
9
11
|
}
|
|
10
12
|
export declare function buildYAxisSeriesGroups(track: DisplayTrack, overlayMode: WaveformOverlayMode): YAxisSeriesGroup[];
|
|
11
|
-
export declare function
|
|
13
|
+
export declare function resolveYAxisSeriesGroups(track: DisplayTrack, overlayMode: WaveformOverlayMode, yDomain?: WaveformYDomain, yDomains?: Record<string, WaveformYDomain>): YAxisSeriesGroup[];
|
|
14
|
+
export declare function axisTextMetrics(domain: [number, number], nice?: boolean): {
|
|
12
15
|
exponentLabel: string | null;
|
|
13
16
|
exponentWidth: number;
|
|
14
17
|
tickTextWidth: number;
|
|
15
18
|
};
|
|
16
19
|
export declare function measureYAxisGroupClearance(group: YAxisSeriesGroup): number;
|
|
17
|
-
export declare function measureTrackYAxisClearance(track: DisplayTrack, overlayMode: WaveformOverlayMode): {
|
|
20
|
+
export declare function measureTrackYAxisClearance(track: DisplayTrack, overlayMode: WaveformOverlayMode, yDomain?: WaveformYDomain, yDomains?: Record<string, WaveformYDomain>): {
|
|
18
21
|
left: number;
|
|
19
22
|
right: number;
|
|
20
23
|
};
|
|
@@ -15,6 +15,8 @@ export interface BuildTrackLayoutsOptions {
|
|
|
15
15
|
sharedZoomDomain: [number, number];
|
|
16
16
|
initialXDomain?: [number, number];
|
|
17
17
|
initialXDomains?: Record<string, [number, number]>;
|
|
18
|
+
fixedYDomain?: [number, number];
|
|
19
|
+
fixedYDomains?: Record<string, [number, number]>;
|
|
18
20
|
yDomains?: Record<string, [number, number]>;
|
|
19
21
|
timeUnit: 's' | 'ms';
|
|
20
22
|
rendering: ResolvedWaveformRenderingOptions;
|
|
@@ -338,6 +338,8 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
|
|
|
338
338
|
minZoomSpan?: number | undefined;
|
|
339
339
|
minVisiblePoints: number;
|
|
340
340
|
initialXDomains?: Record<string, [number, number]> | undefined;
|
|
341
|
+
yDomain?: [number, number] | undefined;
|
|
342
|
+
yDomains?: Record<string, [number, number]> | undefined;
|
|
341
343
|
timeUnit: "s" | "ms";
|
|
342
344
|
frameNumber?: string | number | undefined;
|
|
343
345
|
frameStyle?: import("..").WaveformFrameStyle | undefined;
|
|
@@ -16,6 +16,8 @@ export interface WaveformChartProps {
|
|
|
16
16
|
minVisiblePoints?: number;
|
|
17
17
|
initialXDomain?: [number, number];
|
|
18
18
|
initialXDomains?: Record<string, [number, number]>;
|
|
19
|
+
yDomain?: [number, number];
|
|
20
|
+
yDomains?: Record<string, [number, number]>;
|
|
19
21
|
timeUnit?: 's' | 'ms';
|
|
20
22
|
frameNumber?: string | number;
|
|
21
23
|
frameStyle?: WaveformFrameStyle;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DisplaySeries, DisplayTrack } from './types';
|
|
2
|
+
export type WaveformYDomain = [number, number];
|
|
3
|
+
export declare function normalizeYDomain(domain: readonly [number, number] | undefined): WaveformYDomain | undefined;
|
|
4
|
+
export declare function resolveTrackFixedYDomain(track: Pick<DisplayTrack, 'id'>, yDomains?: Record<string, WaveformYDomain>): WaveformYDomain | undefined;
|
|
5
|
+
export declare function resolveSeriesFixedYDomain(series: Pick<DisplaySeries, 'id'>, yDomain?: WaveformYDomain, yDomains?: Record<string, WaveformYDomain>): WaveformYDomain | undefined;
|
|
6
|
+
export declare function hasFixedYDomainForTrack(track: Pick<DisplayTrack, 'id' | 'visibleSeries'>, yDomain?: WaveformYDomain, yDomains?: Record<string, WaveformYDomain>): boolean;
|
|
7
|
+
export declare function mergeYDomains(domains: readonly WaveformYDomain[]): WaveformYDomain;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { DemoChartModel } from './types';
|
|
2
|
+
declare function resetViewport(): void;
|
|
3
|
+
type __VLS_ModelProps = {
|
|
4
|
+
'model': DemoChartModel;
|
|
5
|
+
};
|
|
6
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {
|
|
7
|
+
resetViewport: typeof resetViewport;
|
|
8
|
+
}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
9
|
+
"update:model": (value: DemoChartModel) => any;
|
|
10
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
11
|
+
"onUpdate:model"?: ((value: DemoChartModel) => any) | undefined;
|
|
12
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
13
|
+
declare const _default: typeof __VLS_export;
|
|
14
|
+
export default _default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DemoControlPanelModel } from './types';
|
|
2
|
+
type __VLS_ModelProps = {
|
|
3
|
+
'model': DemoControlPanelModel;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
"update:model": (value: DemoControlPanelModel) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
8
|
+
"onUpdate:model"?: ((value: DemoControlPanelModel) => any) | undefined;
|
|
9
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DemoControlPanelModel } from './types';
|
|
2
|
+
type __VLS_ModelProps = {
|
|
3
|
+
'model': DemoControlPanelModel;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
"update:model": (value: DemoControlPanelModel) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
8
|
+
"onUpdate:model"?: ((value: DemoControlPanelModel) => any) | undefined;
|
|
9
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DemoControlPanelModel } from './types';
|
|
2
|
+
type __VLS_ModelProps = {
|
|
3
|
+
'model': DemoControlPanelModel;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
"update:model": (value: DemoControlPanelModel) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
8
|
+
"onUpdate:model"?: ((value: DemoControlPanelModel) => any) | undefined;
|
|
9
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { DemoControlPanelModel } from './types';
|
|
2
|
+
type __VLS_ModelProps = {
|
|
3
|
+
'model': DemoControlPanelModel;
|
|
4
|
+
};
|
|
5
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_ModelProps, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {
|
|
6
|
+
"update:model": (value: DemoControlPanelModel) => any;
|
|
7
|
+
}, string, import("vue").PublicProps, Readonly<__VLS_ModelProps> & Readonly<{
|
|
8
|
+
"onUpdate:model"?: ((value: DemoControlPanelModel) => any) | undefined;
|
|
9
|
+
}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
10
|
+
declare const _default: typeof __VLS_export;
|
|
11
|
+
export default _default;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import type { WaveformAnnotation, WaveformAxesOptions, WaveformData, WaveformDisplayMode, WaveformFrameStyle, WaveformGridTrackLines, WaveformInteractionMode, WaveformLegendOrientation, WaveformLegendPosition, WaveformLineStyle, WaveformOverlayMode, WaveformTitleOptions, WaveformZeroLineOptions, WaveformZoomEndPayload } from '../components';
|
|
2
|
+
interface SelectOption<T> {
|
|
3
|
+
label: string;
|
|
4
|
+
value: T;
|
|
5
|
+
}
|
|
6
|
+
export interface DemoControlPanelModel {
|
|
7
|
+
controlsOpen: boolean;
|
|
8
|
+
displayMode: WaveformDisplayMode;
|
|
9
|
+
overlayMode: WaveformOverlayMode;
|
|
10
|
+
showTooltip: boolean;
|
|
11
|
+
cleanView: boolean;
|
|
12
|
+
presentationMode: boolean;
|
|
13
|
+
selectedSeriesId: string;
|
|
14
|
+
selectedLineStyle: WaveformLineStyle;
|
|
15
|
+
lineStyleOptions: Array<SelectOption<WaveformLineStyle>>;
|
|
16
|
+
seriesStyleOptions: Array<SelectOption<string>>;
|
|
17
|
+
zeroLineVisible: boolean;
|
|
18
|
+
zeroLineColor: string;
|
|
19
|
+
zeroLineWidth: number;
|
|
20
|
+
zeroLineDash: string;
|
|
21
|
+
zeroLineDashOptions: Array<SelectOption<string>>;
|
|
22
|
+
rowCount: number;
|
|
23
|
+
columnCount: number;
|
|
24
|
+
horizontalGridVisible: boolean;
|
|
25
|
+
horizontalGridColor: string;
|
|
26
|
+
verticalGridVisible: boolean;
|
|
27
|
+
verticalGridColor: string;
|
|
28
|
+
xAxisLineVisible: boolean;
|
|
29
|
+
yAxisLineVisible: boolean;
|
|
30
|
+
frameBorderColor: string;
|
|
31
|
+
frameBackgroundColor: string;
|
|
32
|
+
frameBorderWidth: number;
|
|
33
|
+
frameBorderStyle: NonNullable<WaveformFrameStyle['borderStyle']>;
|
|
34
|
+
frameBorderStyleOptions: Array<SelectOption<string>>;
|
|
35
|
+
frameWatermarkVisible: boolean;
|
|
36
|
+
titleVisible: boolean;
|
|
37
|
+
titleText: string;
|
|
38
|
+
titleAlign: NonNullable<WaveformTitleOptions['align']>;
|
|
39
|
+
titleAlignOptions: Array<SelectOption<NonNullable<WaveformTitleOptions['align']>>>;
|
|
40
|
+
titleFontFamily: string;
|
|
41
|
+
titleFontFamilyOptions: Array<SelectOption<string>>;
|
|
42
|
+
titleFontSize: number;
|
|
43
|
+
titleBold: boolean;
|
|
44
|
+
titleItalic: boolean;
|
|
45
|
+
titleUnderline: boolean;
|
|
46
|
+
titleRotation: number;
|
|
47
|
+
titleColor: string;
|
|
48
|
+
legendPosition: WaveformLegendPosition;
|
|
49
|
+
legendPositionOptions: Array<SelectOption<WaveformLegendPosition>>;
|
|
50
|
+
legendOrientation: WaveformLegendOrientation;
|
|
51
|
+
legendOrientationOptions: Array<SelectOption<WaveformLegendOrientation>>;
|
|
52
|
+
legendBackgroundColor: string;
|
|
53
|
+
closeControls: () => void;
|
|
54
|
+
resetTitleTextStyle: () => void;
|
|
55
|
+
resetWaveformViewport: () => void;
|
|
56
|
+
}
|
|
57
|
+
export interface DemoChartModel {
|
|
58
|
+
data: WaveformData;
|
|
59
|
+
minZoomSpan?: number;
|
|
60
|
+
initialXDomain?: [number, number];
|
|
61
|
+
displayMode: WaveformDisplayMode;
|
|
62
|
+
overlayMode: WaveformOverlayMode;
|
|
63
|
+
rowCount: number;
|
|
64
|
+
columnCount: number;
|
|
65
|
+
gridTrackLines: WaveformGridTrackLines;
|
|
66
|
+
title: WaveformTitleOptions;
|
|
67
|
+
legendPosition: WaveformLegendPosition;
|
|
68
|
+
legendOrientation: WaveformLegendOrientation;
|
|
69
|
+
legendBackgroundColor: string;
|
|
70
|
+
frameStyle: WaveformFrameStyle;
|
|
71
|
+
axes: WaveformAxesOptions;
|
|
72
|
+
cleanView: boolean;
|
|
73
|
+
presentationMode: boolean;
|
|
74
|
+
showTooltip: boolean;
|
|
75
|
+
zeroLine: WaveformZeroLineOptions;
|
|
76
|
+
frameWatermarkVisible: boolean;
|
|
77
|
+
annotations: WaveformAnnotation[];
|
|
78
|
+
annotationsVisible: boolean;
|
|
79
|
+
interactionMode: WaveformInteractionMode;
|
|
80
|
+
hiddenSeriesIds: string[];
|
|
81
|
+
handleZoomEnd: (payload: WaveformZoomEndPayload) => void | Promise<void>;
|
|
82
|
+
resetWaveformViewport: () => void;
|
|
83
|
+
}
|
|
84
|
+
export {};
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
declare const __VLS_export: import("vue").DefineComponent<{}, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<{}> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, true, {}, any>;
|
|
2
|
+
declare const _default: typeof __VLS_export;
|
|
3
|
+
export default _default;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "waveform-analysis",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.26",
|
|
4
4
|
"main": "./dist/index.cjs",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"types": "./dist/types/index.d.ts",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"ant-design-vue": "3.2.20",
|
|
26
26
|
"d3": "^7.9.0",
|
|
27
|
+
"vue-router": "^4.6.4",
|
|
27
28
|
"vue3-colorpicker": "^2.3.0"
|
|
28
29
|
},
|
|
29
30
|
"peerDependencies": {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="demo-shell">
|
|
3
|
+
<header class="demo-shell__header">
|
|
4
|
+
<RouterLink class="demo-shell__brand" to="/">Waveform Analysis</RouterLink>
|
|
5
|
+
<nav class="demo-shell__nav" aria-label="示例导航">
|
|
6
|
+
<RouterLink to="/">综合示例</RouterLink>
|
|
7
|
+
<RouterLink to="/fixed-y-domain">固定振幅范围</RouterLink>
|
|
8
|
+
</nav>
|
|
9
|
+
</header>
|
|
10
|
+
<div class="demo-shell__content">
|
|
11
|
+
<RouterView />
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</template>
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { zoomIdentity } from 'd3'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { DEFAULT_WAVEFORM_RENDERING_OPTIONS } from '../../core'
|
|
5
|
+
import type { DisplaySeries, DisplayTrack } from './types'
|
|
6
|
+
import { buildTrackLayouts, resolveYAxisSeriesGroups } from './layout'
|
|
7
|
+
|
|
8
|
+
function series(id: string, minimum: number, maximum: number): DisplaySeries {
|
|
9
|
+
return {
|
|
10
|
+
id,
|
|
11
|
+
name: id,
|
|
12
|
+
color: '#1677ff',
|
|
13
|
+
lineType: 'linear',
|
|
14
|
+
lineStyle: 'solid',
|
|
15
|
+
pointType: 'none',
|
|
16
|
+
errorBar: { visible: false, width: 1.5, capWidth: 8 },
|
|
17
|
+
points: [
|
|
18
|
+
{ x: 0, y: minimum },
|
|
19
|
+
{ x: 1, y: maximum },
|
|
20
|
+
],
|
|
21
|
+
xDomain: [0, 1],
|
|
22
|
+
yDomain: [minimum, maximum],
|
|
23
|
+
hasErrorPoints: false,
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function track(seriesList: DisplaySeries[]): DisplayTrack {
|
|
28
|
+
return {
|
|
29
|
+
id: 'track',
|
|
30
|
+
series: seriesList,
|
|
31
|
+
visibleSeries: seriesList,
|
|
32
|
+
xDomain: [0, 1],
|
|
33
|
+
yDomain: [0, 100],
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
describe('fixed Y-domain layout', () => {
|
|
38
|
+
it('uses an exact global fixed domain without applying nice bounds', () => {
|
|
39
|
+
const sourceTrack = track([series('a', 0, 100)])
|
|
40
|
+
const result = buildTrackLayouts({
|
|
41
|
+
cells: [
|
|
42
|
+
{
|
|
43
|
+
slotIndex: 0,
|
|
44
|
+
row: 0,
|
|
45
|
+
column: 0,
|
|
46
|
+
left: 0,
|
|
47
|
+
top: 0,
|
|
48
|
+
width: 120,
|
|
49
|
+
height: 100,
|
|
50
|
+
plotHeight: 100,
|
|
51
|
+
cellHeight: 130,
|
|
52
|
+
xAxisBand: 30,
|
|
53
|
+
series: sourceTrack,
|
|
54
|
+
},
|
|
55
|
+
],
|
|
56
|
+
grid: { rowCount: 1, columnCount: 1, showPagination: false, trackLines: {} },
|
|
57
|
+
displayMode: 'independent',
|
|
58
|
+
overlayMode: 'single-axis',
|
|
59
|
+
independentTransforms: [zoomIdentity],
|
|
60
|
+
sharedZoomDomain: [0, 1],
|
|
61
|
+
fixedYDomain: [3, 97],
|
|
62
|
+
timeUnit: 'ms',
|
|
63
|
+
rendering: DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
64
|
+
hideSecondaryLabels: false,
|
|
65
|
+
yAxisLabelX: -50,
|
|
66
|
+
showCompactEmptyTracks: false,
|
|
67
|
+
})[0]
|
|
68
|
+
|
|
69
|
+
expect(result?.yScale.domain()).toEqual([3, 97])
|
|
70
|
+
expect(result?.yAxes[0]?.tickValues).toContain(3)
|
|
71
|
+
expect(result?.yAxes[0]?.tickValues).toContain(97)
|
|
72
|
+
expect(result?.seriesPaths[0]?.yScale.domain()).toEqual([3, 97])
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('resolves track, series, and global fixed-domain precedence', () => {
|
|
76
|
+
const sourceTrack = track([series('a', 0, 10), series('b', 20, 30)])
|
|
77
|
+
|
|
78
|
+
expect(
|
|
79
|
+
resolveYAxisSeriesGroups(sourceTrack, 'single-axis', [-5, 5], {
|
|
80
|
+
a: [-10, 10],
|
|
81
|
+
track: [300, 100],
|
|
82
|
+
})[0]?.domain,
|
|
83
|
+
).toEqual([100, 300])
|
|
84
|
+
|
|
85
|
+
expect(
|
|
86
|
+
resolveYAxisSeriesGroups(sourceTrack, 'single-axis', [-5, 5], {
|
|
87
|
+
a: [-10, 10],
|
|
88
|
+
})[0]?.domain,
|
|
89
|
+
).toEqual([-10, 10])
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
it('keeps per-series fixed domains on separate axes and merges axis overflow', () => {
|
|
93
|
+
const sourceTrack = track([
|
|
94
|
+
series('a', 0, 1),
|
|
95
|
+
series('b', 10, 11),
|
|
96
|
+
series('c', 20, 21),
|
|
97
|
+
series('d', 30, 31),
|
|
98
|
+
series('e', 40, 41),
|
|
99
|
+
])
|
|
100
|
+
const groups = resolveYAxisSeriesGroups(sourceTrack, 'multi-axis', undefined, {
|
|
101
|
+
a: [-1, 1],
|
|
102
|
+
b: [-2, 2],
|
|
103
|
+
c: [-3, 3],
|
|
104
|
+
d: [-4, 4],
|
|
105
|
+
e: [-5, 5],
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
expect(groups.map((group) => group.domain)).toEqual([
|
|
109
|
+
[-1, 1],
|
|
110
|
+
[-2, 2],
|
|
111
|
+
[-3, 3],
|
|
112
|
+
[-5, 5],
|
|
113
|
+
])
|
|
114
|
+
expect(groups.every((group) => group.fixed)).toBe(true)
|
|
115
|
+
})
|
|
116
|
+
})
|
|
@@ -4,6 +4,12 @@ import type { WaveformOverlayMode } from '../../types'
|
|
|
4
4
|
import { formatScientificAxisExponent, formatScientificAxisLabel, paddedDomain } from '../../utils'
|
|
5
5
|
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
|
6
6
|
import { MAX_MULTI_Y_AXIS_COUNT, Y_AXIS_EXPONENT_GAP } from './constants'
|
|
7
|
+
import {
|
|
8
|
+
mergeYDomains,
|
|
9
|
+
resolveSeriesFixedYDomain,
|
|
10
|
+
resolveTrackFixedYDomain,
|
|
11
|
+
type WaveformYDomain,
|
|
12
|
+
} from './yDomain'
|
|
7
13
|
|
|
8
14
|
// 导出常量供外部使用
|
|
9
15
|
export { MAX_MULTI_Y_AXIS_COUNT, Y_AXIS_EXPONENT_GAP } from './constants'
|
|
@@ -14,11 +20,12 @@ const Y_AXIS_OUTER_PADDING = 4
|
|
|
14
20
|
const Y_AXIS_LABEL_GAP = 6
|
|
15
21
|
const Y_AXIS_LABEL_BAND_WIDTH = 24
|
|
16
22
|
|
|
17
|
-
interface YAxisSeriesGroup {
|
|
23
|
+
export interface YAxisSeriesGroup {
|
|
18
24
|
index: number
|
|
19
25
|
side: 'left' | 'right'
|
|
20
26
|
seriesList: DisplaySeries[]
|
|
21
27
|
domain: [number, number]
|
|
28
|
+
fixed: boolean
|
|
22
29
|
}
|
|
23
30
|
|
|
24
31
|
function resolveAxisSides(axisCount: number): Array<'left' | 'right'> {
|
|
@@ -53,6 +60,7 @@ export function buildYAxisSeriesGroups(
|
|
|
53
60
|
side: sides[index],
|
|
54
61
|
seriesList: [] as DisplaySeries[],
|
|
55
62
|
domain: [0, 1] as [number, number],
|
|
63
|
+
fixed: false,
|
|
56
64
|
}))
|
|
57
65
|
|
|
58
66
|
track.visibleSeries.forEach((series, index) => {
|
|
@@ -72,12 +80,35 @@ export function buildYAxisSeriesGroups(
|
|
|
72
80
|
return grouped
|
|
73
81
|
}
|
|
74
82
|
|
|
75
|
-
export function
|
|
83
|
+
export function resolveYAxisSeriesGroups(
|
|
84
|
+
track: DisplayTrack,
|
|
85
|
+
overlayMode: WaveformOverlayMode,
|
|
86
|
+
yDomain?: WaveformYDomain,
|
|
87
|
+
yDomains?: Record<string, WaveformYDomain>,
|
|
88
|
+
): YAxisSeriesGroup[] {
|
|
89
|
+
const trackDomain = resolveTrackFixedYDomain(track, yDomains)
|
|
90
|
+
return buildYAxisSeriesGroups(track, overlayMode).map((group) => {
|
|
91
|
+
if (trackDomain) return { ...group, domain: trackDomain, fixed: true }
|
|
92
|
+
const seriesDomains = group.seriesList.map(
|
|
93
|
+
(series) => resolveSeriesFixedYDomain(series, yDomain, yDomains) ?? series.yDomain,
|
|
94
|
+
)
|
|
95
|
+
const fixed = group.seriesList.some((series) =>
|
|
96
|
+
resolveSeriesFixedYDomain(series, yDomain, yDomains),
|
|
97
|
+
)
|
|
98
|
+
return fixed ? { ...group, domain: mergeYDomains(seriesDomains), fixed } : group
|
|
99
|
+
})
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function axisTextMetrics(
|
|
103
|
+
domain: [number, number],
|
|
104
|
+
nice = true,
|
|
105
|
+
): {
|
|
76
106
|
exponentLabel: string | null
|
|
77
107
|
exponentWidth: number
|
|
78
108
|
tickTextWidth: number
|
|
79
109
|
} {
|
|
80
|
-
const scale = scaleLinear(domain, [1, 0])
|
|
110
|
+
const scale = scaleLinear(domain, [1, 0])
|
|
111
|
+
if (nice) scale.nice()
|
|
81
112
|
const [axisMin, axisMax] = scale.domain()
|
|
82
113
|
const values = scale.ticks(10)
|
|
83
114
|
const maximumTickCharacters = Math.max(
|
|
@@ -92,15 +123,15 @@ export function axisTextMetrics(domain: [number, number]): {
|
|
|
92
123
|
}
|
|
93
124
|
}
|
|
94
125
|
|
|
95
|
-
function axisExponentClearance(domain: [number, number]): number {
|
|
96
|
-
const { exponentLabel, exponentWidth } = axisTextMetrics(domain)
|
|
126
|
+
function axisExponentClearance(domain: [number, number], nice: boolean): number {
|
|
127
|
+
const { exponentLabel, exponentWidth } = axisTextMetrics(domain, nice)
|
|
97
128
|
return exponentLabel ? exponentWidth + Y_AXIS_EXPONENT_GAP : 0
|
|
98
129
|
}
|
|
99
130
|
|
|
100
131
|
export function measureYAxisGroupClearance(group: YAxisSeriesGroup): number {
|
|
101
132
|
return (
|
|
102
|
-
axisTextMetrics(group.domain).tickTextWidth +
|
|
103
|
-
axisExponentClearance(group.domain) +
|
|
133
|
+
axisTextMetrics(group.domain, !group.fixed).tickTextWidth +
|
|
134
|
+
axisExponentClearance(group.domain, !group.fixed) +
|
|
104
135
|
Y_AXIS_TICK_PADDING +
|
|
105
136
|
Y_AXIS_LABEL_GAP +
|
|
106
137
|
Y_AXIS_LABEL_BAND_WIDTH +
|
|
@@ -110,8 +141,8 @@ export function measureYAxisGroupClearance(group: YAxisSeriesGroup): number {
|
|
|
110
141
|
|
|
111
142
|
function measureYAxisGroupTickClearance(group: YAxisSeriesGroup): number {
|
|
112
143
|
return (
|
|
113
|
-
axisTextMetrics(group.domain).tickTextWidth +
|
|
114
|
-
axisExponentClearance(group.domain) +
|
|
144
|
+
axisTextMetrics(group.domain, !group.fixed).tickTextWidth +
|
|
145
|
+
axisExponentClearance(group.domain, !group.fixed) +
|
|
115
146
|
Y_AXIS_TICK_PADDING +
|
|
116
147
|
Y_AXIS_OUTER_PADDING
|
|
117
148
|
)
|
|
@@ -120,8 +151,10 @@ function measureYAxisGroupTickClearance(group: YAxisSeriesGroup): number {
|
|
|
120
151
|
export function measureTrackYAxisClearance(
|
|
121
152
|
track: DisplayTrack,
|
|
122
153
|
overlayMode: WaveformOverlayMode,
|
|
154
|
+
yDomain?: WaveformYDomain,
|
|
155
|
+
yDomains?: Record<string, WaveformYDomain>,
|
|
123
156
|
): { left: number; right: number } {
|
|
124
|
-
return
|
|
157
|
+
return resolveYAxisSeriesGroups(track, overlayMode, yDomain, yDomains).reduce(
|
|
125
158
|
(clearance, group) => {
|
|
126
159
|
clearance[group.side] +=
|
|
127
160
|
overlayMode === 'multi-axis' || track.visibleSeries.length === 1
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
type GridCellGeometry,
|
|
20
20
|
type NormalizedWaveformGridOptions,
|
|
21
21
|
} from './grid'
|
|
22
|
-
import { axisTextMetrics,
|
|
22
|
+
import { axisTextMetrics, resolveYAxisSeriesGroups } from './layout'
|
|
23
23
|
import type { DisplaySeries, DisplayTrack, TrackLayout, WaveformYAxisLayout } from './types'
|
|
24
24
|
import { Y_AXIS_EXPONENT_GAP } from './constants'
|
|
25
25
|
import {
|
|
@@ -42,6 +42,8 @@ export interface BuildTrackLayoutsOptions {
|
|
|
42
42
|
sharedZoomDomain: [number, number]
|
|
43
43
|
initialXDomain?: [number, number]
|
|
44
44
|
initialXDomains?: Record<string, [number, number]>
|
|
45
|
+
fixedYDomain?: [number, number]
|
|
46
|
+
fixedYDomains?: Record<string, [number, number]>
|
|
45
47
|
yDomains?: Record<string, [number, number]>
|
|
46
48
|
timeUnit: 's' | 'ms'
|
|
47
49
|
rendering: ResolvedWaveformRenderingOptions
|
|
@@ -94,13 +96,18 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
94
96
|
: zoomIdentity
|
|
95
97
|
const xScale = transform.rescaleX(baseXScale)
|
|
96
98
|
const configuredYDomain = options.yDomains?.[displayTrack.id]
|
|
97
|
-
const yAxisGroups =
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
99
|
+
const yAxisGroups = resolveYAxisSeriesGroups(
|
|
100
|
+
displayTrack,
|
|
101
|
+
options.overlayMode,
|
|
102
|
+
options.fixedYDomain,
|
|
103
|
+
options.fixedYDomains,
|
|
104
|
+
).map((group) =>
|
|
105
|
+
!group.fixed && configuredYDomain ? { ...group, domain: configuredYDomain } : group,
|
|
106
|
+
)
|
|
101
107
|
const sideOffsets = { left: 0, right: 0 }
|
|
102
108
|
const yAxes: WaveformYAxisLayout[] = yAxisGroups.map((group) => {
|
|
103
|
-
const scale = scaleLinear(group.domain, [cell.plotHeight, 0])
|
|
109
|
+
const scale = scaleLinear(group.domain, [cell.plotHeight, 0])
|
|
110
|
+
if (!group.fixed) scale.nice()
|
|
104
111
|
const majorTicks = scale.ticks(Math.max(2, Math.floor(cell.plotHeight / 55)))
|
|
105
112
|
const [axisStart, axisEnd] = scale.domain()
|
|
106
113
|
const showAxisEnd = options.displayMode !== 'compact' || cell.row === 0
|
|
@@ -110,7 +117,10 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
110
117
|
const tickValues = Array.from(
|
|
111
118
|
new Set([axisStart, ...visibleMajorTicks, ...(showAxisEnd ? [axisEnd] : [])]),
|
|
112
119
|
)
|
|
113
|
-
const { exponentLabel, exponentWidth, tickTextWidth } = axisTextMetrics(
|
|
120
|
+
const { exponentLabel, exponentWidth, tickTextWidth } = axisTextMetrics(
|
|
121
|
+
group.domain,
|
|
122
|
+
!group.fixed,
|
|
123
|
+
)
|
|
114
124
|
const exponentClearance = exponentLabel ? exponentWidth + Y_AXIS_EXPONENT_GAP : 0
|
|
115
125
|
const clearance =
|
|
116
126
|
tickTextWidth +
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
shallowReactive,
|
|
7
7
|
shallowRef,
|
|
8
8
|
toRefs,
|
|
9
|
+
watch,
|
|
9
10
|
watchEffect,
|
|
10
11
|
type ComponentPublicInstance,
|
|
11
12
|
type Ref,
|
|
@@ -68,6 +69,15 @@ export function useWaveformChartController(
|
|
|
68
69
|
const annotationInteraction = useWaveformAnnotationInteraction()
|
|
69
70
|
const editorSeriesOptions = ref<AnnotationSeriesCandidate[]>([])
|
|
70
71
|
const hoverThrottle = useAnimationFrameThrottle()
|
|
72
|
+
|
|
73
|
+
watch(
|
|
74
|
+
[() => props.yDomain, () => props.yDomains],
|
|
75
|
+
() => {
|
|
76
|
+
sharedYDomains.value = {}
|
|
77
|
+
independentYDomains.value = {}
|
|
78
|
+
},
|
|
79
|
+
{ deep: true },
|
|
80
|
+
)
|
|
71
81
|
const selection = ref<ViewportSelectionState | null>(null)
|
|
72
82
|
const spacePressed = ref(false)
|
|
73
83
|
const pointerInsideChart = ref(false)
|
|
@@ -26,7 +26,12 @@ import {
|
|
|
26
26
|
resolveGridCellGeometry,
|
|
27
27
|
X_AXIS_BAND,
|
|
28
28
|
} from './grid'
|
|
29
|
-
import {
|
|
29
|
+
import {
|
|
30
|
+
buildTrackLayouts,
|
|
31
|
+
measureTrackYAxisClearance,
|
|
32
|
+
resolveYAxisSeriesGroups,
|
|
33
|
+
Y_AXIS_EXPONENT_GAP,
|
|
34
|
+
} from './layout'
|
|
30
35
|
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
|
31
36
|
import type { PreparedWaveformSeries } from './useWaveformData'
|
|
32
37
|
import type { ResolvedWaveformChartProps } from './waveformChartTypes'
|
|
@@ -104,8 +109,12 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
104
109
|
const yAxisMetrics = computed(() => {
|
|
105
110
|
const axisText = chartTracks.value
|
|
106
111
|
.filter((track) => track.visibleSeries.length > 0)
|
|
107
|
-
.
|
|
108
|
-
|
|
112
|
+
.flatMap((track) =>
|
|
113
|
+
resolveYAxisSeriesGroups(track, props.overlayMode, props.yDomain, props.yDomains),
|
|
114
|
+
)
|
|
115
|
+
.map((group) => {
|
|
116
|
+
const scale = scaleLinear(group.domain, [1, 0])
|
|
117
|
+
if (!group.fixed) scale.nice()
|
|
109
118
|
const [axisMin, axisMax] = scale.domain()
|
|
110
119
|
return {
|
|
111
120
|
exponentLabel: formatScientificAxisExponent(axisMin, axisMax),
|
|
@@ -165,7 +174,12 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
165
174
|
const multiAxisClearance = computed(() =>
|
|
166
175
|
chartTracks.value.reduce(
|
|
167
176
|
(maximum, track) => {
|
|
168
|
-
const clearance = measureTrackYAxisClearance(
|
|
177
|
+
const clearance = measureTrackYAxisClearance(
|
|
178
|
+
track,
|
|
179
|
+
props.overlayMode,
|
|
180
|
+
props.yDomain,
|
|
181
|
+
props.yDomains,
|
|
182
|
+
)
|
|
169
183
|
return {
|
|
170
184
|
left: Math.max(maximum.left, clearance.left),
|
|
171
185
|
right: Math.max(maximum.right, clearance.right),
|
|
@@ -281,6 +295,8 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
281
295
|
sharedZoomDomain: sharedZoomDomain.value,
|
|
282
296
|
initialXDomain: props.initialXDomain ? initialXDomain.value : undefined,
|
|
283
297
|
initialXDomains: props.initialXDomains,
|
|
298
|
+
fixedYDomain: props.yDomain,
|
|
299
|
+
fixedYDomains: props.yDomains,
|
|
284
300
|
yDomains:
|
|
285
301
|
props.displayMode === 'independent'
|
|
286
302
|
? Object.fromEntries(
|
|
@@ -31,6 +31,8 @@ export interface WaveformChartProps {
|
|
|
31
31
|
minVisiblePoints?: number
|
|
32
32
|
initialXDomain?: [number, number]
|
|
33
33
|
initialXDomains?: Record<string, [number, number]>
|
|
34
|
+
yDomain?: [number, number]
|
|
35
|
+
yDomains?: Record<string, [number, number]>
|
|
34
36
|
timeUnit?: 's' | 'ms'
|
|
35
37
|
frameNumber?: string | number
|
|
36
38
|
frameStyle?: WaveformFrameStyle
|