Rocket ECharts Pro

Rocket is currently in alpha – available with Datastar Pro.

An ECharts integration, loaded via CDN using Rocket’s declarative import.

Demo

Explanation #

ECharts is a powerful, enterprise-grade charting library with hundreds of chart types, animations, and complex interaction patterns. Despite its complexity, Rocket allows you to create a fully-featured ECharts component in around 100 lines of code. This showcases Rocket’s ability to seamlessly integrate sophisticated JavaScript libraries while maintaining clean, declarative markup.

Component Structure #

The ECharts component accepts the following props:

Simplicity Through Rocket #

What would typically require complex initialization code, manual DOM manipulation, and event handling is reduced to:

The key features of this Rocket ECharts integration include:

Usage Example #

 1<rocket-echarts option='{
 2    "title": { "text": "Sales Data" },
 3    "tooltip": { "trigger": "axis" },
 4    "xAxis": { 
 5        "type": "category", 
 6        "data": ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"] 
 7    },
 8    "yAxis": { "type": "value" },
 9    "series": [{
10        "data": [120, 200, 150, 80, 70, 110, 130],
11        "type": "bar"
12    }]
13}'></rocket-echarts>

Rocket Component #

  1<template data-rocket:rocket-echarts
  2          data-props:option="js|={}"
  3          data-props:theme="string|=default"
  4          data-props:resize-delay="int|=100"
  5          data-import:echarts="https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.esm.js"
  6>
  7  <script>
  8    // Store non-serializable objects
  9    let chart = null
 10    let currentTheme = $$theme
 11
 12    // Helper to resolve CSS variables in options
 13    function resolveVars(val) {
 14      if (Array.isArray(val)) return val.map(resolveVars)
 15      if (val && typeof val === 'object') {
 16        const resolved = {}
 17        for (const key in val) {
 18          if (Object.hasOwn(val, key)) resolved[key] = resolveVars(val[key])
 19        }
 20        return resolved
 21      }
 22      if (typeof val === 'string' && val.includes('var(')) {
 23        return val.replace(/var\((--[\w-]+)\)/g, (_, name) =>
 24          getComputedStyle(el).getPropertyValue(name).trim() || `var(${name})`)
 25      }
 26      return val
 27    }
 28
 29    // Initialize chart - DOM and imports are ready when setup runs
 30    const chartEl = $$chartNode
 31    if (!chartEl) {
 32      console.error('[ECharts] Chart element not found')
 33      return
 34    }
 35
 36    // Ensure container has dimensions
 37    function ensureDimensions() {
 38      const hostHeight = el.clientHeight
 39      if (hostHeight > 0 && chartEl.clientHeight === 0) {
 40        chartEl.style.height = hostHeight + 'px'
 41      }
 42      if (chartEl.clientHeight === 0) {
 43        chartEl.style.height = '400px'
 44      }
 45      if (chartEl.clientWidth === 0) {
 46        chartEl.style.width = '100%'
 47      }
 48    }
 49    ensureDimensions()
 50
 51    // Check if ECharts is available
 52    if (typeof echarts === 'undefined') {
 53      console.error('[ECharts] ECharts library not available')
 54      return
 55    }
 56
 57    // Helper to get plain value from signal (handles Proxy objects)
 58    function getPlainOption() {
 59      // Deep clone to resolve all proxy values and CSS variables
 60      return resolveVars(JSON.parse(JSON.stringify($$option || {})))
 61    }
 62
 63    // Set and update chart option
 64    function updateOption() {
 65      const option = getPlainOption()
 66      if (Object.keys(option).length) {
 67        chart.setOption(option)
 68      }
 69    }
 70
 71    function initChart() {
 72      ensureDimensions()
 73      if (chartEl.clientWidth === 0 || chartEl.clientHeight === 0) {
 74        requestAnimationFrame(initChart)
 75        return
 76      }
 77      chart?.dispose()
 78      chart = echarts.init(chartEl, currentTheme === 'default' ? null : currentTheme)
 79      updateOption()
 80      effect(updateOption)
 81    }
 82    requestAnimationFrame(initChart)
 83
 84    // Handle theme changes
 85    effect(() => {
 86      if (!chart) return
 87      if ($$theme !== currentTheme) {
 88        currentTheme = $$theme
 89        chart.dispose()
 90        chart = echarts.init(chartEl, currentTheme === 'default' ? null : currentTheme)
 91        updateOption()
 92      }
 93    })
 94
 95    // Set up resize observer with debounce
 96    let resizeTimeout
 97    const resizeObserver = new ResizeObserver(() => {
 98      clearTimeout(resizeTimeout)
 99      resizeTimeout = setTimeout(() => chart?.resize(), $$resizeDelay)
100    })
101    resizeObserver.observe(chartEl)
102
103    // Handle color scheme changes
104    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
105    mediaQuery.onchange = () => {
106      if (!chart) return
107      if ($$theme === 'default') {
108        chart.dispose()
109        chart = echarts.init(chartEl, null)
110        updateOption()
111      }
112    }
113
114    // Cleanup on disconnect
115    onCleanup(() => {
116      resizeObserver.disconnect()
117      clearTimeout(resizeTimeout)
118      mediaQuery.onchange = null
119      chart?.dispose()
120    })
121  </script>
122  <style>
123    :host {
124      display: block;
125      width: 100%;
126      height: 100%;
127      min-height: 400px;
128    }
129
130    .chart-container {
131      display: block;
132      width: 100%;
133      height: 100%;
134      min-height: inherit;
135      position: relative;
136      /* Important for ECharts' absolute positioned canvas */
137    }
138  </style>
139
140  <div class="chart-container" data-ref="chartNode"></div>
141</template>