Performance Optimization
Learn how to optimize TradeX Chart for maximum performance and smooth rendering.
Data Optimization
Limit Data Points
Limit the number of candles loaded in memory:
const MAX_CANDLES = 5000
function loadDataWithLimit(chart, data) {
if (data.length > MAX_CANDLES) {
// Keep only the most recent candles
const trimmedData = data.slice(-MAX_CANDLES)
chart.setData(trimmedData)
} else {
chart.setData(data)
}
}
Lazy Loading
Load data on demand as users scroll:
class LazyDataLoader {
constructor(chart, fetchFunction) {
this.chart = chart
this.fetchFunction = fetchFunction
this.loading = false
this.chart.on('scroll', this.onScroll.bind(this))
}
async onScroll(event) {
if (event.position < 0.1 && !this.loading) {
this.loading = true
const oldData = await this.fetchFunction()
this.chart.prependData(oldData)
this.loading = false
}
}
}
Data Decimation
Reduce data points for distant time ranges:
function decimateData(data, factor) {
if (factor <= 1) return data
const decimated = []
for (let i = 0; i < data.length; i += factor) {
decimated.push(data[i])
}
return decimated
}
// Use when zoomed out
const zoomLevel = chart.getZoomLevel()
if (zoomLevel < 0.5) {
const decimated = decimateData(fullData, 2)
chart.setData(decimated)
}
Rendering Optimization
Throttle Updates
Throttle rapid updates to reduce rendering:
class ThrottledUpdater {
constructor(chart, delay = 100) {
this.chart = chart
this.delay = delay
this.pending = null
this.timer = null
}
update(candle) {
this.pending = candle
if (!this.timer) {
this.timer = setTimeout(() => {
if (this.pending) {
this.chart.updateStreamingCandle(this.pending)
this.pending = null
}
this.timer = null
}, this.delay)
}
}
}
// Usage
const updater = new ThrottledUpdater(chart, 50)
ws.onmessage = (event) => {
const candle = parseCandle(event.data)
updater.update(candle)
}
Debounce Resize
Debounce window resize events:
let resizeTimer
window.addEventListener('resize', () => {
clearTimeout(resizeTimer)
resizeTimer = setTimeout(() => {
chart.resize()
}, 250)
})
Disable Animations
Disable animations for better performance:
chart.start({
animations: false,
// ... other config
})
Indicator Optimization
Limit Active Indicators
Limit the number of active indicators:
const MAX_INDICATORS = 5
function addIndicatorWithLimit(chart, name, params) {
const indicators = chart.getIndicators()
if (indicators.length >= MAX_INDICATORS) {
// Remove oldest indicator
chart.removeIndicator(indicators[0].id)
}
return chart.addIndicator(name, params)
}
Cache Indicator Calculations
class CachedIndicator {
constructor(indicator) {
this.indicator = indicator
this.cache = new Map()
}
calculate(data, params) {
const key = this.getCacheKey(data, params)
if (this.cache.has(key)) {
return this.cache.get(key)
}
const result = this.indicator.calculate(data, params)
this.cache.set(key, result)
// Limit cache size
if (this.cache.size > 100) {
const firstKey = this.cache.keys().next().value
this.cache.delete(firstKey)
}
return result
}
getCacheKey(data, params) {
return `${data.length}_${JSON.stringify(params)}`
}
}
Memory Management
Clear Unused Data
function clearOldData(chart, maxAge) {
const data = chart.getData()
const cutoff = Date.now() - maxAge
const filtered = data.filter(candle => candle[0] >= cutoff)
chart.setData(filtered)
}
// Clear data older than 7 days
setInterval(() => {
clearOldData(chart, 7 * 24 * 60 * 60 * 1000)
}, 60 * 60 * 1000) // Check every hour
Cleanup on Destroy
function destroyChart(chart) {
// Remove event listeners
chart.off('all')
// Clear data
chart.clearData()
// Destroy chart
chart.destroy()
// Clear references
chart = null
}
Use WeakMap for References
const chartData = new WeakMap()
function storeChartData(chart, data) {
chartData.set(chart, data)
}
function getChartData(chart) {
return chartData.get(chart)
}
// Data will be garbage collected when chart is destroyed
Canvas Optimization
Use OffscreenCanvas
if (typeof OffscreenCanvas !== 'undefined') {
chart.start({
useOffscreenCanvas: true,
// ... other config
})
}
Reduce Canvas Resolution
For lower-end devices:
const pixelRatio = window.devicePixelRatio
chart.start({
pixelRatio: Math.min(pixelRatio, 2), // Cap at 2x
// ... other config
})
Network Optimization
Compress Data
async function fetchCompressedData(url) {
const response = await fetch(url, {
headers: {
'Accept-Encoding': 'gzip, deflate, br'
}
})
return response.json()
}
Use WebSocket for Real-time
WebSocket is more efficient than polling:
// Bad: Polling
setInterval(async () => {
const data = await fetch('/api/latest')
chart.updateStreamingCandle(data)
}, 1000)
// Good: WebSocket
const ws = new WebSocket('wss://api.example.com')
ws.onmessage = (event) => {
const data = JSON.parse(event.data)
chart.updateStreamingCandle(data)
}
Batch API Requests
class BatchedFetcher {
constructor(fetchFunction, batchSize = 10, delay = 100) {
this.fetchFunction = fetchFunction
this.batchSize = batchSize
this.delay = delay
this.queue = []
this.timer = null
}
fetch(symbol) {
return new Promise((resolve, reject) => {
this.queue.push({ symbol, resolve, reject })
if (!this.timer) {
this.timer = setTimeout(() => this.processBatch(), this.delay)
}
if (this.queue.length >= this.batchSize) {
clearTimeout(this.timer)
this.processBatch()
}
})
}
async processBatch() {
const batch = this.queue.splice(0, this.batchSize)
this.timer = null
try {
const symbols = batch.map(item => item.symbol)
const results = await this.fetchFunction(symbols)
batch.forEach((item, index) => {
item.resolve(results[index])
})
} catch (error) {
batch.forEach(item => item.reject(error))
}
}
}
Browser Optimization
Use RequestAnimationFrame
class AnimationLoop {
constructor(chart) {
this.chart = chart
this.running = false
this.updates = []
}
start() {
this.running = true
this.loop()
}
stop() {
this.running = false
}
queueUpdate(update) {
this.updates.push(update)
}
loop() {
if (!this.running) return
if (this.updates.length > 0) {
const updates = this.updates.splice(0)
updates.forEach(update => update())
}
requestAnimationFrame(() => this.loop())
}
}
Use Web Workers
// worker.js
self.onmessage = function(e) {
const { data, params } = e.data
const result = calculateIndicator(data, params)
self.postMessage(result)
}
// main.js
const worker = new Worker('worker.js')
worker.postMessage({
data: chartData,
params: { period: 14 }
})
worker.onmessage = function(e) {
const result = e.data
chart.updateIndicator(result)
}
Mobile Optimization
Reduce Touch Sensitivity
chart.start({
touch: {
sensitivity: 0.5,
threshold: 10
}
})
Disable Features on Mobile
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent)
chart.start({
crosshair: !isMobile,
tooltip: !isMobile,
animations: !isMobile
})
Use Passive Event Listeners
chartElement.addEventListener('touchstart', handler, { passive: true })
chartElement.addEventListener('touchmove', handler, { passive: true })
Profiling and Monitoring
Performance Monitoring
class PerformanceMonitor {
constructor() {
this.metrics = {
fps: 0,
renderTime: 0,
updateTime: 0
}
this.frameCount = 0
this.lastTime = performance.now()
}
startFrame() {
this.frameStart = performance.now()
}
endFrame() {
const now = performance.now()
const frameTime = now - this.frameStart
this.metrics.renderTime = frameTime
this.frameCount++
// Calculate FPS every second
if (now - this.lastTime >= 1000) {
this.metrics.fps = this.frameCount
this.frameCount = 0
this.lastTime = now
}
}
getMetrics() {
return this.metrics
}
}
// Usage
const monitor = new PerformanceMonitor()
chart.on('beforeRender', () => monitor.startFrame())
chart.on('afterRender', () => monitor.endFrame())
setInterval(() => {
const metrics = monitor.getMetrics()
console.log(`FPS: ${metrics.fps}, Render: ${metrics.renderTime.toFixed(2)}ms`)
}, 1000)
Memory Profiling
function logMemoryUsage() {
if (performance.memory) {
const used = performance.memory.usedJSHeapSize / 1048576
const total = performance.memory.totalJSHeapSize / 1048576
console.log(`Memory: ${used.toFixed(2)}MB / ${total.toFixed(2)}MB`)
}
}
setInterval(logMemoryUsage, 5000)
Best Practices
1. Batch Operations
// Bad: Multiple individual operations
data.forEach(candle => chart.addCandle(candle))
// Good: Single batch operation
chart.addCandles(data)
2. Avoid Frequent Redraws
// Bad: Triggers redraw for each change
chart.setOption('theme.candle.color', 'red')
chart.setOption('theme.grid.color', 'gray')
chart.setOption('theme.background', 'black')
// Good: Batch configuration
chart.setOptions({
theme: {
candle: { color: 'red' },
grid: { color: 'gray' },
background: 'black'
}
})
3. Use Appropriate Data Structures
// Use Map for fast lookups
const candleMap = new Map()
data.forEach(candle => {
candleMap.set(candle[0], candle)
})
// Use Set for unique values
const timestamps = new Set(data.map(c => c[0]))
4. Cleanup Event Listeners
function setupChart(chart) {
const handler = (event) => {
// Handle event
}
chart.on('update', handler)
return () => {
chart.off('update', handler)
}
}
const cleanup = setupChart(chart)
// Later
cleanup()
Performance Checklist
- Limit data points to 5000-10000 candles
- Use lazy loading for historical data
- Throttle real-time updates (50-100ms)
- Limit active indicators to 3-5
- Disable animations on mobile
- Use WebSocket instead of polling
- Implement data caching
- Cleanup on component unmount
- Monitor FPS and memory usage
- Test on low-end devices
Related Documentation
- Data Management - Efficient data handling
- WebSocket Integration - Real-time updates
- Mobile Responsive - Mobile optimization
- API Reference - Performance methods