Rocket ECharts Pro

Rocket is currently in alpha – available in the Datastar Pro repo.

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 data-attr:option="{
 2    title: { text: 'Sales Data' },
 3    tooltip: { trigger: 'axis' },
 4    legend: {
 5        formatter: function (name) {
 6            return 'Series: ' + name
 7        }
 8    },
 9    xAxis: {
10        type: 'category',
11        data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
12    },
13    yAxis: { type: 'value' },
14    series: [{
15        name: 'Sales',
16        data: [120, 200, 150, 80, 70, 110, 130],
17        type: 'bar'
18    }]
19}"></rocket-echarts>

Rocket Component #

  1<template data-rocket:rocket-echarts
  2          data-prop:option="js"
  3          data-prop:theme="str (= 'default')"
  4          data-prop:resize-delay="int (min 0) (= 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
 32    // Ensure container has dimensions
 33    function ensureDimensions() {
 34      if (el.clientHeight > 0 && chartEl.clientHeight === 0) {
 35        chartEl.style.height = el.clientHeight + 'px'
 36      }
 37      if (chartEl.clientHeight === 0) {
 38        chartEl.style.height = '400px'
 39      }
 40      if (chartEl.clientWidth === 0) {
 41        chartEl.style.width = '100%'
 42      }
 43    }
 44    ensureDimensions()
 45
 46    // Set and update chart option
 47    function updateOption() {
 48      if (Object.keys($$option).length) chart.setOption(resolveVars($$option))
 49    }
 50
 51    function initChart() {
 52      ensureDimensions()
 53      if (chartEl.clientWidth === 0 || chartEl.clientHeight === 0) {
 54        requestAnimationFrame(initChart)
 55        return
 56      }
 57      chart?.dispose()
 58      chart = echarts.init(chartEl, currentTheme === 'default' ? null : currentTheme)
 59      updateOption()
 60      effect(updateOption)
 61    }
 62    requestAnimationFrame(initChart)
 63
 64    // Handle theme changes
 65    effect(() => {
 66      if (!chart) return
 67      if ($$theme !== currentTheme) {
 68        currentTheme = $$theme
 69        chart.dispose()
 70        chart = echarts.init(chartEl, currentTheme === 'default' ? null : currentTheme)
 71        updateOption()
 72      }
 73    })
 74
 75    // Set up resize observer with debounce
 76    let resizeTimeout
 77    const resizeObserver = new ResizeObserver(() => {
 78      clearTimeout(resizeTimeout)
 79      resizeTimeout = setTimeout(() => chart?.resize(), $$resizeDelay)
 80    })
 81    resizeObserver.observe(chartEl)
 82
 83    // Handle color scheme changes
 84    const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)')
 85    mediaQuery.onchange = () => {
 86      if (!chart) return
 87      if ($$theme === 'default') {
 88        chart.dispose()
 89        chart = echarts.init(chartEl, null)
 90        updateOption()
 91      }
 92    }
 93
 94    // Cleanup on disconnect
 95    onCleanup(() => {
 96      resizeObserver.disconnect()
 97      clearTimeout(resizeTimeout)
 98      mediaQuery.onchange = null
 99      chart?.dispose()
100    })
101  </script>
102  <style>
103    :host {
104      display: block;
105      width: 100%;
106      height: 100%;
107      min-height: 400px;
108    }
109
110    .chart-container {
111      display: block;
112      width: 100%;
113      height: 100%;
114      min-height: inherit;
115      position: relative;
116      /* Important for ECharts' absolute positioned canvas */
117    }
118  </style>
119
120  <div class="chart-container" data-ref="chartNode"></div>
121</template>