Rocket ECharts Pro
Rocket is currently in alpha – available with Datastar Pro.
An ECharts integration, loaded via CDN using Rocket’s declarative import.
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:
option– The configuration object (JavaScript object, defaults to{ }).theme– Chart theme name (string, defaults todefault).resize-delay– Delay in milliseconds before resizing (integer, defaults to100).
Simplicity Through Rocket #
What would typically require complex initialization code, manual DOM manipulation, and event handling is reduced to:
- One
data-import:echartsattribute to load the library. - Reactive effects that automatically update charts when data changes.
- Built-in cleanup handling through Rocket’s lifecycle management.
- Built-in prop validation through codec syntax.
The key features of this Rocket ECharts integration include:
- Declarative imports: ECharts is loaded via CDN with a single attribute.
- Reactive updates: Charts automatically re-render when bound data changes.
- CSS variable resolution: Chart colors can use CSS custom properties.
- Responsive design: ResizeObserver ensures charts adapt to container changes.
- Theme switching: Automatic dark/light mode support via media queries.
- Component isolation: Each component maintains isolated state.
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>