feat(chart): add multi-axis overlay controls
This commit is contained in:
@@ -28,9 +28,19 @@ describe('waveform grid helpers', () => {
|
||||
|
||||
it('resolves mode-specific gaps and bottom cells', () => {
|
||||
const options = normalizeGridOptions({ rowCount: 2, columnCount: 2 })
|
||||
const separated = resolveGridCellGeometry(400, 200, options, 'separated', [true, true, true, true])
|
||||
const separated = resolveGridCellGeometry(400, 200, options, 'separated', [
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
true,
|
||||
])
|
||||
const compact = resolveGridCellGeometry(400, 200, options, 'compact', [true, true, true, true])
|
||||
const independent = resolveGridCellGeometry(400, 200, options, 'independent', [true, true, false, false])
|
||||
const independent = resolveGridCellGeometry(400, 200, options, 'independent', [
|
||||
true,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
])
|
||||
expect(separated[1].left).toBeGreaterThan(separated[0].left + separated[0].width)
|
||||
expect(separated[2].top).toBe(separated[0].plotHeight + 16)
|
||||
expect(separated[2].xAxisBand).toBe(X_AXIS_BAND)
|
||||
@@ -50,7 +60,14 @@ describe('waveform grid helpers', () => {
|
||||
|
||||
it('accepts an independent horizontal gap without changing vertical spacing', () => {
|
||||
const options = normalizeGridOptions({ rowCount: 2, columnCount: 2 })
|
||||
const cells = resolveGridCellGeometry(400, 200, options, 'independent', [true, true, true, true], 64)
|
||||
const cells = resolveGridCellGeometry(
|
||||
400,
|
||||
200,
|
||||
options,
|
||||
'independent',
|
||||
[true, true, true, true],
|
||||
64,
|
||||
)
|
||||
|
||||
expect(cells[0].width).toBe(168)
|
||||
expect(cells[1].left).toBe(232)
|
||||
|
||||
@@ -2,7 +2,7 @@ import type { WaveformDisplayMode } from '../../types'
|
||||
|
||||
export const GRID_MIN_COUNT = 1
|
||||
export const GRID_MAX_COUNT = 10
|
||||
export const X_AXIS_BAND = 16
|
||||
export const X_AXIS_BAND = 30
|
||||
|
||||
export interface WaveformGridOptions {
|
||||
rowCount?: number
|
||||
@@ -76,7 +76,9 @@ export function resolveGridCellGeometry(
|
||||
horizontalGap?: number,
|
||||
): GridCellGeometry[] {
|
||||
const defaultGap = getGridGap(displayMode)
|
||||
const columnGap = Number.isFinite(horizontalGap) ? Math.max(0, horizontalGap as number) : defaultGap
|
||||
const columnGap = Number.isFinite(horizontalGap)
|
||||
? Math.max(0, horizontalGap as number)
|
||||
: defaultGap
|
||||
const totalHorizontalGap = Math.max(0, options.columnCount - 1) * columnGap
|
||||
const axisRows = new Set<number>()
|
||||
if (displayMode === 'independent') {
|
||||
@@ -99,7 +101,10 @@ export function resolveGridCellGeometry(
|
||||
const totalVerticalGap = Math.max(0, options.rowCount - 1) * defaultGap
|
||||
const totalAxisBand = axisRows.size * X_AXIS_BAND
|
||||
const width = Math.max(1, (innerWidth - totalHorizontalGap) / options.columnCount)
|
||||
const plotHeight = Math.max(1, (innerHeight - totalVerticalGap - totalAxisBand) / options.rowCount)
|
||||
const plotHeight = Math.max(
|
||||
1,
|
||||
(innerHeight - totalVerticalGap - totalAxisBand) / options.rowCount,
|
||||
)
|
||||
|
||||
return Array.from({ length: getPageSize(options) }, (_, slotIndex) => {
|
||||
const row = Math.floor(slotIndex / options.columnCount)
|
||||
|
||||
98
src/components/core/layout.test.ts
Normal file
98
src/components/core/layout.test.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import type { DisplaySeries, DisplayTrack } from './types'
|
||||
import { buildYAxisSeriesGroups, MAX_MULTI_Y_AXIS_COUNT } from './layout'
|
||||
|
||||
function series(id: string, minimum: number, maximum: number): DisplaySeries {
|
||||
return {
|
||||
id,
|
||||
name: id,
|
||||
color: '#1677ff',
|
||||
points: [
|
||||
{ x: 0, y: minimum },
|
||||
{ x: 1, y: maximum },
|
||||
],
|
||||
xDomain: [0, 1],
|
||||
yDomain: [minimum, maximum],
|
||||
}
|
||||
}
|
||||
|
||||
function track(seriesList: DisplaySeries[]): DisplayTrack {
|
||||
return {
|
||||
id: 'track',
|
||||
series: seriesList,
|
||||
xDomain: [0, 1],
|
||||
yDomain: [0, 50],
|
||||
}
|
||||
}
|
||||
|
||||
describe('multi-value Y-axis grouping', () => {
|
||||
it('keeps every overlaid series on one axis in single-axis mode', () => {
|
||||
const groups = buildYAxisSeriesGroups(
|
||||
track([series('a', 0, 1), series('b', 10, 20)]),
|
||||
'single-axis',
|
||||
)
|
||||
|
||||
expect(groups).toHaveLength(1)
|
||||
expect(groups[0]?.seriesList.map((item) => item.id)).toEqual(['a', 'b'])
|
||||
expect(groups[0]?.domain).toEqual([0, 50])
|
||||
})
|
||||
|
||||
it('uses the reference left-right axis order and merges overflow into axis four', () => {
|
||||
const groups = buildYAxisSeriesGroups(
|
||||
track([
|
||||
series('a', 0, 1),
|
||||
series('b', 10, 11),
|
||||
series('c', 20, 21),
|
||||
series('d', 30, 31),
|
||||
series('e', 40, 50),
|
||||
]),
|
||||
'multi-axis',
|
||||
)
|
||||
|
||||
expect(groups).toHaveLength(MAX_MULTI_Y_AXIS_COUNT)
|
||||
expect(groups.map((group) => group.side)).toEqual(['left', 'left', 'right', 'right'])
|
||||
expect(groups.map((group) => group.seriesList.map((item) => item.id))).toEqual([
|
||||
['a'],
|
||||
['b'],
|
||||
['c'],
|
||||
['d', 'e'],
|
||||
])
|
||||
expect(groups[3]?.domain[0]).toBeLessThanOrEqual(30)
|
||||
expect(groups[3]?.domain[1]).toBeGreaterThanOrEqual(50)
|
||||
})
|
||||
|
||||
it('derives merged multi-axis domains from precomputed series domains', () => {
|
||||
const first = series('a', -20, -10)
|
||||
const second = series('b', 40, 60)
|
||||
first.points = [{ x: 0, y: -15 }]
|
||||
second.points = [{ x: 0, y: 50 }]
|
||||
|
||||
const groups = buildYAxisSeriesGroups(
|
||||
track([
|
||||
series('left', 0, 1),
|
||||
series('middle', 10, 11),
|
||||
series('right', 20, 21),
|
||||
first,
|
||||
second,
|
||||
]),
|
||||
'multi-axis',
|
||||
)
|
||||
|
||||
expect(groups[3]?.domain[0]).toBeLessThanOrEqual(-20)
|
||||
expect(groups[3]?.domain[1]).toBeGreaterThanOrEqual(60)
|
||||
})
|
||||
|
||||
it('places two and three axes on the expected sides', () => {
|
||||
const source = [series('a', 0, 1), series('b', 10, 11), series('c', 20, 21)]
|
||||
|
||||
expect(
|
||||
buildYAxisSeriesGroups(track(source.slice(0, 2)), 'multi-axis').map((g) => g.side),
|
||||
).toEqual(['left', 'right'])
|
||||
expect(buildYAxisSeriesGroups(track(source), 'multi-axis').map((g) => g.side)).toEqual([
|
||||
'left',
|
||||
'right',
|
||||
'right',
|
||||
])
|
||||
})
|
||||
})
|
||||
@@ -1,14 +1,149 @@
|
||||
import { line, scaleLinear, zoomIdentity, type ZoomTransform } from 'd3'
|
||||
|
||||
import { selectRenderablePoints, type ResolvedWaveformRenderingOptions } from '../../core'
|
||||
import type { WaveformDisplayMode, WaveformPoint } from '../../types'
|
||||
import { buildMinorTicks, formatEndpointTime } from '../../utils'
|
||||
import type { WaveformDisplayMode, WaveformOverlayMode, WaveformPoint } from '../../types'
|
||||
import {
|
||||
buildMinorTicks,
|
||||
formatAxisTimeExponent,
|
||||
formatEndpointTime,
|
||||
formatScientificAxisExponent,
|
||||
formatScientificAxisLabel,
|
||||
paddedDomain,
|
||||
} from '../../utils'
|
||||
import {
|
||||
getBottomRowCellIndexes,
|
||||
type GridCellGeometry,
|
||||
type NormalizedWaveformGridOptions,
|
||||
} from './grid'
|
||||
import type { DisplaySeries, DisplayTrack, TrackLayout } from './types'
|
||||
import type { DisplaySeries, DisplayTrack, TrackLayout, WaveformYAxisLayout } from './types'
|
||||
|
||||
export const MAX_MULTI_Y_AXIS_COUNT = 4
|
||||
const Y_AXIS_CHARACTER_WIDTH = 7
|
||||
const Y_AXIS_TICK_PADDING = 7
|
||||
const Y_AXIS_OUTER_PADDING = 4
|
||||
const Y_AXIS_LABEL_GAP = 6
|
||||
const Y_AXIS_LABEL_BAND_WIDTH = 24
|
||||
const Y_AXIS_EXPONENT_GAP = 4
|
||||
|
||||
interface YAxisSeriesGroup {
|
||||
index: number
|
||||
side: 'left' | 'right'
|
||||
seriesList: DisplaySeries[]
|
||||
domain: [number, number]
|
||||
}
|
||||
|
||||
function resolveAxisSides(axisCount: number): Array<'left' | 'right'> {
|
||||
if (axisCount >= 4) return ['left', 'left', 'right', 'right']
|
||||
if (axisCount === 3) return ['left', 'right', 'right']
|
||||
if (axisCount === 2) return ['left', 'right']
|
||||
return ['left']
|
||||
}
|
||||
|
||||
// 缓存 axis groups 计算结果,避免重复计算
|
||||
const yAxisGroupsCache = new WeakMap<DisplayTrack, Map<WaveformOverlayMode, YAxisSeriesGroup[]>>()
|
||||
|
||||
export function buildYAxisSeriesGroups(
|
||||
track: DisplayTrack,
|
||||
overlayMode: WaveformOverlayMode,
|
||||
): YAxisSeriesGroup[] {
|
||||
// 检查缓存
|
||||
let trackCache = yAxisGroupsCache.get(track)
|
||||
if (!trackCache) {
|
||||
trackCache = new Map()
|
||||
yAxisGroupsCache.set(track, trackCache)
|
||||
}
|
||||
|
||||
const cached = trackCache.get(overlayMode)
|
||||
if (cached) return cached
|
||||
const axisCount =
|
||||
overlayMode === 'multi-axis'
|
||||
? Math.min(track.series.length, MAX_MULTI_Y_AXIS_COUNT)
|
||||
: Math.min(track.series.length, 1)
|
||||
const sides = resolveAxisSides(axisCount)
|
||||
const grouped = Array.from({ length: axisCount }, (_, index) => ({
|
||||
index,
|
||||
side: sides[index],
|
||||
seriesList: [] as DisplaySeries[],
|
||||
domain: [0, 1] as [number, number],
|
||||
}))
|
||||
|
||||
track.series.forEach((series, index) => {
|
||||
grouped[Math.min(index, axisCount - 1)]?.seriesList.push(series)
|
||||
})
|
||||
grouped.forEach((group) => {
|
||||
if (overlayMode === 'single-axis') {
|
||||
group.domain = track.yDomain
|
||||
} else {
|
||||
const yDomainValues = group.seriesList.flatMap((series) => series.yDomain)
|
||||
group.domain = yDomainValues.length > 0 ? paddedDomain(yDomainValues) : track.yDomain
|
||||
}
|
||||
})
|
||||
|
||||
// 缓存结果
|
||||
trackCache.set(overlayMode, grouped)
|
||||
return grouped
|
||||
}
|
||||
|
||||
function axisTextMetrics(domain: [number, number]): {
|
||||
exponentLabel: string | null
|
||||
exponentWidth: number
|
||||
tickTextWidth: number
|
||||
} {
|
||||
const scale = scaleLinear(domain, [1, 0]).nice()
|
||||
const [axisMin, axisMax] = scale.domain()
|
||||
const values = scale.ticks(10)
|
||||
const maximumTickCharacters = Math.max(
|
||||
1,
|
||||
...values.map((value) => formatScientificAxisLabel(value, { axisMin, axisMax }).length),
|
||||
)
|
||||
const exponentLabel = formatScientificAxisExponent(axisMin, axisMax)
|
||||
return {
|
||||
exponentLabel,
|
||||
exponentWidth: exponentLabel ? exponentLabel.length * Y_AXIS_CHARACTER_WIDTH : 0,
|
||||
tickTextWidth: maximumTickCharacters * Y_AXIS_CHARACTER_WIDTH,
|
||||
}
|
||||
}
|
||||
|
||||
function axisExponentClearance(domain: [number, number]): number {
|
||||
const { exponentLabel, exponentWidth } = axisTextMetrics(domain)
|
||||
return exponentLabel ? exponentWidth + Y_AXIS_EXPONENT_GAP : 0
|
||||
}
|
||||
|
||||
export function measureYAxisGroupClearance(group: YAxisSeriesGroup): number {
|
||||
return (
|
||||
axisTextMetrics(group.domain).tickTextWidth +
|
||||
axisExponentClearance(group.domain) +
|
||||
Y_AXIS_TICK_PADDING +
|
||||
Y_AXIS_LABEL_GAP +
|
||||
Y_AXIS_LABEL_BAND_WIDTH +
|
||||
Y_AXIS_OUTER_PADDING
|
||||
)
|
||||
}
|
||||
|
||||
function measureYAxisGroupTickClearance(group: YAxisSeriesGroup): number {
|
||||
return (
|
||||
axisTextMetrics(group.domain).tickTextWidth +
|
||||
axisExponentClearance(group.domain) +
|
||||
Y_AXIS_TICK_PADDING +
|
||||
Y_AXIS_OUTER_PADDING
|
||||
)
|
||||
}
|
||||
|
||||
export function measureTrackYAxisClearance(
|
||||
track: DisplayTrack,
|
||||
overlayMode: WaveformOverlayMode,
|
||||
): { left: number; right: number } {
|
||||
return buildYAxisSeriesGroups(track, overlayMode).reduce(
|
||||
(clearance, group) => {
|
||||
clearance[group.side] +=
|
||||
overlayMode === 'multi-axis' || track.series.length === 1
|
||||
? measureYAxisGroupClearance(group)
|
||||
: measureYAxisGroupTickClearance(group)
|
||||
return clearance
|
||||
},
|
||||
{ left: 0, right: 0 },
|
||||
)
|
||||
}
|
||||
|
||||
interface SeriesGridCell extends GridCellGeometry {
|
||||
series?: DisplayTrack
|
||||
@@ -18,6 +153,7 @@ export interface BuildTrackLayoutsOptions {
|
||||
cells: SeriesGridCell[]
|
||||
grid: NormalizedWaveformGridOptions
|
||||
displayMode: WaveformDisplayMode
|
||||
overlayMode: WaveformOverlayMode
|
||||
independentTransforms: ZoomTransform[]
|
||||
sharedZoomDomain: [number, number]
|
||||
timeUnit: 's' | 'ms'
|
||||
@@ -58,22 +194,67 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
||||
? (options.independentTransforms[index] ?? zoomIdentity)
|
||||
: zoomIdentity
|
||||
const xScale = transform.rescaleX(baseXScale)
|
||||
const yScale = scaleLinear(displayTrack.yDomain, [cell.plotHeight, 0]).nice()
|
||||
const yAxisGroups = buildYAxisSeriesGroups(displayTrack, options.overlayMode)
|
||||
const sideOffsets = { left: 0, right: 0 }
|
||||
const yAxes: WaveformYAxisLayout[] = yAxisGroups.map((group) => {
|
||||
const scale = scaleLinear(group.domain, [cell.plotHeight, 0]).nice()
|
||||
const majorTicks = scale.ticks(Math.max(2, Math.floor(cell.plotHeight / 55)))
|
||||
const [axisStart, axisEnd] = scale.domain()
|
||||
const showAxisEnd = options.displayMode !== 'compact' || cell.row === 0
|
||||
const visibleMajorTicks = showAxisEnd
|
||||
? majorTicks
|
||||
: majorTicks.filter((tick) => tick !== axisEnd)
|
||||
const tickValues = Array.from(
|
||||
new Set([axisStart, ...visibleMajorTicks, ...(showAxisEnd ? [axisEnd] : [])]),
|
||||
)
|
||||
const { exponentLabel, exponentWidth, tickTextWidth } = axisTextMetrics(group.domain)
|
||||
const exponentClearance = exponentLabel ? exponentWidth + Y_AXIS_EXPONENT_GAP : 0
|
||||
const clearance =
|
||||
tickTextWidth +
|
||||
Y_AXIS_TICK_PADDING +
|
||||
exponentClearance +
|
||||
Y_AXIS_LABEL_GAP +
|
||||
Y_AXIS_LABEL_BAND_WIDTH +
|
||||
Y_AXIS_OUTER_PADDING
|
||||
const x = group.side === 'left' ? -sideOffsets.left : cell.width + sideOffsets.right
|
||||
const exponentX =
|
||||
x +
|
||||
(group.side === 'left'
|
||||
? -(Y_AXIS_TICK_PADDING + tickTextWidth + exponentClearance)
|
||||
: Y_AXIS_TICK_PADDING + tickTextWidth + exponentClearance)
|
||||
const labelDistance =
|
||||
tickTextWidth +
|
||||
Y_AXIS_TICK_PADDING +
|
||||
exponentClearance +
|
||||
exponentWidth +
|
||||
Y_AXIS_LABEL_GAP +
|
||||
Y_AXIS_LABEL_BAND_WIDTH / 2
|
||||
const labelX = x + (group.side === 'left' ? -labelDistance : labelDistance)
|
||||
sideOffsets[group.side] += clearance
|
||||
return {
|
||||
index: group.index,
|
||||
side: group.side,
|
||||
x,
|
||||
labelX,
|
||||
exponentX,
|
||||
exponentLabel,
|
||||
scale,
|
||||
majorTicks,
|
||||
minorTicks: buildMinorTicks(majorTicks),
|
||||
tickValues,
|
||||
seriesList: group.seriesList,
|
||||
}
|
||||
})
|
||||
const yScale = yAxes[0]?.scale ?? scaleLinear(displayTrack.yDomain, [cell.plotHeight, 0]).nice()
|
||||
const xMajorTicks = xScale.ticks(Math.max(2, Math.floor(cell.width / 100)))
|
||||
const yMajorTicks = yScale.ticks(Math.max(2, Math.floor(cell.plotHeight / 55)))
|
||||
const [yAxisStart, yAxisEnd] = yScale.domain()
|
||||
const showYAxisEnd = options.displayMode !== 'compact' || cell.row === 0
|
||||
const visibleYMajorTicks = showYAxisEnd
|
||||
? yMajorTicks
|
||||
: yMajorTicks.filter((tick) => tick !== yAxisEnd)
|
||||
const yAxisTickValues = Array.from(
|
||||
new Set([yAxisStart, ...visibleYMajorTicks, ...(showYAxisEnd ? [yAxisEnd] : [])]),
|
||||
)
|
||||
const yMajorTicks = yAxes[0]?.majorTicks ?? []
|
||||
const yAxisTickValues = yAxes[0]?.tickValues ?? []
|
||||
const domain = xScale.domain() as [number, number]
|
||||
const endpointLabels = {
|
||||
start: formatEndpointTime(domain[0], domain, options.timeUnit),
|
||||
end: formatEndpointTime(domain[1], domain, options.timeUnit),
|
||||
}
|
||||
const xAxisExponent = formatAxisTimeExponent(domain, options.timeUnit)
|
||||
const leftClearance = endpointLabels.start.length * 7 + 10
|
||||
const rightClearance = endpointLabels.end.length * 7 + 10
|
||||
const xAxisTickValues = xMajorTicks.filter((tick) => {
|
||||
@@ -81,6 +262,10 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
||||
return position > leftClearance && position < cell.width - rightClearance
|
||||
})
|
||||
const seriesPaths = displayTrack.series.map((trackSeries) => {
|
||||
const yAxis = yAxes.find((axis) =>
|
||||
axis.seriesList.some((series) => series.id === trackSeries.id),
|
||||
)
|
||||
const seriesYScale = yAxis?.scale ?? yScale
|
||||
const renderPoints = selectRenderablePoints(
|
||||
trackSeries.points,
|
||||
domain,
|
||||
@@ -93,7 +278,9 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
||||
? null
|
||||
: line<WaveformPoint>()
|
||||
.x((point) => xScale(point.x))
|
||||
.y((point) => yScale(point.y))(renderPoints),
|
||||
.y((point) => seriesYScale(point.y))(renderPoints),
|
||||
yScale: seriesYScale,
|
||||
yAxisIndex: yAxis?.index ?? 0,
|
||||
}
|
||||
})
|
||||
|
||||
@@ -111,13 +298,15 @@ export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayou
|
||||
height: cell.plotHeight,
|
||||
xScale,
|
||||
yScale,
|
||||
yAxes,
|
||||
xMajorTicks,
|
||||
xMinorTicks: buildMinorTicks(xMajorTicks, 5, domain),
|
||||
yMajorTicks,
|
||||
yMinorTicks: buildMinorTicks(yMajorTicks),
|
||||
yMinorTicks: yAxes[0]?.minorTicks ?? [],
|
||||
yAxisTickValues,
|
||||
xAxisTickValues,
|
||||
endpointLabels,
|
||||
xAxisExponent,
|
||||
path: seriesPaths[0]?.path ?? null,
|
||||
seriesPaths,
|
||||
showXAxis:
|
||||
|
||||
@@ -60,10 +60,8 @@ export function calculateRotatedTitleLayout({
|
||||
}
|
||||
|
||||
const maximumVisualHeight = TITLE_AREA_MAX_HEIGHT - TITLE_AREA_VERTICAL_PADDING
|
||||
const naturalVisualWidth =
|
||||
safeNaturalWidth * absoluteCosine + safeNaturalHeight * absoluteSine
|
||||
const naturalVisualHeight =
|
||||
safeNaturalWidth * absoluteSine + safeNaturalHeight * absoluteCosine
|
||||
const naturalVisualWidth = safeNaturalWidth * absoluteCosine + safeNaturalHeight * absoluteSine
|
||||
const naturalVisualHeight = safeNaturalWidth * absoluteSine + safeNaturalHeight * absoluteCosine
|
||||
const scale = Math.min(
|
||||
1,
|
||||
safeAvailableWidth / naturalVisualWidth,
|
||||
|
||||
@@ -25,6 +25,22 @@ export interface DisplayTrack {
|
||||
export interface TrackSeriesPath {
|
||||
series: DisplaySeries
|
||||
path: string | null
|
||||
yScale: ScaleLinear<number, number>
|
||||
yAxisIndex: number
|
||||
}
|
||||
|
||||
export interface WaveformYAxisLayout {
|
||||
index: number
|
||||
side: 'left' | 'right'
|
||||
x: number
|
||||
labelX: number
|
||||
exponentX: number
|
||||
exponentLabel: string | null
|
||||
scale: ScaleLinear<number, number>
|
||||
majorTicks: number[]
|
||||
minorTicks: number[]
|
||||
tickValues: number[]
|
||||
seriesList: DisplaySeries[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,6 +68,7 @@ export interface TrackLayout {
|
||||
height: number
|
||||
xScale: ScaleLinear<number, number>
|
||||
yScale: ScaleLinear<number, number>
|
||||
yAxes: WaveformYAxisLayout[]
|
||||
xMajorTicks: number[]
|
||||
xMinorTicks: number[]
|
||||
yMajorTicks: number[]
|
||||
@@ -59,6 +76,7 @@ export interface TrackLayout {
|
||||
yAxisTickValues: number[]
|
||||
xAxisTickValues: number[]
|
||||
endpointLabels: { start: string; end: string }
|
||||
xAxisExponent: string | null
|
||||
path: string | null
|
||||
seriesPaths: TrackSeriesPath[]
|
||||
showXAxis: boolean
|
||||
|
||||
@@ -34,10 +34,7 @@ export function prepareWaveformSeries(data: WaveformData): PreparedWaveformSerie
|
||||
}))
|
||||
}
|
||||
|
||||
export function usePreparedWaveformSeries(
|
||||
data: () => WaveformData,
|
||||
onDataChange: () => void,
|
||||
) {
|
||||
export function usePreparedWaveformSeries(data: () => WaveformData, onDataChange: () => void) {
|
||||
const preparedSeries = shallowRef<PreparedWaveformSeries[]>(prepareWaveformSeries(data()))
|
||||
watch(data, (nextData) => {
|
||||
preparedSeries.value = prepareWaveformSeries(nextData)
|
||||
|
||||
Reference in New Issue
Block a user