waveform-analysis 0.1.47 → 0.1.48
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 +7 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +68 -62
- package/dist/types/components/core/layout.d.ts +2 -2
- package/dist/types/components/core/trackLayoutBuilder.d.ts +1 -0
- package/dist/types/types/chart.d.ts +2 -0
- package/package.json +1 -1
- package/src/components/core/layout.ts +15 -6
- package/src/components/core/trackLayoutBuilder.ts +5 -2
- package/src/components/core/useWaveformLayout.ts +12 -6
- package/src/components/core/yAxisNice.test.ts +174 -0
- package/src/components/waveformChartCases/trackOrder.test.ts +130 -0
- package/src/demo/xAxisLabelControls.test.ts +1 -1
- package/src/types/chart.ts +2 -0
package/dist/index.js
CHANGED
|
@@ -1135,7 +1135,7 @@ function pn(e) {
|
|
|
1135
1135
|
right: 0
|
|
1136
1136
|
}, p = d.map((n) => {
|
|
1137
1137
|
let r = yn(t.plotHeight, e.yAxisSplitNumber), i = Math.max(1, r - 1), a = V(n.domain, [t.plotHeight, 0]);
|
|
1138
|
-
a.nice(i);
|
|
1138
|
+
e.yAxisNice !== !1 && a.nice(i);
|
|
1139
1139
|
let [o, s] = a.domain(), c = Array.from({ length: r }, (e, t) => o + (s - o) * t / (r - 1)), l = e.displayMode !== "compact" || t.row === 0 ? c : c.slice(0, -1), { tickTextWidth: u } = wn(a.domain(), !1, l, n.seriesList[0]?.unit, r), d = u + 7 + 0 + 12 + 4, p = n.side === "left" ? -f.left : t.width + f.right, m = u + 7 + 0 + 12 / 2, h = p + (n.side === "left" ? -m : m);
|
|
1140
1140
|
return f[n.side] += d, {
|
|
1141
1141
|
index: n.index,
|
|
@@ -1148,14 +1148,16 @@ function pn(e) {
|
|
|
1148
1148
|
tickValues: l,
|
|
1149
1149
|
seriesList: n.seriesList
|
|
1150
1150
|
};
|
|
1151
|
-
}), m =
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1151
|
+
}), m = V(a.yDomain, [t.plotHeight, 0]);
|
|
1152
|
+
e.yAxisNice !== !1 && m.nice();
|
|
1153
|
+
let h = p[0]?.scale ?? m, g = l.ticks(Math.max(2, Math.floor(t.width / 100))), _ = p[0]?.majorTicks ?? [], v = p[0]?.tickValues ?? [], y = l.domain(), b = {
|
|
1154
|
+
start: ut(y[0], y, e.timeUnit, "start", e.xAxisLabelFormatter),
|
|
1155
|
+
end: ut(y[1], y, e.timeUnit, "end", e.xAxisLabelFormatter)
|
|
1156
|
+
}, x = b.start.length * 7 + 10, S = b.end.length * 7 + 10, C = g.filter((e) => {
|
|
1155
1157
|
let n = l(e);
|
|
1156
|
-
return n >
|
|
1157
|
-
}),
|
|
1158
|
-
let r = p.find((e) => e.seriesList.some((e) => e.id === n.id)), a = r?.scale ??
|
|
1158
|
+
return n > x && n < t.width - S;
|
|
1159
|
+
}), w = a.visibleSeries.map((n) => {
|
|
1160
|
+
let r = p.find((e) => e.seriesList.some((e) => e.id === n.id)), a = r?.scale ?? h, o = Qt(n.points, y, t.width, e.rendering, {
|
|
1159
1161
|
lineVisible: !i && n.lineType !== "none",
|
|
1160
1162
|
pointVisible: n.pointType !== "none",
|
|
1161
1163
|
errorBarVisible: n.errorBar.visible,
|
|
@@ -1186,17 +1188,17 @@ function pn(e) {
|
|
|
1186
1188
|
width: t.width,
|
|
1187
1189
|
height: t.plotHeight,
|
|
1188
1190
|
xScale: l,
|
|
1189
|
-
yScale:
|
|
1191
|
+
yScale: h,
|
|
1190
1192
|
yAxes: p,
|
|
1191
|
-
xMajorTicks:
|
|
1192
|
-
xMinorTicks: Xe(
|
|
1193
|
-
yMajorTicks:
|
|
1193
|
+
xMajorTicks: g,
|
|
1194
|
+
xMinorTicks: Xe(g, 3, y),
|
|
1195
|
+
yMajorTicks: _,
|
|
1194
1196
|
yMinorTicks: p[0]?.minorTicks ?? [],
|
|
1195
|
-
yAxisTickValues:
|
|
1196
|
-
xAxisTickValues:
|
|
1197
|
-
endpointLabels:
|
|
1198
|
-
path:
|
|
1199
|
-
seriesPaths:
|
|
1197
|
+
yAxisTickValues: v,
|
|
1198
|
+
xAxisTickValues: C,
|
|
1199
|
+
endpointLabels: b,
|
|
1200
|
+
path: w[0]?.path ?? null,
|
|
1201
|
+
seriesPaths: w,
|
|
1200
1202
|
gridLines: e.grid.trackLines[a.id] ?? {
|
|
1201
1203
|
horizontal: !0,
|
|
1202
1204
|
vertical: !0
|
|
@@ -1273,14 +1275,14 @@ function wn(e, t = !0, n, r, i = 10) {
|
|
|
1273
1275
|
unit: r
|
|
1274
1276
|
}).length)) * mn };
|
|
1275
1277
|
}
|
|
1276
|
-
function Tn(e, t) {
|
|
1277
|
-
return wn(e.domain,
|
|
1278
|
+
function Tn(e, t, n = !0) {
|
|
1279
|
+
return wn(e.domain, n, void 0, e.seriesList[0]?.unit, t).tickTextWidth + hn + _n + vn + gn;
|
|
1278
1280
|
}
|
|
1279
|
-
function En(e, t) {
|
|
1280
|
-
return wn(e.domain,
|
|
1281
|
+
function En(e, t, n = !0) {
|
|
1282
|
+
return wn(e.domain, n, void 0, e.seriesList[0]?.unit, t).tickTextWidth + hn + gn;
|
|
1281
1283
|
}
|
|
1282
|
-
function Dn(e, t, n, r, i) {
|
|
1283
|
-
return Cn(e, t, n, r).reduce((n, r) => (n[r.side] += t === "multi-axis" || e.visibleSeries.length === 1 ? Tn(r, i) : En(r, i), n), {
|
|
1284
|
+
function Dn(e, t, n, r, i, a = !0) {
|
|
1285
|
+
return Cn(e, t, n, r).reduce((n, r) => (n[r.side] += t === "multi-axis" || e.visibleSeries.length === 1 ? Tn(r, i, a) : En(r, i, a), n), {
|
|
1284
1286
|
left: 0,
|
|
1285
1287
|
right: 0
|
|
1286
1288
|
});
|
|
@@ -2258,14 +2260,17 @@ function rr(e) {
|
|
|
2258
2260
|
};
|
|
2259
2261
|
});
|
|
2260
2262
|
}), _ = t(() => Ut(n.rendering)), v = t(() => rn(g.value.length, h.value)), y = t(() => an(g.value, i.value, h.value)), b = t(() => {
|
|
2261
|
-
let e =
|
|
2263
|
+
let e = y.value.filter((e) => e.series.length > 0);
|
|
2264
|
+
return h.value.fillIncompleteLastRow && e.length > 0 ? e : y.value;
|
|
2265
|
+
}), x = t(() => {
|
|
2266
|
+
let e = g.value.filter((e) => e.visibleSeries.length > 0).flatMap((e) => Cn(e, n.overlayMode, n.yDomain, n.yDomains)).map((e) => wn(e.domain, n.axes?.y?.nice !== !1, void 0, e.seriesList[0]?.unit, n.axes?.y?.splitNumber).tickTextWidth), t = Math.max(7, ...e), r = t + 7 + 4, i = -(7 + t + 0 + 12 / 2);
|
|
2262
2267
|
return {
|
|
2263
2268
|
tickClearance: r,
|
|
2264
2269
|
fullClearance: t + 7 + 0 + 12 + 4,
|
|
2265
2270
|
labelCenterX: i
|
|
2266
2271
|
};
|
|
2267
|
-
}),
|
|
2268
|
-
let r = Dn(t, n.overlayMode, n.yDomain, n.yDomains, n.axes?.y?.splitNumber);
|
|
2272
|
+
}), S = t(() => g.value.some((e) => e.visibleSeries.length === 1 && !!(e.visibleSeries[0]?.name.trim() || n.yLabel))), C = t(() => g.value.some((e) => e.visibleSeries.length > 0)), w = t(() => Math.max(_t.left, S.value ? x.value.fullClearance : C.value ? x.value.tickClearance : 0)), T = t(() => g.value.reduce((e, t) => {
|
|
2273
|
+
let r = Dn(t, n.overlayMode, n.yDomain, n.yDomains, n.axes?.y?.splitNumber, n.axes?.y?.nice !== !1);
|
|
2269
2274
|
return {
|
|
2270
2275
|
left: Math.max(e.left, r.left),
|
|
2271
2276
|
right: Math.max(e.right, r.right)
|
|
@@ -2273,34 +2278,34 @@ function rr(e) {
|
|
|
2273
2278
|
}, {
|
|
2274
2279
|
left: 0,
|
|
2275
2280
|
right: 0
|
|
2276
|
-
})),
|
|
2277
|
-
let e = on(n.displayMode), t = h.value.columnCount, r = t > 1, i = Math.max(e,
|
|
2281
|
+
})), E = t(() => n.overlayMode === "multi-axis" ? Math.max(w.value, T.value.left) : w.value), D = t(() => n.overlayMode === "multi-axis" ? Math.max(_t.right, T.value.right) : _t.right), O = t(() => Math.max(0, o.value - E.value - D.value)), k = t(() => {
|
|
2282
|
+
let e = on(n.displayMode), t = h.value.columnCount, r = t > 1, i = Math.max(e, x.value.fullClearance), a = Math.max(e, x.value.tickClearance), o = (O.value - i * Math.max(0, t - 1)) / t >= 120;
|
|
2278
2283
|
return {
|
|
2279
|
-
horizontalGap: n.overlayMode === "multi-axis" && r &&
|
|
2280
|
-
hideSecondaryLabels: n.overlayMode !== "multi-axis" && r &&
|
|
2284
|
+
horizontalGap: n.overlayMode === "multi-axis" && r && C.value ? Math.max(e, T.value.left + T.value.right) : r && C.value ? S.value && o ? i : a : e,
|
|
2285
|
+
hideSecondaryLabels: n.overlayMode !== "multi-axis" && r && S.value && !o
|
|
2281
2286
|
};
|
|
2282
|
-
}),
|
|
2287
|
+
}), A = t(() => m.value.length > 0), j = t(() => O.value > 0 && s.value > 0), M = t(() => n.xLabel ?? `时间(${n.timeUnit})`), N = t(() => n.interactionMode), P = t(() => N.value !== "annotation"), F = t(() => {
|
|
2283
2288
|
let e = [];
|
|
2284
2289
|
return g.value.forEach((t) => {
|
|
2285
2290
|
t.visibleSeries.length && e.push(t.xDomain[0], t.xDomain[1]);
|
|
2286
2291
|
}), $(e);
|
|
2287
|
-
}),
|
|
2292
|
+
}), I = t(() => {
|
|
2288
2293
|
let e = n.initialXDomain;
|
|
2289
|
-
return e && Number.isFinite(e[0]) && Number.isFinite(e[1]) && e[0] !== e[1] ? dn(e[0] < e[1] ? e : [e[1], e[0]], n.xDomainStrategy, !0) : dn(
|
|
2290
|
-
}),
|
|
2294
|
+
return e && Number.isFinite(e[0]) && Number.isFinite(e[1]) && e[0] !== e[1] ? dn(e[0] < e[1] ? e : [e[1], e[0]], n.xDomainStrategy, !0) : dn(F.value, n.xDomainStrategy);
|
|
2295
|
+
}), L = (e) => {
|
|
2291
2296
|
let t = n.initialXDomains?.[e.series?.trackId ?? e.series?.id ?? e.id] ?? (e.series ? n.initialXDomains?.[e.series.id] : void 0) ?? n.initialXDomain;
|
|
2292
2297
|
return t && Number.isFinite(t[0]) && Number.isFinite(t[1]) && t[0] !== t[1] ? dn(t[0] < t[1] ? t : [t[1], t[0]], n.xDomainStrategy, !0) : dn($(e.seriesList.flatMap((e) => e.xDomain)), n.xDomainStrategy);
|
|
2293
|
-
},
|
|
2298
|
+
}, R = t(() => l.value.rescaleX(V(I.value, [0, O.value])).domain()), z = t(() => sn(O.value, s.value, h.value, n.displayMode, b.value.map(Boolean), k.value.horizontalGap).map((e, t) => ({
|
|
2294
2299
|
...e,
|
|
2295
|
-
series:
|
|
2296
|
-
}))),
|
|
2297
|
-
cells:
|
|
2300
|
+
series: b.value[t]
|
|
2301
|
+
}))), B = t(() => pn({
|
|
2302
|
+
cells: z.value,
|
|
2298
2303
|
grid: h.value,
|
|
2299
2304
|
displayMode: n.displayMode,
|
|
2300
2305
|
overlayMode: n.overlayMode,
|
|
2301
2306
|
independentTransforms: u.value,
|
|
2302
|
-
sharedZoomDomain:
|
|
2303
|
-
initialXDomain: n.initialXDomain ?
|
|
2307
|
+
sharedZoomDomain: R.value,
|
|
2308
|
+
initialXDomain: n.initialXDomain ? I.value : void 0,
|
|
2304
2309
|
initialXDomains: n.initialXDomains,
|
|
2305
2310
|
xDomainStrategy: n.xDomainStrategy,
|
|
2306
2311
|
fixedYDomain: n.yDomain,
|
|
@@ -2312,38 +2317,39 @@ function rr(e) {
|
|
|
2312
2317
|
timeUnit: n.timeUnit,
|
|
2313
2318
|
xAxisLabelFormatter: n.axes?.x?.labelFormatter,
|
|
2314
2319
|
yAxisSplitNumber: n.axes?.y?.splitNumber,
|
|
2320
|
+
yAxisNice: n.axes?.y?.nice,
|
|
2315
2321
|
rendering: _.value,
|
|
2316
|
-
hideSecondaryLabels: c.value ||
|
|
2317
|
-
yAxisLabelX:
|
|
2318
|
-
showCompactEmptyTracks: n.displayMode === "compact" &&
|
|
2319
|
-
})),
|
|
2322
|
+
hideSecondaryLabels: c.value || k.value.hideSecondaryLabels,
|
|
2323
|
+
yAxisLabelX: x.value.labelCenterX,
|
|
2324
|
+
showCompactEmptyTracks: n.displayMode === "compact" && A.value
|
|
2325
|
+
})), H = (e) => e.seriesList.map((t) => ({
|
|
2320
2326
|
...e,
|
|
2321
2327
|
series: t,
|
|
2322
2328
|
yScale: e.seriesPaths.find((e) => e.series.id === t.id)?.yScale ?? e.yScale
|
|
2323
|
-
})),
|
|
2329
|
+
})), U = (e, t) => e.seriesPaths.find((e) => e.series.id === t)?.yScale ?? e.yScale, ee = t(() => B.value.flatMap(H));
|
|
2324
2330
|
return {
|
|
2325
2331
|
chartSeries: m,
|
|
2326
2332
|
chartTracks: g,
|
|
2327
2333
|
gridOptions: h,
|
|
2328
2334
|
pageCount: v,
|
|
2329
2335
|
pagedTracks: y,
|
|
2330
|
-
resolvedChartLeftMargin:
|
|
2331
|
-
innerWidth:
|
|
2332
|
-
hasVisibleWaveformData:
|
|
2333
|
-
hasWaveformData:
|
|
2334
|
-
hasChartArea:
|
|
2335
|
-
resolvedXLabel:
|
|
2336
|
-
activeInteractionMode:
|
|
2337
|
-
isZoomMode:
|
|
2338
|
-
initialXDomain:
|
|
2339
|
-
sharedZoomDomain:
|
|
2340
|
-
resolveInitialTrackDomain:
|
|
2341
|
-
gridCells:
|
|
2342
|
-
trackLayouts:
|
|
2343
|
-
annotationLayoutsForTrack:
|
|
2344
|
-
resolveSeriesYScale:
|
|
2345
|
-
annotationTrackLayouts:
|
|
2346
|
-
renderedAnnotations: t(() => n.annotationsVisible ? ge(n.annotations,
|
|
2336
|
+
resolvedChartLeftMargin: E,
|
|
2337
|
+
innerWidth: O,
|
|
2338
|
+
hasVisibleWaveformData: C,
|
|
2339
|
+
hasWaveformData: A,
|
|
2340
|
+
hasChartArea: j,
|
|
2341
|
+
resolvedXLabel: M,
|
|
2342
|
+
activeInteractionMode: N,
|
|
2343
|
+
isZoomMode: P,
|
|
2344
|
+
initialXDomain: I,
|
|
2345
|
+
sharedZoomDomain: R,
|
|
2346
|
+
resolveInitialTrackDomain: L,
|
|
2347
|
+
gridCells: z,
|
|
2348
|
+
trackLayouts: B,
|
|
2349
|
+
annotationLayoutsForTrack: H,
|
|
2350
|
+
resolveSeriesYScale: U,
|
|
2351
|
+
annotationTrackLayouts: ee,
|
|
2352
|
+
renderedAnnotations: t(() => n.annotationsVisible ? ge(n.annotations, ee.value, O.value, s.value) : []),
|
|
2347
2353
|
editorSeries: t(() => {
|
|
2348
2354
|
let e = p.editorDraft.value?.annotation.seriesId, t = m.value.find((t) => t.id === e);
|
|
2349
2355
|
return t ? {
|
|
@@ -15,8 +15,8 @@ export declare function resolveYAxisSeriesGroups(track: DisplayTrack, overlayMod
|
|
|
15
15
|
export declare function axisTextMetrics(domain: [number, number], nice?: boolean, tickValues?: number[], unit?: string, tickCount?: number): {
|
|
16
16
|
tickTextWidth: number;
|
|
17
17
|
};
|
|
18
|
-
export declare function measureYAxisGroupClearance(group: YAxisSeriesGroup, tickCount?: number): number;
|
|
19
|
-
export declare function measureTrackYAxisClearance(track: DisplayTrack, overlayMode: WaveformOverlayMode, yDomain?: WaveformYDomain, yDomains?: Record<string, WaveformYDomain>, tickCount?: number): {
|
|
18
|
+
export declare function measureYAxisGroupClearance(group: YAxisSeriesGroup, tickCount?: number, nice?: boolean): number;
|
|
19
|
+
export declare function measureTrackYAxisClearance(track: DisplayTrack, overlayMode: WaveformOverlayMode, yDomain?: WaveformYDomain, yDomains?: Record<string, WaveformYDomain>, tickCount?: number, nice?: boolean): {
|
|
20
20
|
left: number;
|
|
21
21
|
right: number;
|
|
22
22
|
};
|
|
@@ -22,6 +22,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
22
22
|
timeUnit: 's' | 'ms';
|
|
23
23
|
xAxisLabelFormatter?: WaveformXAxisLabelFormatter;
|
|
24
24
|
yAxisSplitNumber?: number;
|
|
25
|
+
yAxisNice?: boolean;
|
|
25
26
|
rendering: ResolvedWaveformRenderingOptions;
|
|
26
27
|
hideSecondaryLabels: boolean;
|
|
27
28
|
yAxisLabelX: number;
|
|
@@ -152,6 +152,8 @@ export interface WaveformAxesOptions {
|
|
|
152
152
|
lineVisible?: boolean;
|
|
153
153
|
/** Target total number of equal Y-axis ticks, including endpoints. Defaults to 5. */
|
|
154
154
|
splitNumber?: number;
|
|
155
|
+
/** Expands Y-axis domains to equal, human-friendly intervals. Defaults to true. */
|
|
156
|
+
nice?: boolean;
|
|
155
157
|
};
|
|
156
158
|
}
|
|
157
159
|
/** Styling and visibility options for the horizontal zero-value reference line. */
|
package/package.json
CHANGED
|
@@ -133,9 +133,13 @@ export function axisTextMetrics(
|
|
|
133
133
|
}
|
|
134
134
|
}
|
|
135
135
|
|
|
136
|
-
export function measureYAxisGroupClearance(
|
|
136
|
+
export function measureYAxisGroupClearance(
|
|
137
|
+
group: YAxisSeriesGroup,
|
|
138
|
+
tickCount?: number,
|
|
139
|
+
nice = true,
|
|
140
|
+
): number {
|
|
137
141
|
return (
|
|
138
|
-
axisTextMetrics(group.domain,
|
|
142
|
+
axisTextMetrics(group.domain, nice, undefined, group.seriesList[0]?.unit, tickCount)
|
|
139
143
|
.tickTextWidth +
|
|
140
144
|
Y_AXIS_TICK_PADDING +
|
|
141
145
|
Y_AXIS_LABEL_GAP +
|
|
@@ -144,9 +148,13 @@ export function measureYAxisGroupClearance(group: YAxisSeriesGroup, tickCount?:
|
|
|
144
148
|
)
|
|
145
149
|
}
|
|
146
150
|
|
|
147
|
-
function measureYAxisGroupTickClearance(
|
|
151
|
+
function measureYAxisGroupTickClearance(
|
|
152
|
+
group: YAxisSeriesGroup,
|
|
153
|
+
tickCount?: number,
|
|
154
|
+
nice = true,
|
|
155
|
+
): number {
|
|
148
156
|
return (
|
|
149
|
-
axisTextMetrics(group.domain,
|
|
157
|
+
axisTextMetrics(group.domain, nice, undefined, group.seriesList[0]?.unit, tickCount)
|
|
150
158
|
.tickTextWidth +
|
|
151
159
|
Y_AXIS_TICK_PADDING +
|
|
152
160
|
Y_AXIS_OUTER_PADDING
|
|
@@ -159,13 +167,14 @@ export function measureTrackYAxisClearance(
|
|
|
159
167
|
yDomain?: WaveformYDomain,
|
|
160
168
|
yDomains?: Record<string, WaveformYDomain>,
|
|
161
169
|
tickCount?: number,
|
|
170
|
+
nice = true,
|
|
162
171
|
): { left: number; right: number } {
|
|
163
172
|
return resolveYAxisSeriesGroups(track, overlayMode, yDomain, yDomains).reduce(
|
|
164
173
|
(clearance, group) => {
|
|
165
174
|
clearance[group.side] +=
|
|
166
175
|
overlayMode === 'multi-axis' || track.visibleSeries.length === 1
|
|
167
|
-
? measureYAxisGroupClearance(group, tickCount)
|
|
168
|
-
: measureYAxisGroupTickClearance(group, tickCount)
|
|
176
|
+
? measureYAxisGroupClearance(group, tickCount, nice)
|
|
177
|
+
: measureYAxisGroupTickClearance(group, tickCount, nice)
|
|
169
178
|
return clearance
|
|
170
179
|
},
|
|
171
180
|
{ left: 0, right: 0 },
|
|
@@ -55,6 +55,7 @@ export interface BuildTrackLayoutsOptions {
|
|
|
55
55
|
timeUnit: 's' | 'ms'
|
|
56
56
|
xAxisLabelFormatter?: WaveformXAxisLabelFormatter
|
|
57
57
|
yAxisSplitNumber?: number
|
|
58
|
+
yAxisNice?: boolean
|
|
58
59
|
rendering: ResolvedWaveformRenderingOptions
|
|
59
60
|
hideSecondaryLabels: boolean
|
|
60
61
|
yAxisLabelX: number
|
|
@@ -119,7 +120,7 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
119
120
|
const tickCount = resolveYAxisTickCount(cell.plotHeight, options.yAxisSplitNumber)
|
|
120
121
|
const niceCount = Math.max(1, tickCount - 1)
|
|
121
122
|
const scale = scaleLinear(group.domain, [cell.plotHeight, 0])
|
|
122
|
-
scale.nice(niceCount)
|
|
123
|
+
if (options.yAxisNice !== false) scale.nice(niceCount)
|
|
123
124
|
const [axisStart, axisEnd] = scale.domain()
|
|
124
125
|
const majorTicks = Array.from(
|
|
125
126
|
{ length: tickCount },
|
|
@@ -158,7 +159,9 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
|
|
158
159
|
seriesList: group.seriesList,
|
|
159
160
|
}
|
|
160
161
|
})
|
|
161
|
-
const
|
|
162
|
+
const fallbackYScale = scaleLinear(displayTrack.yDomain, [cell.plotHeight, 0])
|
|
163
|
+
if (options.yAxisNice !== false) fallbackYScale.nice()
|
|
164
|
+
const yScale = yAxes[0]?.scale ?? fallbackYScale
|
|
162
165
|
const xMajorTicks = xScale.ticks(Math.max(2, Math.floor(cell.width / 100)))
|
|
163
166
|
const yMajorTicks = yAxes[0]?.majorTicks ?? []
|
|
164
167
|
const yAxisTickValues = yAxes[0]?.tickValues ?? []
|
|
@@ -112,6 +112,12 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
112
112
|
const pagedTracks = computed(() =>
|
|
113
113
|
paginateSeries(chartTracks.value, currentPage.value, gridOptions.value),
|
|
114
114
|
)
|
|
115
|
+
const layoutTracks = computed(() => {
|
|
116
|
+
const tracksWithSeries = pagedTracks.value.filter((track) => track.series.length > 0)
|
|
117
|
+
return gridOptions.value.fillIncompleteLastRow && tracksWithSeries.length > 0
|
|
118
|
+
? tracksWithSeries
|
|
119
|
+
: pagedTracks.value
|
|
120
|
+
})
|
|
115
121
|
const yAxisMetrics = computed(() => {
|
|
116
122
|
const axisText = chartTracks.value
|
|
117
123
|
.filter((track) => track.visibleSeries.length > 0)
|
|
@@ -122,7 +128,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
122
128
|
(group) =>
|
|
123
129
|
axisTextMetrics(
|
|
124
130
|
group.domain,
|
|
125
|
-
|
|
131
|
+
props.axes?.y?.nice !== false,
|
|
126
132
|
undefined,
|
|
127
133
|
group.seriesList[0]?.unit,
|
|
128
134
|
props.axes?.y?.splitNumber,
|
|
@@ -173,6 +179,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
173
179
|
props.yDomain,
|
|
174
180
|
props.yDomains,
|
|
175
181
|
props.axes?.y?.splitNumber,
|
|
182
|
+
props.axes?.y?.nice !== false,
|
|
176
183
|
)
|
|
177
184
|
return {
|
|
178
185
|
left: Math.max(maximum.left, clearance.left),
|
|
@@ -223,9 +230,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
223
230
|
const hasChartArea = computed(() => innerWidth.value > 0 && innerHeight.value > 0)
|
|
224
231
|
const resolvedXLabel = computed(() => props.xLabel ?? `时间(${props.timeUnit})`)
|
|
225
232
|
const activeInteractionMode = computed(() => props.interactionMode)
|
|
226
|
-
const isZoomMode = computed(
|
|
227
|
-
() => activeInteractionMode.value === 'zoom' || activeInteractionMode.value === undefined,
|
|
228
|
-
)
|
|
233
|
+
const isZoomMode = computed(() => activeInteractionMode.value !== 'annotation')
|
|
229
234
|
const sharedXDomain = computed(() => {
|
|
230
235
|
const values: number[] = []
|
|
231
236
|
chartTracks.value.forEach((track) => {
|
|
@@ -285,10 +290,10 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
285
290
|
innerHeight.value,
|
|
286
291
|
gridOptions.value,
|
|
287
292
|
props.displayMode,
|
|
288
|
-
|
|
293
|
+
layoutTracks.value.map(Boolean),
|
|
289
294
|
yAxisLayout.value.horizontalGap,
|
|
290
295
|
)
|
|
291
|
-
return cells.map((cell, index) => ({ ...cell, series:
|
|
296
|
+
return cells.map((cell, index) => ({ ...cell, series: layoutTracks.value[index] }))
|
|
292
297
|
})
|
|
293
298
|
const trackLayouts = computed<TrackLayout[]>(() =>
|
|
294
299
|
buildTrackLayouts({
|
|
@@ -315,6 +320,7 @@ export function useWaveformLayout(context: LayoutContext) {
|
|
|
315
320
|
timeUnit: props.timeUnit,
|
|
316
321
|
xAxisLabelFormatter: props.axes?.x?.labelFormatter,
|
|
317
322
|
yAxisSplitNumber: props.axes?.y?.splitNumber,
|
|
323
|
+
yAxisNice: props.axes?.y?.nice,
|
|
318
324
|
rendering: renderingOptions.value,
|
|
319
325
|
hideSecondaryLabels: isCleanView.value || yAxisLayout.value.hideSecondaryLabels,
|
|
320
326
|
yAxisLabelX: yAxisMetrics.value.labelCenterX,
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { zoomIdentity } from 'd3'
|
|
2
|
+
import { describe, expect, it } from 'vitest'
|
|
3
|
+
|
|
4
|
+
import { DEFAULT_WAVEFORM_RENDERING_OPTIONS } from '../../core'
|
|
5
|
+
import type { WaveformData } from '../../types'
|
|
6
|
+
import { buildTrackLayouts } from './trackLayoutBuilder'
|
|
7
|
+
import type { DisplaySeries, DisplayTrack } from './types'
|
|
8
|
+
import { prepareWaveformSeries } from './useWaveformData'
|
|
9
|
+
|
|
10
|
+
function displaySeries(id: string, domain: [number, number]): DisplaySeries {
|
|
11
|
+
return {
|
|
12
|
+
id,
|
|
13
|
+
name: id,
|
|
14
|
+
color: '#1677ff',
|
|
15
|
+
lineType: 'linear',
|
|
16
|
+
lineStyle: 'solid',
|
|
17
|
+
pointType: 'none',
|
|
18
|
+
errorBar: { visible: false, width: 1.5, capWidth: 8 },
|
|
19
|
+
points: [
|
|
20
|
+
{ x: 0, y: domain[0] },
|
|
21
|
+
{ x: 1, y: domain[1] },
|
|
22
|
+
],
|
|
23
|
+
xDomain: [0, 1],
|
|
24
|
+
yDomain: domain,
|
|
25
|
+
hasErrorPoints: false,
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function layout(
|
|
30
|
+
series: DisplaySeries[],
|
|
31
|
+
options: { overlayMode?: 'single-axis' | 'multi-axis'; fixedYDomain?: [number, number] } = {},
|
|
32
|
+
) {
|
|
33
|
+
const track: DisplayTrack = {
|
|
34
|
+
id: 'track',
|
|
35
|
+
series,
|
|
36
|
+
visibleSeries: series,
|
|
37
|
+
xDomain: [0, 1],
|
|
38
|
+
yDomain: [
|
|
39
|
+
Math.min(...series.map((item) => item.yDomain[0])),
|
|
40
|
+
Math.max(...series.map((item) => item.yDomain[1])),
|
|
41
|
+
],
|
|
42
|
+
}
|
|
43
|
+
return buildTrackLayouts({
|
|
44
|
+
cells: [
|
|
45
|
+
{
|
|
46
|
+
slotIndex: 0,
|
|
47
|
+
row: 0,
|
|
48
|
+
column: 0,
|
|
49
|
+
left: 0,
|
|
50
|
+
top: 0,
|
|
51
|
+
width: 120,
|
|
52
|
+
height: 100,
|
|
53
|
+
plotHeight: 100,
|
|
54
|
+
cellHeight: 130,
|
|
55
|
+
xAxisBand: 30,
|
|
56
|
+
series: track,
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
grid: {
|
|
60
|
+
rowCount: 1,
|
|
61
|
+
columnCount: 1,
|
|
62
|
+
showPagination: false,
|
|
63
|
+
fillIncompleteLastRow: false,
|
|
64
|
+
trackLines: {},
|
|
65
|
+
},
|
|
66
|
+
displayMode: 'independent',
|
|
67
|
+
overlayMode: options.overlayMode ?? 'single-axis',
|
|
68
|
+
independentTransforms: [zoomIdentity],
|
|
69
|
+
sharedZoomDomain: [0, 1],
|
|
70
|
+
fixedYDomain: options.fixedYDomain,
|
|
71
|
+
timeUnit: 'ms',
|
|
72
|
+
rendering: DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
73
|
+
hideSecondaryLabels: false,
|
|
74
|
+
yAxisLabelX: -50,
|
|
75
|
+
yAxisNice: false,
|
|
76
|
+
showCompactEmptyTracks: false,
|
|
77
|
+
})[0]!
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
describe('Y-axis nice domains', () => {
|
|
81
|
+
it('defaults to a nice domain and can preserve the actual data endpoints', () => {
|
|
82
|
+
const source = displaySeries('source', [3, 97])
|
|
83
|
+
const track: DisplayTrack = {
|
|
84
|
+
id: 'track',
|
|
85
|
+
series: [source],
|
|
86
|
+
visibleSeries: [source],
|
|
87
|
+
xDomain: [0, 1],
|
|
88
|
+
yDomain: [3, 97],
|
|
89
|
+
}
|
|
90
|
+
const defaults = buildTrackLayouts({
|
|
91
|
+
cells: [
|
|
92
|
+
{
|
|
93
|
+
slotIndex: 0,
|
|
94
|
+
row: 0,
|
|
95
|
+
column: 0,
|
|
96
|
+
left: 0,
|
|
97
|
+
top: 0,
|
|
98
|
+
width: 120,
|
|
99
|
+
height: 100,
|
|
100
|
+
plotHeight: 100,
|
|
101
|
+
cellHeight: 130,
|
|
102
|
+
xAxisBand: 30,
|
|
103
|
+
series: track,
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
grid: {
|
|
107
|
+
rowCount: 1,
|
|
108
|
+
columnCount: 1,
|
|
109
|
+
showPagination: false,
|
|
110
|
+
fillIncompleteLastRow: false,
|
|
111
|
+
trackLines: {},
|
|
112
|
+
},
|
|
113
|
+
displayMode: 'independent',
|
|
114
|
+
overlayMode: 'single-axis',
|
|
115
|
+
independentTransforms: [zoomIdentity],
|
|
116
|
+
sharedZoomDomain: [0, 1],
|
|
117
|
+
timeUnit: 'ms',
|
|
118
|
+
rendering: DEFAULT_WAVEFORM_RENDERING_OPTIONS,
|
|
119
|
+
hideSecondaryLabels: false,
|
|
120
|
+
yAxisLabelX: -50,
|
|
121
|
+
showCompactEmptyTracks: false,
|
|
122
|
+
})[0]!
|
|
123
|
+
|
|
124
|
+
expect(defaults.yScale.domain()).toEqual([0, 100])
|
|
125
|
+
expect(layout([source]).yScale.domain()).toEqual([3, 97])
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
it('merges visible single-axis series but keeps multi-axis series independent', () => {
|
|
129
|
+
const first = displaySeries('first', [3, 40])
|
|
130
|
+
const second = displaySeries('second', [10, 97])
|
|
131
|
+
|
|
132
|
+
expect(layout([first, second]).yScale.domain()).toEqual([3, 97])
|
|
133
|
+
expect(
|
|
134
|
+
layout([first, second], { overlayMode: 'multi-axis' }).yAxes.map((axis) =>
|
|
135
|
+
axis.scale.domain(),
|
|
136
|
+
),
|
|
137
|
+
).toEqual([
|
|
138
|
+
[3, 40],
|
|
139
|
+
[10, 97],
|
|
140
|
+
])
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('keeps explicit domains and constant-data safety ranges unchanged', () => {
|
|
144
|
+
expect(
|
|
145
|
+
layout([displaySeries('source', [3, 97])], { fixedYDomain: [3, 97] }).yScale.domain(),
|
|
146
|
+
).toEqual([3, 97])
|
|
147
|
+
expect(layout([displaySeries('constant', [9.5, 10.5])]).yScale.domain()).toEqual([9.5, 10.5])
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
it('continues to include visible error-bar bounds in the preserved domain', () => {
|
|
151
|
+
const input: WaveformData = {
|
|
152
|
+
kind: 'series',
|
|
153
|
+
series: [
|
|
154
|
+
{
|
|
155
|
+
id: 'error-series',
|
|
156
|
+
name: 'error series',
|
|
157
|
+
errorBar: { visible: true },
|
|
158
|
+
data: {
|
|
159
|
+
kind: 'points',
|
|
160
|
+
points: [
|
|
161
|
+
{ x: 0, y: 3, lowerError: 2 },
|
|
162
|
+
{ x: 1, y: 7, upperError: 4 },
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
],
|
|
167
|
+
}
|
|
168
|
+
const [prepared] = prepareWaveformSeries(input)
|
|
169
|
+
const errorSeries: DisplaySeries = { ...prepared!, color: prepared!.color ?? '#1677ff' }
|
|
170
|
+
|
|
171
|
+
expect(prepared?.yDomain).toEqual([1, 11])
|
|
172
|
+
expect(layout([errorSeries]).yScale.domain()).toEqual([1, 11])
|
|
173
|
+
})
|
|
174
|
+
})
|