Skip to content

Event System

TradeX Chart uses an event-driven architecture for component communication and user interactions.

Event Bus

Implementation

class EventBus {
  constructor() {
    this.events = new Map()
  }

  on(event, callback, context = null) {
    if (!this.events.has(event)) {
      this.events.set(event, [])
    }
    
    const listener = { callback, context }
    this.events.get(event).push(listener)
    
    return () => this.off(event, callback)
  }

  once(event, callback, context = null) {
    const wrapper = (...args) => {
      this.off(event, wrapper)
      callback.apply(context, args)
    }
    
    return this.on(event, wrapper, context)
  }

  off(event, callback) {
    if (!this.events.has(event)) return
    
    const listeners = this.events.get(event)
    const index = listeners.findIndex(l => l.callback === callback)
    
    if (index > -1) {
      listeners.splice(index, 1)
    }
  }

  emit(event, ...args) {
    if (!this.events.has(event)) return
    
    const listeners = this.events.get(event)
    listeners.forEach(({ callback, context }) => {
      callback.apply(context, args)
    })
  }

  clear(event) {
    if (event) {
      this.events.delete(event)
    } else {
      this.events.clear()
    }
  }
}

Event Types

User Interaction Events

// Mouse events
chart.on('click', (event) => {
  console.log('Clicked at:', event.x, event.y)
})

chart.on('mousemove', (event) => {
  console.log('Mouse at:', event.x, event.y)
})

chart.on('mousedown', (event) => {
  console.log('Mouse down')
})

chart.on('mouseup', (event) => {
  console.log('Mouse up')
})

// Touch events
chart.on('touchstart', (event) => {
  console.log('Touch started')
})

chart.on('touchmove', (event) => {
  console.log('Touch moved')
})

chart.on('touchend', (event) => {
  console.log('Touch ended')
})

Chart Events

// Data events
chart.on('dataLoaded', (data) => {
  console.log('Data loaded:', data.length)
})

chart.on('dataUpdated', (candle) => {
  console.log('New candle:', candle)
})

// Viewport events
chart.on('zoom', (event) => {
  console.log('Zoom level:', event.level)
})

chart.on('scroll', (event) => {
  console.log('Scroll position:', event.position)
})

chart.on('pan', (event) => {
  console.log('Panned by:', event.deltaX, event.deltaY)
})

// UI events
chart.on('crosshair', (event) => {
  console.log('Crosshair at:', event.timestamp, event.price)
})

chart.on('selection', (event) => {
  console.log('Selected:', event.start, event.end)
})

Event Delegation

DOM Event Handling

class EventDelegator {
  constructor(element) {
    this.element = element
    this.handlers = new Map()
    this.setupDelegation()
  }

  setupDelegation() {
    this.element.addEventListener('click', (e) => {
      this.handleEvent('click', e)
    })

    this.element.addEventListener('mousemove', (e) => {
      this.handleEvent('mousemove', e)
    })

    this.element.addEventListener('wheel', (e) => {
      e.preventDefault()
      this.handleEvent('wheel', e)
    }, { passive: false })
  }

  handleEvent(type, domEvent) {
    const chartEvent = this.transformEvent(domEvent)
    this.emit(type, chartEvent)
  }

  transformEvent(domEvent) {
    const rect = this.element.getBoundingClientRect()
    
    return {
      x: domEvent.clientX - rect.left,
      y: domEvent.clientY - rect.top,
      timestamp: this.xToTimestamp(domEvent.clientX - rect.left),
      price: this.yToPrice(domEvent.clientY - rect.top),
      originalEvent: domEvent
    }
  }
}

Event Throttling

Performance Optimization

class ThrottledEvents {
  constructor(eventBus) {
    this.eventBus = eventBus
    this.throttled = new Map()
  }

  throttle(event, callback, delay = 16) {
    let lastCall = 0
    let timeoutId = null
    
    const throttledCallback = (...args) => {
      const now = Date.now()
      
      if (now - lastCall >= delay) {
        lastCall = now
        callback(...args)
      } else {
        clearTimeout(timeoutId)
        timeoutId = setTimeout(() => {
          lastCall = Date.now()
          callback(...args)
        }, delay - (now - lastCall))
      }
    }
    
    this.eventBus.on(event, throttledCallback)
    return () => this.eventBus.off(event, throttledCallback)
  }

  debounce(event, callback, delay = 250) {
    let timeoutId = null
    
    const debouncedCallback = (...args) => {
      clearTimeout(timeoutId)
      timeoutId = setTimeout(() => {
        callback(...args)
      }, delay)
    }
    
    this.eventBus.on(event, debouncedCallback)
    return () => this.eventBus.off(event, debouncedCallback)
  }
}

// Usage
const throttled = new ThrottledEvents(chart.eventBus)

// Throttle mousemove to 60fps
throttled.throttle('mousemove', (event) => {
  updateCrosshair(event)
}, 16)

// Debounce resize
throttled.debounce('resize', () => {
  chart.resize()
}, 250)

Custom Events

Creating Custom Events

class CustomEventEmitter {
  constructor(chart) {
    this.chart = chart
  }

  emitPriceAlert(price, condition) {
    this.chart.emit('priceAlert', {
      price,
      condition,
      timestamp: Date.now()
    })
  }

  emitIndicatorCross(indicator1, indicator2) {
    this.chart.emit('indicatorCross', {
      indicator1,
      indicator2,
      timestamp: Date.now()
    })
  }
}

// Usage
chart.on('priceAlert', (event) => {
  console.log(`Price alert: ${event.price} ${event.condition}`)
  showNotification(event)
})

chart.on('indicatorCross', (event) => {
  console.log(`${event.indicator1} crossed ${event.indicator2}`)
})

Event Propagation

Bubbling and Capturing

class EventPropagation {
  constructor() {
    this.phases = ['capture', 'target', 'bubble']
  }

  dispatchEvent(target, event) {
    const path = this.getEventPath(target)
    
    // Capture phase
    for (let i = path.length - 1; i > 0; i--) {
      if (event.stopped) break
      this.callListeners(path[i], event, 'capture')
    }
    
    // Target phase
    if (!event.stopped) {
      this.callListeners(target, event, 'target')
    }
    
    // Bubble phase
    if (!event.stopped && event.bubbles) {
      for (let i = 1; i < path.length; i++) {
        if (event.stopped) break
        this.callListeners(path[i], event, 'bubble')
      }
    }
  }

  getEventPath(target) {
    const path = [target]
    let current = target.parent
    
    while (current) {
      path.push(current)
      current = current.parent
    }
    
    return path
  }
}