2026-07-20 17:57:52 +08:00
|
|
|
import {
|
|
|
|
|
curveStep,
|
|
|
|
|
curveStepAfter,
|
|
|
|
|
curveStepBefore,
|
|
|
|
|
line,
|
|
|
|
|
scaleLinear,
|
|
|
|
|
zoomIdentity,
|
|
|
|
|
type ZoomTransform,
|
|
|
|
|
} from 'd3'
|
2026-07-20 10:21:30 +08:00
|
|
|
|
2026-07-20 17:57:52 +08:00
|
|
|
import {
|
2026-07-22 10:56:25 +08:00
|
|
|
selectSeriesRenderPoints,
|
2026-07-20 17:57:52 +08:00
|
|
|
type ResolvedWaveformRenderingOptions,
|
2026-07-22 10:56:25 +08:00
|
|
|
} from '../../core/rendering'
|
2026-07-20 15:35:19 +08:00
|
|
|
import type { WaveformDisplayMode, WaveformOverlayMode, WaveformPoint } from '../../types'
|
|
|
|
|
import {
|
|
|
|
|
buildMinorTicks,
|
|
|
|
|
formatAxisTimeExponent,
|
|
|
|
|
formatEndpointTime,
|
|
|
|
|
formatScientificAxisExponent,
|
|
|
|
|
formatScientificAxisLabel,
|
|
|
|
|
paddedDomain,
|
|
|
|
|
} from '../../utils'
|
2026-07-20 10:21:30 +08:00
|
|
|
import {
|
|
|
|
|
getBottomRowCellIndexes,
|
|
|
|
|
type GridCellGeometry,
|
|
|
|
|
type NormalizedWaveformGridOptions,
|
|
|
|
|
} from './grid'
|
2026-07-20 15:35:19 +08:00
|
|
|
import type { DisplaySeries, DisplayTrack, TrackLayout, WaveformYAxisLayout } from './types'
|
2026-07-22 16:36:48 +08:00
|
|
|
import { MAX_MULTI_Y_AXIS_COUNT, Y_AXIS_EXPONENT_GAP } from './constants'
|
|
|
|
|
|
|
|
|
|
// 导出常量供外部使用
|
|
|
|
|
export { MAX_MULTI_Y_AXIS_COUNT, Y_AXIS_EXPONENT_GAP } from './constants'
|
2026-07-20 15:35:19 +08:00
|
|
|
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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']
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-22 16:36:48 +08:00
|
|
|
// 使用 WeakMap 进行缓存优化,避免手动清理
|
|
|
|
|
const yAxisGroupsCache = new WeakMap<DisplayTrack, Map<WaveformOverlayMode, YAxisSeriesGroup[]>>()
|
2026-07-20 15:35:19 +08:00
|
|
|
|
|
|
|
|
export function buildYAxisSeriesGroups(
|
|
|
|
|
track: DisplayTrack,
|
|
|
|
|
overlayMode: WaveformOverlayMode,
|
|
|
|
|
): YAxisSeriesGroup[] {
|
2026-07-22 16:36:48 +08:00
|
|
|
let trackCache = yAxisGroupsCache.get(track)
|
2026-07-20 15:35:19 +08:00
|
|
|
if (!trackCache) {
|
|
|
|
|
trackCache = new Map()
|
2026-07-22 16:36:48 +08:00
|
|
|
yAxisGroupsCache.set(track, trackCache)
|
2026-07-20 15:35:19 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const cached = trackCache.get(overlayMode)
|
|
|
|
|
if (cached) return cached
|
|
|
|
|
const axisCount =
|
|
|
|
|
overlayMode === 'multi-axis'
|
2026-07-20 17:57:52 +08:00
|
|
|
? Math.min(track.visibleSeries.length, MAX_MULTI_Y_AXIS_COUNT)
|
|
|
|
|
: Math.min(track.visibleSeries.length, 1)
|
2026-07-20 15:35:19 +08:00
|
|
|
const sides = resolveAxisSides(axisCount)
|
|
|
|
|
const grouped = Array.from({ length: axisCount }, (_, index) => ({
|
|
|
|
|
index,
|
|
|
|
|
side: sides[index],
|
|
|
|
|
seriesList: [] as DisplaySeries[],
|
|
|
|
|
domain: [0, 1] as [number, number],
|
|
|
|
|
}))
|
|
|
|
|
|
2026-07-20 17:57:52 +08:00
|
|
|
track.visibleSeries.forEach((series, index) => {
|
2026-07-20 15:35:19 +08:00
|
|
|
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] +=
|
2026-07-20 17:57:52 +08:00
|
|
|
overlayMode === 'multi-axis' || track.visibleSeries.length === 1
|
2026-07-20 15:35:19 +08:00
|
|
|
? measureYAxisGroupClearance(group)
|
|
|
|
|
: measureYAxisGroupTickClearance(group)
|
|
|
|
|
return clearance
|
|
|
|
|
},
|
|
|
|
|
{ left: 0, right: 0 },
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-07-20 10:21:30 +08:00
|
|
|
|
|
|
|
|
interface SeriesGridCell extends GridCellGeometry {
|
2026-07-20 14:07:43 +08:00
|
|
|
series?: DisplayTrack
|
2026-07-20 10:21:30 +08:00
|
|
|
}
|
|
|
|
|
|
2026-07-22 16:36:48 +08:00
|
|
|
type PositionedTrack = Pick<TrackLayout, 'left' | 'top' | 'width' | 'height'>
|
|
|
|
|
|
|
|
|
|
export function findClosestTrackAtPointer<T extends PositionedTrack>(
|
|
|
|
|
tracks: readonly T[],
|
|
|
|
|
pointerX: number,
|
|
|
|
|
pointerY: number,
|
|
|
|
|
): T | undefined {
|
|
|
|
|
const distanceToTrack = (track: T) => {
|
|
|
|
|
const xDistance =
|
|
|
|
|
pointerX < track.left
|
|
|
|
|
? track.left - pointerX
|
|
|
|
|
: pointerX > track.left + track.width
|
|
|
|
|
? pointerX - track.left - track.width
|
|
|
|
|
: 0
|
|
|
|
|
return pointerY < track.top
|
|
|
|
|
? track.top - pointerY
|
|
|
|
|
: pointerY > track.top + track.height
|
|
|
|
|
? pointerY - (track.top + track.height)
|
|
|
|
|
: xDistance
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let closestTrack = tracks[0]
|
|
|
|
|
if (!closestTrack) return undefined
|
|
|
|
|
let closestDistance = distanceToTrack(closestTrack)
|
|
|
|
|
for (let index = 1; index < tracks.length; index += 1) {
|
|
|
|
|
const candidate = tracks[index]!
|
|
|
|
|
const distance = distanceToTrack(candidate)
|
|
|
|
|
if (distance < closestDistance) {
|
|
|
|
|
closestTrack = candidate
|
|
|
|
|
closestDistance = distance
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if (distance === closestDistance) {
|
|
|
|
|
const centerDistance = Math.abs(pointerY - (candidate.top + candidate.height / 2))
|
|
|
|
|
const closestCenterDistance = Math.abs(
|
|
|
|
|
pointerY - (closestTrack.top + closestTrack.height / 2),
|
|
|
|
|
)
|
|
|
|
|
if (centerDistance < closestCenterDistance) closestTrack = candidate
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return closestTrack
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-20 10:21:30 +08:00
|
|
|
export interface BuildTrackLayoutsOptions {
|
|
|
|
|
cells: SeriesGridCell[]
|
|
|
|
|
grid: NormalizedWaveformGridOptions
|
|
|
|
|
displayMode: WaveformDisplayMode
|
2026-07-20 15:35:19 +08:00
|
|
|
overlayMode: WaveformOverlayMode
|
2026-07-20 10:21:30 +08:00
|
|
|
independentTransforms: ZoomTransform[]
|
|
|
|
|
sharedZoomDomain: [number, number]
|
2026-07-21 15:41:46 +08:00
|
|
|
initialXDomain?: [number, number]
|
|
|
|
|
initialXDomains?: Record<string, [number, number]>
|
|
|
|
|
yDomains?: Record<string, [number, number]>
|
2026-07-20 10:21:30 +08:00
|
|
|
timeUnit: 's' | 'ms'
|
|
|
|
|
rendering: ResolvedWaveformRenderingOptions
|
|
|
|
|
hideSecondaryLabels: boolean
|
|
|
|
|
yAxisLabelX: number
|
2026-07-20 11:27:50 +08:00
|
|
|
showCompactEmptyTracks: boolean
|
2026-07-20 10:21:30 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function buildTrackLayouts(options: BuildTrackLayoutsOptions): TrackLayout[] {
|
|
|
|
|
const visibleCells = options.cells.map((cell) => ({ ...cell, hasSeries: Boolean(cell.series) }))
|
|
|
|
|
const bottomCells = getBottomRowCellIndexes(visibleCells, options.grid.columnCount)
|
|
|
|
|
|
|
|
|
|
return visibleCells.flatMap((cell, index) => {
|
2026-07-20 11:27:50 +08:00
|
|
|
const isEmpty = !cell.series
|
|
|
|
|
if (isEmpty && (options.displayMode !== 'compact' || !options.showCompactEmptyTracks)) return []
|
2026-07-20 14:07:43 +08:00
|
|
|
const emptySeries: DisplaySeries = {
|
2026-07-20 11:27:50 +08:00
|
|
|
id: `empty-grid-slot-${cell.slotIndex}`,
|
|
|
|
|
name: '',
|
|
|
|
|
color: 'transparent',
|
2026-07-20 17:57:52 +08:00
|
|
|
lineType: 'linear',
|
2026-07-22 16:36:48 +08:00
|
|
|
lineStyle: 'solid',
|
2026-07-20 17:57:52 +08:00
|
|
|
pointType: 'none',
|
|
|
|
|
errorBar: { visible: false, width: 1.5, capWidth: 8 },
|
2026-07-20 11:27:50 +08:00
|
|
|
points: [],
|
|
|
|
|
xDomain: [0, 1],
|
|
|
|
|
yDomain: [0, 1],
|
2026-07-22 10:56:25 +08:00
|
|
|
hasErrorPoints: false,
|
2026-07-20 11:27:50 +08:00
|
|
|
}
|
2026-07-20 14:07:43 +08:00
|
|
|
const displayTrack: DisplayTrack = cell.series ?? {
|
|
|
|
|
id: emptySeries.id,
|
|
|
|
|
series: [emptySeries],
|
2026-07-20 17:57:52 +08:00
|
|
|
visibleSeries: [emptySeries],
|
2026-07-20 14:07:43 +08:00
|
|
|
xDomain: emptySeries.xDomain,
|
|
|
|
|
yDomain: emptySeries.yDomain,
|
|
|
|
|
}
|
2026-07-20 17:57:52 +08:00
|
|
|
const hasVisibleSeries = !isEmpty && displayTrack.visibleSeries.length > 0
|
|
|
|
|
const series = displayTrack.visibleSeries[0] ?? displayTrack.series[0] ?? emptySeries
|
2026-07-20 10:21:30 +08:00
|
|
|
const baseXScale =
|
|
|
|
|
options.displayMode === 'independent'
|
2026-07-21 15:41:46 +08:00
|
|
|
? scaleLinear(
|
|
|
|
|
options.initialXDomains?.[displayTrack.id] ??
|
|
|
|
|
options.initialXDomains?.[series.id] ??
|
|
|
|
|
displayTrack.xDomain,
|
|
|
|
|
[0, cell.width],
|
|
|
|
|
)
|
2026-07-20 10:21:30 +08:00
|
|
|
: scaleLinear(options.sharedZoomDomain, [0, cell.width])
|
|
|
|
|
const transform =
|
|
|
|
|
options.displayMode === 'independent'
|
|
|
|
|
? (options.independentTransforms[index] ?? zoomIdentity)
|
|
|
|
|
: zoomIdentity
|
|
|
|
|
const xScale = transform.rescaleX(baseXScale)
|
2026-07-21 15:41:46 +08:00
|
|
|
const configuredYDomain = options.yDomains?.[displayTrack.id]
|
|
|
|
|
const yAxisGroups = buildYAxisSeriesGroups(displayTrack, options.overlayMode).map((group) => ({
|
|
|
|
|
...group,
|
|
|
|
|
domain: configuredYDomain ?? group.domain,
|
|
|
|
|
}))
|
2026-07-20 15:35:19 +08:00
|
|
|
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'
|
2026-07-20 17:57:52 +08:00
|
|
|
? -(Y_AXIS_TICK_PADDING + tickTextWidth + Y_AXIS_EXPONENT_GAP)
|
|
|
|
|
: Y_AXIS_TICK_PADDING + tickTextWidth + Y_AXIS_EXPONENT_GAP)
|
2026-07-20 15:35:19 +08:00
|
|
|
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()
|
2026-07-20 10:21:30 +08:00
|
|
|
const xMajorTicks = xScale.ticks(Math.max(2, Math.floor(cell.width / 100)))
|
2026-07-20 15:35:19 +08:00
|
|
|
const yMajorTicks = yAxes[0]?.majorTicks ?? []
|
|
|
|
|
const yAxisTickValues = yAxes[0]?.tickValues ?? []
|
2026-07-20 10:21:30 +08:00
|
|
|
const domain = xScale.domain() as [number, number]
|
|
|
|
|
const endpointLabels = {
|
|
|
|
|
start: formatEndpointTime(domain[0], domain, options.timeUnit),
|
|
|
|
|
end: formatEndpointTime(domain[1], domain, options.timeUnit),
|
|
|
|
|
}
|
2026-07-20 15:35:19 +08:00
|
|
|
const xAxisExponent = formatAxisTimeExponent(domain, options.timeUnit)
|
2026-07-20 10:21:30 +08:00
|
|
|
const leftClearance = endpointLabels.start.length * 7 + 10
|
|
|
|
|
const rightClearance = endpointLabels.end.length * 7 + 10
|
|
|
|
|
const xAxisTickValues = xMajorTicks.filter((tick) => {
|
|
|
|
|
const position = xScale(tick)
|
|
|
|
|
return position > leftClearance && position < cell.width - rightClearance
|
|
|
|
|
})
|
2026-07-20 17:57:52 +08:00
|
|
|
const seriesPaths = displayTrack.visibleSeries.map((trackSeries) => {
|
2026-07-20 15:35:19 +08:00
|
|
|
const yAxis = yAxes.find((axis) =>
|
|
|
|
|
axis.seriesList.some((series) => series.id === trackSeries.id),
|
|
|
|
|
)
|
|
|
|
|
const seriesYScale = yAxis?.scale ?? yScale
|
2026-07-22 10:56:25 +08:00
|
|
|
const renderPoints = selectSeriesRenderPoints(
|
2026-07-20 14:07:43 +08:00
|
|
|
trackSeries.points,
|
|
|
|
|
domain,
|
|
|
|
|
cell.width,
|
|
|
|
|
options.rendering,
|
2026-07-22 10:56:25 +08:00
|
|
|
{
|
|
|
|
|
lineVisible: !isEmpty && trackSeries.lineType !== 'none',
|
|
|
|
|
pointVisible: trackSeries.pointType !== 'none',
|
|
|
|
|
errorBarVisible: trackSeries.errorBar.visible,
|
|
|
|
|
hasErrorPoints: trackSeries.hasErrorPoints,
|
|
|
|
|
},
|
2026-07-20 14:07:43 +08:00
|
|
|
)
|
2026-07-20 17:57:52 +08:00
|
|
|
const pathGenerator = line<WaveformPoint>()
|
|
|
|
|
.x((point) => xScale(point.x))
|
|
|
|
|
.y((point) => seriesYScale(point.y))
|
|
|
|
|
if (trackSeries.lineType === 'step-start') pathGenerator.curve(curveStepBefore)
|
|
|
|
|
if (trackSeries.lineType === 'step-middle') pathGenerator.curve(curveStep)
|
|
|
|
|
if (trackSeries.lineType === 'step-end' || trackSeries.lineType === 'step-after') {
|
|
|
|
|
pathGenerator.curve(curveStepAfter)
|
|
|
|
|
}
|
2026-07-20 14:07:43 +08:00
|
|
|
return {
|
|
|
|
|
series: trackSeries,
|
2026-07-22 10:56:25 +08:00
|
|
|
path: renderPoints.linePoints.length ? pathGenerator(renderPoints.linePoints) : null,
|
|
|
|
|
pointRenderPoints: renderPoints.pointRenderPoints,
|
|
|
|
|
errorBarRenderPoints: renderPoints.errorBarRenderPoints,
|
2026-07-20 15:35:19 +08:00
|
|
|
yScale: seriesYScale,
|
|
|
|
|
yAxisIndex: yAxis?.index ?? 0,
|
2026-07-20 14:07:43 +08:00
|
|
|
}
|
|
|
|
|
})
|
2026-07-20 10:21:30 +08:00
|
|
|
|
|
|
|
|
return {
|
2026-07-22 19:53:00 +08:00
|
|
|
id: displayTrack.id,
|
2026-07-20 10:21:30 +08:00
|
|
|
index,
|
|
|
|
|
series,
|
2026-07-20 17:57:52 +08:00
|
|
|
seriesList: displayTrack.visibleSeries,
|
|
|
|
|
legendSeries: displayTrack.series,
|
2026-07-20 11:27:50 +08:00
|
|
|
isEmpty,
|
2026-07-20 17:57:52 +08:00
|
|
|
hasVisibleSeries,
|
2026-07-20 10:21:30 +08:00
|
|
|
column: cell.column,
|
|
|
|
|
showYAxisLabel: !options.hideSecondaryLabels || cell.column === 0,
|
|
|
|
|
yAxisLabelX: options.yAxisLabelX,
|
|
|
|
|
left: cell.left,
|
|
|
|
|
top: cell.top,
|
|
|
|
|
width: cell.width,
|
|
|
|
|
height: cell.plotHeight,
|
|
|
|
|
xScale,
|
|
|
|
|
yScale,
|
2026-07-20 15:35:19 +08:00
|
|
|
yAxes,
|
2026-07-20 10:21:30 +08:00
|
|
|
xMajorTicks,
|
2026-07-20 11:27:50 +08:00
|
|
|
xMinorTicks: buildMinorTicks(xMajorTicks, 5, domain),
|
2026-07-20 10:21:30 +08:00
|
|
|
yMajorTicks,
|
2026-07-20 15:35:19 +08:00
|
|
|
yMinorTicks: yAxes[0]?.minorTicks ?? [],
|
2026-07-20 10:21:30 +08:00
|
|
|
yAxisTickValues,
|
|
|
|
|
xAxisTickValues,
|
|
|
|
|
endpointLabels,
|
2026-07-20 15:35:19 +08:00
|
|
|
xAxisExponent,
|
2026-07-20 14:07:43 +08:00
|
|
|
path: seriesPaths[0]?.path ?? null,
|
|
|
|
|
seriesPaths,
|
2026-07-22 10:56:25 +08:00
|
|
|
gridLines: options.grid.trackLines[displayTrack.id] ?? {
|
|
|
|
|
horizontal: true,
|
|
|
|
|
vertical: true,
|
|
|
|
|
},
|
2026-07-20 11:27:50 +08:00
|
|
|
showXAxis:
|
2026-07-20 17:57:52 +08:00
|
|
|
(isEmpty || hasVisibleSeries) &&
|
|
|
|
|
(options.displayMode === 'independent' ||
|
|
|
|
|
(options.displayMode === 'compact'
|
|
|
|
|
? cell.row === options.grid.rowCount - 1
|
|
|
|
|
: bottomCells.has(cell.slotIndex))),
|
2026-07-20 10:21:30 +08:00
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|