waveform-analysis 0.1.65 → 0.1.66
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 +23 -3
- package/dist/index.cjs +1 -1
- package/dist/index.js +705 -617
- package/dist/types/components/WaveformChart.vue.d.ts +3 -2
- package/dist/types/components/core/constants.d.ts +2 -0
- package/dist/types/components/core/trackLayoutBuilder.d.ts +1 -0
- package/dist/types/components/core/types.d.ts +2 -1
- package/dist/types/components/core/useWaveformChartController.d.ts +3 -0
- package/dist/types/components/core/useWaveformPresentation.d.ts +2 -0
- package/dist/types/components/core/waveformChartTypes.d.ts +3 -1
- package/dist/types/components/core/yAxisTitleLayout.d.ts +4 -0
- package/dist/types/components/interaction/integerZoom.d.ts +12 -0
- package/dist/types/components/interaction/viewportDomain.d.ts +1 -0
- package/dist/types/components/interaction/zoomConstraints.d.ts +2 -1
- package/dist/types/components/rendering/WaveformTrack.vue.d.ts +2 -6
- package/dist/types/components/rendering/WaveformTrackBackdrop.vue.d.ts +0 -4
- package/dist/types/components/rendering/WaveformZeroLines.vue.d.ts +12 -0
- package/dist/types/demo/types.d.ts +3 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/types/chart.d.ts +4 -0
- package/package.json +1 -1
- package/src/App.vue +6 -0
- package/src/components/WaveformChart.vue +1 -0
- package/src/components/core/constants.ts +2 -0
- package/src/components/core/trackLayoutBuilder.ts +26 -56
- package/src/components/core/types.ts +2 -0
- package/src/components/core/useWaveformChartLifecycle.ts +1 -1
- package/src/components/core/useWaveformLayout.ts +32 -16
- package/src/components/core/useWaveformPresentation.ts +10 -0
- package/src/components/core/waveformChartTypes.ts +3 -0
- package/src/components/core/yAxisTitleLayout.ts +78 -0
- package/src/components/interaction/integerZoom.ts +64 -0
- package/src/components/interaction/useWaveformViewport.ts +8 -12
- package/src/components/interaction/useWaveformZoom.ts +6 -4
- package/src/components/interaction/viewportDomain.ts +12 -0
- package/src/components/interaction/zoomConstraints.ts +7 -5
- package/src/components/rendering/WaveformTrack.vue +11 -4
- package/src/components/rendering/WaveformTrackAxes.vue +1 -1
- package/src/components/rendering/WaveformTrackBackdrop.vue +2 -34
- package/src/components/rendering/WaveformZeroLines.vue +53 -0
- package/src/demo/DemoChartHost.vue +1 -0
- package/src/demo/DemoGridFrameControls.vue +12 -0
- package/src/demo/DemoViewControls.vue +4 -0
- package/src/demo/types.ts +3 -0
- package/src/index.ts +1 -0
- package/src/styles.css +3 -3
- package/src/types/chart.ts +4 -0
|
@@ -53,18 +53,19 @@ declare const __VLS_export: import("vue").DefineComponent<WaveformChartProps, {
|
|
|
53
53
|
}>, {
|
|
54
54
|
legend: import("..").WaveformLegendOptions;
|
|
55
55
|
annotations: import("..").WaveformAnnotation[];
|
|
56
|
+
grid: import("..").WaveformGridOptions;
|
|
56
57
|
displayMode: import("..").WaveformDisplayMode;
|
|
57
58
|
overlayMode: import("..").WaveformOverlayMode;
|
|
59
|
+
xDomainStrategy: import("..").WaveformXDomainStrategy;
|
|
58
60
|
yLabel: string;
|
|
59
61
|
lineColor: string;
|
|
60
62
|
showTooltip: boolean;
|
|
61
63
|
zoomable: boolean;
|
|
64
|
+
integerZoom: boolean;
|
|
62
65
|
pannable: boolean;
|
|
63
66
|
minVisiblePoints: number;
|
|
64
|
-
xDomainStrategy: import("..").WaveformXDomainStrategy;
|
|
65
67
|
timeUnit: "s" | "ms";
|
|
66
68
|
annotationsVisible: boolean;
|
|
67
|
-
grid: import("..").WaveformGridOptions;
|
|
68
69
|
rendering: import("..").WaveformRenderingOptions;
|
|
69
70
|
plotMargin: import("..").WaveformPlotMargin;
|
|
70
71
|
defaultHiddenSeriesIds: string[];
|
|
@@ -17,6 +17,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
17
17
|
initialXDomain?: [number, number];
|
|
18
18
|
initialXDomains?: Record<string, [number, number]>;
|
|
19
19
|
xDomainStrategy?: WaveformXDomainStrategy;
|
|
20
|
+
integerZoom?: boolean;
|
|
20
21
|
fixedYDomain?: [number, number];
|
|
21
22
|
fixedYDomains?: Record<string, [number, number]>;
|
|
22
23
|
yDomains?: Record<string, [number, number]>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ScaleLinear } from 'd3';
|
|
2
|
-
import type { ResolvedWaveformErrorBarOptions, WaveformLineType, WaveformLineStyle, WaveformPoint, WaveformPointType } from '../../types';
|
|
2
|
+
import type { ResolvedWaveformErrorBarOptions, WaveformLineType, WaveformLineStyle, WaveformPoint, WaveformPointType, WaveformXAxisLabelFormatter } from '../../types';
|
|
3
3
|
import type { WaveformPointSource } from '../../core/waveformPointSource';
|
|
4
4
|
import type { NormalizedWaveformGridLineOptions } from './grid';
|
|
5
5
|
/**
|
|
@@ -104,6 +104,7 @@ export interface TrackLayout {
|
|
|
104
104
|
yMinorTicks: number[];
|
|
105
105
|
yAxisTickValues: number[];
|
|
106
106
|
xAxisTickValues: number[];
|
|
107
|
+
xAxisLabelFormatter?: WaveformXAxisLabelFormatter;
|
|
107
108
|
endpointLabels: {
|
|
108
109
|
start: string;
|
|
109
110
|
end: string;
|
|
@@ -298,6 +298,8 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
|
|
|
298
298
|
color: string;
|
|
299
299
|
width: number;
|
|
300
300
|
dash: string;
|
|
301
|
+
opacity: number;
|
|
302
|
+
boundaryThreshold: number;
|
|
301
303
|
};
|
|
302
304
|
legendBackgroundColor: string;
|
|
303
305
|
legendInteractive: boolean;
|
|
@@ -332,6 +334,7 @@ export declare function useWaveformChartController(props: ResolvedWaveformChartP
|
|
|
332
334
|
lineColor: string;
|
|
333
335
|
showTooltip: boolean;
|
|
334
336
|
zoomable: boolean;
|
|
337
|
+
integerZoom: boolean;
|
|
335
338
|
pannable: boolean;
|
|
336
339
|
minZoomSpan?: number | undefined;
|
|
337
340
|
minVisiblePoints: number;
|
|
@@ -26,6 +26,8 @@ export declare function useWaveformPresentation(context: PresentationContext): {
|
|
|
26
26
|
color: string;
|
|
27
27
|
width: number;
|
|
28
28
|
dash: string;
|
|
29
|
+
opacity: number;
|
|
30
|
+
boundaryThreshold: number;
|
|
29
31
|
}>;
|
|
30
32
|
legendBackgroundColor: import("vue").ComputedRef<string>;
|
|
31
33
|
legendInteractive: import("vue").ComputedRef<boolean>;
|
|
@@ -11,6 +11,8 @@ export interface WaveformChartProps {
|
|
|
11
11
|
lineColor?: string;
|
|
12
12
|
showTooltip?: boolean;
|
|
13
13
|
zoomable?: boolean;
|
|
14
|
+
/** Snap viewport endpoints outwards to integer X-axis display units. */
|
|
15
|
+
integerZoom?: boolean;
|
|
14
16
|
pannable?: boolean;
|
|
15
17
|
minZoomSpan?: number;
|
|
16
18
|
minVisiblePoints?: number;
|
|
@@ -40,7 +42,7 @@ export interface WaveformChartProps {
|
|
|
40
42
|
presentationMode?: boolean;
|
|
41
43
|
zeroLine?: WaveformZeroLineOptions;
|
|
42
44
|
}
|
|
43
|
-
type DefaultedProp = 'displayMode' | 'overlayMode' | 'yLabel' | 'lineColor' | 'showTooltip' | 'zoomable' | 'pannable' | 'minVisiblePoints' | 'xDomainStrategy' | 'timeUnit' | 'annotations' | 'annotationsVisible' | 'grid' | 'rendering' | 'plotMargin' | 'legend' | 'defaultHiddenSeriesIds' | 'cleanView' | 'presentationMode' | 'zeroLine';
|
|
45
|
+
type DefaultedProp = 'displayMode' | 'overlayMode' | 'yLabel' | 'lineColor' | 'showTooltip' | 'zoomable' | 'integerZoom' | 'pannable' | 'minVisiblePoints' | 'xDomainStrategy' | 'timeUnit' | 'annotations' | 'annotationsVisible' | 'grid' | 'rendering' | 'plotMargin' | 'legend' | 'defaultHiddenSeriesIds' | 'cleanView' | 'presentationMode' | 'zeroLine';
|
|
44
46
|
export type ResolvedWaveformChartProps = Readonly<WaveformChartProps & Required<Pick<WaveformChartProps, DefaultedProp>>>;
|
|
45
47
|
export interface WaveformChartEmit {
|
|
46
48
|
(event: 'point-hover', point: WaveformPoint | null): void;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface IntegerZoomOptions {
|
|
2
|
+
integerZoom?: boolean;
|
|
3
|
+
timeUnit?: 's' | 'ms';
|
|
4
|
+
}
|
|
5
|
+
export declare function integerZoomTicks(domain: [number, number], count: number, options: IntegerZoomOptions): number[];
|
|
6
|
+
export declare function formatIntegerZoomLabel(value: number): string;
|
|
7
|
+
/** Align outwards in display units; all returned coordinates remain in seconds. */
|
|
8
|
+
export declare function alignIntegerZoomDomain(domain: [number, number], options: IntegerZoomOptions): [number, number];
|
|
9
|
+
/** Recover aligned endpoints after a D3 transform, tolerating floating-point round-off. */
|
|
10
|
+
export declare function normalizeIntegerZoomDomain(domain: [number, number], options: IntegerZoomOptions): [number, number];
|
|
11
|
+
/** Translation snaps to the nearest unit so panning preserves the visible span. */
|
|
12
|
+
export declare function alignIntegerPanDomain(domain: [number, number], options: IntegerZoomOptions): [number, number];
|
|
@@ -17,5 +17,6 @@ interface ViewportDomainContext {
|
|
|
17
17
|
editorSeriesOptions: Ref<AnnotationSeriesCandidate[]>;
|
|
18
18
|
configureZoom: () => void;
|
|
19
19
|
}
|
|
20
|
+
export declare function clampViewportDomain(domain: [number, number], boundary: [number, number]): [number, number];
|
|
20
21
|
export declare function createViewportDomainSetter(context: ViewportDomainContext): (domain: [number, number], trackIndex?: number) => void;
|
|
21
22
|
export {};
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { type ZoomTransform } from 'd3';
|
|
2
2
|
import type { WaveformPoint } from '../../types';
|
|
3
3
|
import { type WaveformPointSource } from '../../core/waveformPointSource';
|
|
4
|
+
import { type IntegerZoomOptions } from './integerZoom';
|
|
4
5
|
interface PointSeriesSource {
|
|
5
6
|
points: WaveformPoint[];
|
|
6
7
|
source?: WaveformPointSource;
|
|
7
8
|
}
|
|
8
9
|
export type ZoomSeriesGroup = readonly PointSeriesSource[];
|
|
9
|
-
interface ZoomConstraintOptions {
|
|
10
|
+
interface ZoomConstraintOptions extends IntegerZoomOptions {
|
|
10
11
|
minZoomSpan?: number;
|
|
11
12
|
minVisiblePoints?: number;
|
|
12
13
|
maxZoomScale?: number | null;
|
|
@@ -15,9 +15,7 @@ interface Props {
|
|
|
15
15
|
timeUnit: 's' | 'ms';
|
|
16
16
|
yLabel?: string;
|
|
17
17
|
cleanView?: boolean;
|
|
18
|
-
zeroLine?: Required<
|
|
19
|
-
visible: boolean;
|
|
20
|
-
};
|
|
18
|
+
zeroLine?: Required<WaveformZeroLineOptions>;
|
|
21
19
|
}
|
|
22
20
|
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {} & {
|
|
23
21
|
contextmenu: (mouseEvent: MouseEvent) => any;
|
|
@@ -38,9 +36,7 @@ declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {},
|
|
|
38
36
|
}>, {
|
|
39
37
|
interactive: boolean;
|
|
40
38
|
cleanView: boolean;
|
|
41
|
-
zeroLine: Required<
|
|
42
|
-
visible: boolean;
|
|
43
|
-
};
|
|
39
|
+
zeroLine: Required<WaveformZeroLineOptions>;
|
|
44
40
|
interactionMode: WaveformInteractionMode;
|
|
45
41
|
}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
46
42
|
declare const _default: typeof __VLS_export;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import type { WaveformZeroLineOptions } from '../../types';
|
|
2
1
|
import type { TrackLayout } from '../core/types';
|
|
3
2
|
interface Props {
|
|
4
3
|
track: TrackLayout;
|
|
@@ -9,9 +8,6 @@ interface Props {
|
|
|
9
8
|
frameStyle: {
|
|
10
9
|
backgroundColor: string;
|
|
11
10
|
};
|
|
12
|
-
zeroLine: Required<Pick<WaveformZeroLineOptions, 'color' | 'width' | 'dash'>> & {
|
|
13
|
-
visible: boolean;
|
|
14
|
-
};
|
|
15
11
|
}
|
|
16
12
|
declare const __VLS_export: import("vue").DefineComponent<Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
17
13
|
declare const _default: typeof __VLS_export;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WaveformZeroLineOptions } from '../../types';
|
|
2
|
+
import type { TrackLayout } from '../core/types';
|
|
3
|
+
type __VLS_Props = {
|
|
4
|
+
track: TrackLayout;
|
|
5
|
+
clipPathId: string;
|
|
6
|
+
innerWidth: number;
|
|
7
|
+
cleanView: boolean;
|
|
8
|
+
zeroLine: Required<WaveformZeroLineOptions>;
|
|
9
|
+
};
|
|
10
|
+
declare const __VLS_export: import("vue").DefineComponent<__VLS_Props, {}, {}, {}, {}, import("vue").ComponentOptionsMixin, import("vue").ComponentOptionsMixin, {}, string, import("vue").PublicProps, Readonly<__VLS_Props> & Readonly<{}>, {}, {}, {}, {}, string, import("vue").ComponentProvideOptions, false, {}, any>;
|
|
11
|
+
declare const _default: typeof __VLS_export;
|
|
12
|
+
export default _default;
|
|
@@ -9,6 +9,7 @@ export interface DemoControlPanelModel {
|
|
|
9
9
|
displayMode: WaveformDisplayMode;
|
|
10
10
|
overlayMode: WaveformOverlayMode;
|
|
11
11
|
showTooltip: boolean;
|
|
12
|
+
integerZoom: boolean;
|
|
12
13
|
plotMarginTop: number;
|
|
13
14
|
plotMarginBottom: number;
|
|
14
15
|
cleanView: boolean;
|
|
@@ -20,6 +21,7 @@ export interface DemoControlPanelModel {
|
|
|
20
21
|
zeroLineVisible: boolean;
|
|
21
22
|
zeroLineColor: string;
|
|
22
23
|
zeroLineWidth: number;
|
|
24
|
+
zeroLineOpacity: number;
|
|
23
25
|
zeroLineDash: string;
|
|
24
26
|
zeroLineDashOptions: Array<SelectOption<string>>;
|
|
25
27
|
rowCount: number;
|
|
@@ -82,6 +84,7 @@ export interface DemoChartModel {
|
|
|
82
84
|
cleanView: boolean;
|
|
83
85
|
presentationMode: boolean;
|
|
84
86
|
showTooltip: boolean;
|
|
87
|
+
integerZoom: boolean;
|
|
85
88
|
plotMargin: WaveformPlotMargin;
|
|
86
89
|
zeroLine: WaveformZeroLineOptions;
|
|
87
90
|
frameWatermarkVisible: boolean;
|
package/dist/types/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* @packageDocumentation
|
|
4
4
|
*/
|
|
5
5
|
export { default as WaveformChart } from './components/WaveformChart.vue';
|
|
6
|
+
export type { WaveformChartProps } from './components/core/waveformChartTypes';
|
|
6
7
|
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, SingleWaveformData, WaveformLineType, WaveformLineStyle, WaveformPointType, WaveformErrorBarOptions, ResolvedWaveformErrorBarOptions, WaveformTypedValues, TypedSampleData, TypedPointData, WaveformSeries, WaveformData, NormalizedWaveformSeries, } from './types';
|
|
7
8
|
export type { WaveformGridOptions, WaveformGridLineOptions, WaveformGridTrackLines, } from './components/core/grid';
|
|
8
9
|
export { normalizeWaveformData, normalizeWaveformSeries } from './core';
|
|
@@ -233,4 +233,8 @@ export interface WaveformZeroLineOptions {
|
|
|
233
233
|
width?: number;
|
|
234
234
|
/** SVG stroke-dasharray. Defaults to '6 4'; use '' for a solid line. */
|
|
235
235
|
dash?: string;
|
|
236
|
+
/** Stroke opacity from 0 to 1. Defaults to 0.5; multiplies the color's alpha. */
|
|
237
|
+
opacity?: number;
|
|
238
|
+
/** Hide within this fraction of a Y-axis tick interval from either boundary. Defaults to 0.02. */
|
|
239
|
+
boundaryThreshold?: number;
|
|
236
240
|
}
|
package/package.json
CHANGED
package/src/App.vue
CHANGED
|
@@ -48,11 +48,13 @@ const annotationsVisible = ref(true)
|
|
|
48
48
|
const cleanView = ref(false)
|
|
49
49
|
const presentationMode = ref(false)
|
|
50
50
|
const showTooltip = ref(true)
|
|
51
|
+
const integerZoom = ref(true)
|
|
51
52
|
const plotMarginTop = ref(18)
|
|
52
53
|
const plotMarginBottom = ref(52)
|
|
53
54
|
const zeroLineVisible = ref(true)
|
|
54
55
|
const zeroLineColor = ref('#ff0000')
|
|
55
56
|
const zeroLineWidth = ref(1)
|
|
57
|
+
const zeroLineOpacity = ref(0.5)
|
|
56
58
|
const zeroLineDash = ref('6 4')
|
|
57
59
|
const interactionMode = ref<WaveformInteractionMode>('zoom')
|
|
58
60
|
const legendPosition = ref<WaveformLegendPosition>('top-right')
|
|
@@ -127,6 +129,7 @@ const zeroLine = computed<WaveformZeroLineOptions>(() => ({
|
|
|
127
129
|
visible: zeroLineVisible.value,
|
|
128
130
|
color: zeroLineColor.value,
|
|
129
131
|
width: zeroLineWidth.value,
|
|
132
|
+
opacity: zeroLineOpacity.value,
|
|
130
133
|
dash: zeroLineDash.value,
|
|
131
134
|
}))
|
|
132
135
|
const plotMargin = computed<WaveformPlotMargin>(() => ({
|
|
@@ -298,6 +301,7 @@ const controlPanelModel = reactive({
|
|
|
298
301
|
displayMode,
|
|
299
302
|
overlayMode,
|
|
300
303
|
showTooltip,
|
|
304
|
+
integerZoom,
|
|
301
305
|
plotMarginTop,
|
|
302
306
|
plotMarginBottom,
|
|
303
307
|
cleanView,
|
|
@@ -309,6 +313,7 @@ const controlPanelModel = reactive({
|
|
|
309
313
|
zeroLineVisible,
|
|
310
314
|
zeroLineColor,
|
|
311
315
|
zeroLineWidth,
|
|
316
|
+
zeroLineOpacity,
|
|
312
317
|
zeroLineDash,
|
|
313
318
|
zeroLineDashOptions,
|
|
314
319
|
rowCount,
|
|
@@ -365,6 +370,7 @@ const chartModel = reactive({
|
|
|
365
370
|
cleanView,
|
|
366
371
|
presentationMode,
|
|
367
372
|
showTooltip,
|
|
373
|
+
integerZoom,
|
|
368
374
|
plotMargin,
|
|
369
375
|
zeroLine,
|
|
370
376
|
frameWatermarkVisible,
|
|
@@ -27,7 +27,6 @@ import {
|
|
|
27
27
|
} from './grid'
|
|
28
28
|
import {
|
|
29
29
|
axisTextMetrics,
|
|
30
|
-
formatYAxisTickLabel,
|
|
31
30
|
resolveRenderedYAxisSeriesGroups,
|
|
32
31
|
resolveYAxisTicks,
|
|
33
32
|
resolveYAxisTickCount,
|
|
@@ -35,9 +34,15 @@ import {
|
|
|
35
34
|
} from './layout'
|
|
36
35
|
import type { DisplayTrack, TrackLayout, WaveformYAxisLayout } from './types'
|
|
37
36
|
import { applyXDomainStrategy } from './xDomain'
|
|
37
|
+
import { alignLeftYAxisTitles } from './yAxisTitleLayout'
|
|
38
|
+
import {
|
|
39
|
+
alignIntegerZoomDomain,
|
|
40
|
+
normalizeIntegerZoomDomain,
|
|
41
|
+
integerZoomTicks,
|
|
42
|
+
formatIntegerZoomLabel,
|
|
43
|
+
} from '../interaction/integerZoom'
|
|
38
44
|
import {
|
|
39
45
|
Y_AXIS_LABEL_BAND_WIDTH,
|
|
40
|
-
Y_AXIS_CHARACTER_WIDTH,
|
|
41
46
|
Y_AXIS_LABEL_GAP,
|
|
42
47
|
Y_AXIS_OUTER_PADDING,
|
|
43
48
|
Y_AXIS_RIGHT_LABEL_OFFSET,
|
|
@@ -58,6 +63,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
58
63
|
initialXDomain?: [number, number]
|
|
59
64
|
initialXDomains?: Record<string, [number, number]>
|
|
60
65
|
xDomainStrategy?: WaveformXDomainStrategy
|
|
66
|
+
integerZoom?: boolean
|
|
61
67
|
fixedYDomain?: [number, number]
|
|
62
68
|
fixedYDomains?: Record<string, [number, number]>
|
|
63
69
|
yDomains?: Record<string, [number, number]>
|
|
@@ -85,9 +91,12 @@ function resolveIndependentXDomain(
|
|
|
85
91
|
options.initialXDomains?.[track.id] ??
|
|
86
92
|
options.initialXDomains?.[seriesId] ??
|
|
87
93
|
options.initialXDomain
|
|
88
|
-
return
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
return alignIntegerZoomDomain(
|
|
95
|
+
explicitDomain
|
|
96
|
+
? applyXDomainStrategy(explicitDomain, strategy, true)
|
|
97
|
+
: applyXDomainStrategy(track.xDomain, strategy),
|
|
98
|
+
options,
|
|
99
|
+
)
|
|
91
100
|
}
|
|
92
101
|
|
|
93
102
|
export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayout[] {
|
|
@@ -119,6 +128,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
119
128
|
? (options.independentTransforms[index] ?? zoomIdentity)
|
|
120
129
|
: zoomIdentity
|
|
121
130
|
const xScale = transform.rescaleX(baseXScale)
|
|
131
|
+
xScale.domain(normalizeIntegerZoomDomain(xScale.domain() as [number, number], options))
|
|
122
132
|
const yAxisGroups = resolveRenderedYAxisSeriesGroups(
|
|
123
133
|
displayTrack,
|
|
124
134
|
options.overlayMode,
|
|
@@ -181,25 +191,19 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
181
191
|
const fallbackYScale = scaleLinear(displayTrack.yDomain, [cell.plotHeight, 0])
|
|
182
192
|
if (options.yAxisNice !== false) fallbackYScale.nice()
|
|
183
193
|
const yScale = yAxes[0]?.scale ?? fallbackYScale
|
|
184
|
-
const xMajorTicks =
|
|
194
|
+
const xMajorTicks = integerZoomTicks(
|
|
195
|
+
xScale.domain() as [number, number],
|
|
196
|
+
Math.max(2, Math.floor(cell.width / 100)),
|
|
197
|
+
options,
|
|
198
|
+
)
|
|
199
|
+
const xAxisLabelFormatter =
|
|
200
|
+
options.xAxisLabelFormatter ?? (options.integerZoom ? formatIntegerZoomLabel : undefined)
|
|
185
201
|
const yMajorTicks = yAxes[0]?.majorTicks ?? []
|
|
186
202
|
const yAxisTickValues = yAxes[0]?.tickValues ?? []
|
|
187
203
|
const domain = xScale.domain() as [number, number]
|
|
188
204
|
const endpointLabels = {
|
|
189
|
-
start: formatXAxisLabel(
|
|
190
|
-
|
|
191
|
-
domain,
|
|
192
|
-
options.timeUnit,
|
|
193
|
-
'start',
|
|
194
|
-
options.xAxisLabelFormatter,
|
|
195
|
-
),
|
|
196
|
-
end: formatXAxisLabel(
|
|
197
|
-
domain[1],
|
|
198
|
-
domain,
|
|
199
|
-
options.timeUnit,
|
|
200
|
-
'end',
|
|
201
|
-
options.xAxisLabelFormatter,
|
|
202
|
-
),
|
|
205
|
+
start: formatXAxisLabel(domain[0], domain, options.timeUnit, 'start', xAxisLabelFormatter),
|
|
206
|
+
end: formatXAxisLabel(domain[1], domain, options.timeUnit, 'end', xAxisLabelFormatter),
|
|
203
207
|
}
|
|
204
208
|
const leftClearance = endpointLabels.start.length * 7 + 10
|
|
205
209
|
const rightClearance = endpointLabels.end.length * 7 + 10
|
|
@@ -262,6 +266,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
262
266
|
yScale,
|
|
263
267
|
yAxes,
|
|
264
268
|
xMajorTicks,
|
|
269
|
+
xAxisLabelFormatter,
|
|
265
270
|
xMinorTicks: buildMinorTicks(xMajorTicks, 3, domain),
|
|
266
271
|
yMajorTicks,
|
|
267
272
|
yMinorTicks: yAxes[0]?.minorTicks ?? [],
|
|
@@ -283,41 +288,6 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
283
288
|
}
|
|
284
289
|
})
|
|
285
290
|
|
|
286
|
-
|
|
287
|
-
const titleDistances = new Map<string, number>()
|
|
288
|
-
for (const track of layouts) {
|
|
289
|
-
let leftIndex = 0
|
|
290
|
-
for (const axis of track.yAxes) {
|
|
291
|
-
if (axis.side !== 'left') continue
|
|
292
|
-
const key = `${track.column}:${leftIndex++}`
|
|
293
|
-
const series = axis.seriesList[0]
|
|
294
|
-
const title = series?.name.trim() || options.yLabel || ''
|
|
295
|
-
const halfTitleHeight = title.length * 8 + 6
|
|
296
|
-
const nearbyTicks = axis.tickValues.filter(
|
|
297
|
-
(value) => Math.abs(axis.scale(value) - track.height / 2) <= halfTitleHeight,
|
|
298
|
-
)
|
|
299
|
-
const tickWidth = Math.max(
|
|
300
|
-
Y_AXIS_CHARACTER_WIDTH,
|
|
301
|
-
...nearbyTicks.map(
|
|
302
|
-
(value) =>
|
|
303
|
-
formatYAxisTickLabel(
|
|
304
|
-
value,
|
|
305
|
-
axis.scale.domain() as [number, number],
|
|
306
|
-
axis.tickValues,
|
|
307
|
-
series?.unit,
|
|
308
|
-
).length * Y_AXIS_CHARACTER_WIDTH,
|
|
309
|
-
),
|
|
310
|
-
)
|
|
311
|
-
const distance = tickWidth + Y_AXIS_TICK_PADDING + Y_AXIS_LABEL_BAND_WIDTH / 2
|
|
312
|
-
titleDistances.set(key, Math.max(titleDistances.get(key) ?? 0, distance))
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
for (const track of layouts) {
|
|
316
|
-
let leftIndex = 0
|
|
317
|
-
for (const axis of track.yAxes) {
|
|
318
|
-
if (axis.side !== 'left') continue
|
|
319
|
-
axis.labelX = axis.x - titleDistances.get(`${track.column}:${leftIndex++}`)!
|
|
320
|
-
}
|
|
321
|
-
}
|
|
291
|
+
alignLeftYAxisTitles(layouts, options.yLabel)
|
|
322
292
|
return layouts
|
|
323
293
|
}
|
|
@@ -5,6 +5,7 @@ import type {
|
|
|
5
5
|
WaveformLineStyle,
|
|
6
6
|
WaveformPoint,
|
|
7
7
|
WaveformPointType,
|
|
8
|
+
WaveformXAxisLabelFormatter,
|
|
8
9
|
} from '../../types'
|
|
9
10
|
import type { WaveformPointSource } from '../../core/waveformPointSource'
|
|
10
11
|
import type { NormalizedWaveformGridLineOptions } from './grid'
|
|
@@ -114,6 +115,7 @@ export interface TrackLayout {
|
|
|
114
115
|
yMinorTicks: number[]
|
|
115
116
|
yAxisTickValues: number[]
|
|
116
117
|
xAxisTickValues: number[]
|
|
118
|
+
xAxisLabelFormatter?: WaveformXAxisLabelFormatter
|
|
117
119
|
endpointLabels: { start: string; end: string }
|
|
118
120
|
path: string | null
|
|
119
121
|
seriesPaths: TrackSeriesPath[]
|
|
@@ -239,7 +239,7 @@ export function useWaveformChartLifecycle(context: LifecycleContext) {
|
|
|
239
239
|
)
|
|
240
240
|
|
|
241
241
|
watch(
|
|
242
|
-
() => props.displayMode,
|
|
242
|
+
() => [props.displayMode, props.integerZoom, props.integerZoom ? props.timeUnit : undefined],
|
|
243
243
|
() => {
|
|
244
244
|
const previousPage = currentPage.value
|
|
245
245
|
currentPage.value = 1
|
|
@@ -21,6 +21,7 @@ import { createFrameNumberResolver, resolvePageableTracks } from './trackPaginat
|
|
|
21
21
|
import type { PreparedWaveformSeries } from './useWaveformData'
|
|
22
22
|
import type { ResolvedWaveformChartProps } from './waveformChartTypes'
|
|
23
23
|
import { applyXDomainStrategy } from './xDomain'
|
|
24
|
+
import { alignIntegerZoomDomain, normalizeIntegerZoomDomain } from '../interaction/integerZoom'
|
|
24
25
|
import { resolveYAxisLayoutMetrics, resolveViewportYDomains } from './yAxisLayoutMetrics'
|
|
25
26
|
import type { useWaveformAnnotationInteraction } from '../annotation'
|
|
26
27
|
interface LayoutContext {
|
|
@@ -220,13 +221,19 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
220
221
|
Number.isFinite(domain[1]) &&
|
|
221
222
|
domain[0] !== domain[1]
|
|
222
223
|
) {
|
|
223
|
-
return
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
224
|
+
return alignIntegerZoomDomain(
|
|
225
|
+
applyXDomainStrategy(
|
|
226
|
+
domain[0] < domain[1] ? domain : [domain[1], domain[0]],
|
|
227
|
+
props.xDomainStrategy,
|
|
228
|
+
true,
|
|
229
|
+
),
|
|
230
|
+
props,
|
|
227
231
|
)
|
|
228
232
|
}
|
|
229
|
-
return
|
|
233
|
+
return alignIntegerZoomDomain(
|
|
234
|
+
applyXDomainStrategy(sharedXDomain.value, props.xDomainStrategy),
|
|
235
|
+
props,
|
|
236
|
+
)
|
|
230
237
|
})
|
|
231
238
|
const resolveInitialTrackDomain = (track: TrackLayout): [number, number] => {
|
|
232
239
|
const configuredDomain =
|
|
@@ -239,24 +246,32 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
239
246
|
Number.isFinite(configuredDomain[1]) &&
|
|
240
247
|
configuredDomain[0] !== configuredDomain[1]
|
|
241
248
|
) {
|
|
242
|
-
return
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
249
|
+
return alignIntegerZoomDomain(
|
|
250
|
+
applyXDomainStrategy(
|
|
251
|
+
configuredDomain[0] < configuredDomain[1]
|
|
252
|
+
? configuredDomain
|
|
253
|
+
: [configuredDomain[1], configuredDomain[0]],
|
|
254
|
+
props.xDomainStrategy,
|
|
255
|
+
true,
|
|
256
|
+
),
|
|
257
|
+
props,
|
|
248
258
|
)
|
|
249
259
|
}
|
|
250
|
-
return
|
|
251
|
-
|
|
252
|
-
|
|
260
|
+
return alignIntegerZoomDomain(
|
|
261
|
+
applyXDomainStrategy(
|
|
262
|
+
paddedDomain(track.seriesList.flatMap((series) => series.xDomain)),
|
|
263
|
+
props.xDomainStrategy,
|
|
264
|
+
),
|
|
265
|
+
props,
|
|
253
266
|
)
|
|
254
267
|
}
|
|
255
|
-
const sharedZoomDomain = computed(
|
|
256
|
-
(
|
|
268
|
+
const sharedZoomDomain = computed(() =>
|
|
269
|
+
normalizeIntegerZoomDomain(
|
|
257
270
|
sharedTransform.value
|
|
258
271
|
.rescaleX(scaleLinear(initialXDomain.value, [0, innerWidth.value]))
|
|
259
272
|
.domain() as [number, number],
|
|
273
|
+
props,
|
|
274
|
+
),
|
|
260
275
|
)
|
|
261
276
|
const gridCells = computed(() => {
|
|
262
277
|
const cells = resolveGridCellGeometry(
|
|
@@ -280,6 +295,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
280
295
|
initialXDomain: props.initialXDomain ? initialXDomain.value : undefined,
|
|
281
296
|
initialXDomains: props.initialXDomains,
|
|
282
297
|
xDomainStrategy: props.xDomainStrategy,
|
|
298
|
+
integerZoom: props.integerZoom,
|
|
283
299
|
fixedYDomain: props.yDomain,
|
|
284
300
|
fixedYDomains: props.yDomains,
|
|
285
301
|
yDomains: viewportYDomains.value,
|
|
@@ -53,6 +53,8 @@ export function useWaveformPresentation(context: PresentationContext) {
|
|
|
53
53
|
const isPresentationMode = computed(() => props.presentationMode === true)
|
|
54
54
|
const resolvedZeroLine = computed(() => {
|
|
55
55
|
const width = props.zeroLine.width
|
|
56
|
+
const opacity = props.zeroLine.opacity
|
|
57
|
+
const threshold = props.zeroLine.boundaryThreshold
|
|
56
58
|
return {
|
|
57
59
|
visible: props.zeroLine.visible !== false,
|
|
58
60
|
color: props.zeroLine.color || ZERO_LINE_DEFAULTS.COLOR,
|
|
@@ -61,6 +63,14 @@ export function useWaveformPresentation(context: PresentationContext) {
|
|
|
61
63
|
? width
|
|
62
64
|
: ZERO_LINE_DEFAULTS.WIDTH,
|
|
63
65
|
dash: props.zeroLine.dash ?? ZERO_LINE_DEFAULTS.DASH,
|
|
66
|
+
opacity:
|
|
67
|
+
typeof opacity === 'number' && Number.isFinite(opacity) && opacity >= 0 && opacity <= 1
|
|
68
|
+
? opacity
|
|
69
|
+
: ZERO_LINE_DEFAULTS.OPACITY,
|
|
70
|
+
boundaryThreshold:
|
|
71
|
+
typeof threshold === 'number' && Number.isFinite(threshold) && threshold >= 0
|
|
72
|
+
? threshold
|
|
73
|
+
: ZERO_LINE_DEFAULTS.BOUNDARY_THRESHOLD,
|
|
64
74
|
}
|
|
65
75
|
})
|
|
66
76
|
const legendBackgroundColor = computed(
|
|
@@ -33,6 +33,8 @@ export interface WaveformChartProps {
|
|
|
33
33
|
lineColor?: string
|
|
34
34
|
showTooltip?: boolean
|
|
35
35
|
zoomable?: boolean
|
|
36
|
+
/** Snap viewport endpoints outwards to integer X-axis display units. */
|
|
37
|
+
integerZoom?: boolean
|
|
36
38
|
pannable?: boolean
|
|
37
39
|
minZoomSpan?: number
|
|
38
40
|
minVisiblePoints?: number
|
|
@@ -70,6 +72,7 @@ type DefaultedProp =
|
|
|
70
72
|
| 'lineColor'
|
|
71
73
|
| 'showTooltip'
|
|
72
74
|
| 'zoomable'
|
|
75
|
+
| 'integerZoom'
|
|
73
76
|
| 'pannable'
|
|
74
77
|
| 'minVisiblePoints'
|
|
75
78
|
| 'xDomainStrategy'
|