95
src/components/core/layout.ts
Normal file
95
src/components/core/layout.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
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 {
|
||||
getBottomRowCellIndexes,
|
||||
type GridCellGeometry,
|
||||
type NormalizedWaveformGridOptions,
|
||||
} from './grid'
|
||||
import type { DisplaySeries, TrackLayout } from './types'
|
||||
|
||||
interface SeriesGridCell extends GridCellGeometry {
|
||||
series?: DisplaySeries
|
||||
}
|
||||
|
||||
export interface BuildTrackLayoutsOptions {
|
||||
cells: SeriesGridCell[]
|
||||
grid: NormalizedWaveformGridOptions
|
||||
displayMode: WaveformDisplayMode
|
||||
independentTransforms: ZoomTransform[]
|
||||
sharedZoomDomain: [number, number]
|
||||
timeUnit: 's' | 'ms'
|
||||
rendering: ResolvedWaveformRenderingOptions
|
||||
hideSecondaryLabels: boolean
|
||||
yAxisLabelX: number
|
||||
}
|
||||
|
||||
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) => {
|
||||
const series = cell.series
|
||||
if (!series) return []
|
||||
const baseXScale =
|
||||
options.displayMode === 'independent'
|
||||
? scaleLinear(series.xDomain, [0, cell.width])
|
||||
: scaleLinear(options.sharedZoomDomain, [0, cell.width])
|
||||
const transform =
|
||||
options.displayMode === 'independent'
|
||||
? (options.independentTransforms[index] ?? zoomIdentity)
|
||||
: zoomIdentity
|
||||
const xScale = transform.rescaleX(baseXScale)
|
||||
const yScale = scaleLinear(series.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 yAxisTickValues =
|
||||
options.displayMode === 'compact' && cell.row < options.grid.rowCount - 1
|
||||
? yMajorTicks.slice(1)
|
||||
: yMajorTicks
|
||||
const domain = xScale.domain() as [number, number]
|
||||
const endpointLabels = {
|
||||
start: formatEndpointTime(domain[0], domain, options.timeUnit),
|
||||
end: formatEndpointTime(domain[1], domain, options.timeUnit),
|
||||
}
|
||||
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
|
||||
})
|
||||
const renderPoints = selectRenderablePoints(
|
||||
series.points,
|
||||
domain,
|
||||
cell.width,
|
||||
options.rendering,
|
||||
)
|
||||
|
||||
return {
|
||||
index,
|
||||
series,
|
||||
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,
|
||||
xMajorTicks,
|
||||
xMinorTicks: buildMinorTicks(xMajorTicks),
|
||||
yMajorTicks,
|
||||
yMinorTicks: buildMinorTicks(yMajorTicks),
|
||||
yAxisTickValues,
|
||||
xAxisTickValues,
|
||||
endpointLabels,
|
||||
path: line<WaveformPoint>()
|
||||
.x((point) => xScale(point.x))
|
||||
.y((point) => yScale(point.y))(renderPoints),
|
||||
showXAxis: options.displayMode === 'independent' || bottomCells.has(cell.slotIndex),
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user