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.
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 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>