Rendering Pipeline
TradeX Chart uses a multi-layered canvas rendering pipeline optimized for performance.
Architecture Overview
┌─────────────────────────────────────┐
│ Data Layer │
│ (OHLCV, Indicators, Overlays) │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Transform Layer │
│ (Scale, Viewport, Coordinates) │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Render Queue │
│ (Batching, Prioritization) │
└──────────────┬──────────────────────┘
│
▼
┌─────────────────────────────────────┐
│ Canvas Layers │
│ (Background, Chart, Overlay, UI) │
└─────────────────────────────────────┘
Canvas Layers
Layer Stack
const layers = {
background: {
zIndex: 0,
static: true,
canvas: backgroundCanvas
},
grid: {
zIndex: 1,
static: true,
canvas: gridCanvas
},
chart: {
zIndex: 2,
static: false,
canvas: chartCanvas
},
indicators: {
zIndex: 3,
static: false,
canvas: indicatorCanvas
},
overlay: {
zIndex: 4,
static: false,
canvas: overlayCanvas
},
ui: {
zIndex: 5,
static: false,
canvas: uiCanvas
}
}
Layer Optimization
Static Layers: Rendered once, cached Dynamic Layers: Re-rendered on data/viewport changes
Render Cycle
Frame Lifecycle
class RenderPipeline {
requestFrame() {
if (this.frameRequested) return
this.frameRequested = true
requestAnimationFrame(() => this.render())
}
render() {
this.frameRequested = false
// 1. Update phase
this.updateTransforms()
this.updateViewport()
// 2. Render phase
this.renderBackground()
this.renderGrid()
this.renderChart()
this.renderIndicators()
this.renderOverlays()
this.renderUI()
// 3. Composite phase
this.composite()
}
}
Coordinate Systems
Transform Pipeline
class CoordinateTransform {
// Data space -> Screen space
dataToScreen(timestamp, price) {
const x = this.timeToX(timestamp)
const y = this.priceToY(price)
return { x, y }
}
// Screen space -> Data space
screenToData(x, y) {
const timestamp = this.xToTime(x)
const price = this.yToPrice(y)
return { timestamp, price }
}
timeToX(timestamp) {
const index = this.getIndexFromTimestamp(timestamp)
return (index - this.viewport.start) * this.candleWidth
}
priceToY(price) {
const range = this.priceRange.max - this.priceRange.min
const normalized = (price - this.priceRange.min) / range
return this.height - (normalized * this.height)
}
}
Rendering Optimizations
Viewport Culling
class ViewportCuller {
getVisibleCandles() {
const start = Math.floor(this.viewport.start)
const end = Math.ceil(this.viewport.end)
return this.data.slice(
Math.max(0, start - 1),
Math.min(this.data.length, end + 1)
)
}
isVisible(x, y, width, height) {
return (
x + width >= 0 &&
x <= this.width &&
y + height >= 0 &&
y <= this.height
)
}
}
Batch Rendering
class BatchRenderer {
renderCandles(candles) {
const ctx = this.ctx
// Batch all wicks
ctx.beginPath()
candles.forEach(candle => {
this.drawWick(candle)
})
ctx.stroke()
// Batch all bodies
candles.forEach(candle => {
this.drawBody(candle)
})
}
}
Performance Monitoring
Frame Timing
class PerformanceMonitor {
measureFrame(callback) {
const start = performance.now()
callback()
const end = performance.now()
this.frameTimes.push(end - start)
this.updateMetrics()
}
updateMetrics() {
const avg = this.frameTimes.reduce((a, b) => a + b, 0) / this.frameTimes.length
const fps = 1000 / avg
console.log(`FPS: ${fps.toFixed(2)}, Avg: ${avg.toFixed(2)}ms`)
}
}
Related Documentation
- Performance Internals - Performance details
- State Management - State architecture
- Event System - Event handling