Rocket ECharts Pro
Rocket is currently in beta.
An ECharts integration, loaded from the ESM bundle inside an Rocket component.
Explanation #
ECharts is a good example of the current Rocket split: the demo page owns the interactive controls and option data, while the component mounts ECharts once from a rendered ref in onFirstRender() and updates the live chart with prop observers.
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 component-level ESM import for ECharts.
- An
onFirstRender()mount step that waits for the rendered container ref. observeProps(...)updates that keep the live chart instance in sync.- Built-in prop validation through codec syntax.
Imperative DOM Ownership #
This example also shows an important integration rule: ECharts owns the DOM inside the chart container. Rocket still handles prop decoding and attribute watchers, but the actual SVG or canvas nodes are created by ECharts, not by Rocket’s template render.
That means option changes should update the existing chart instance without re-rendering the component shell. If Rocket re-renders the template on every prop change, it replaces the container and wipes out the DOM ECharts just drew. For components like this, use renderOnPropChange false, or a predicate that returns false for option updates, and let the watcher update the library instance directly.
The key features of this Rocket ECharts integration include:
- ESM loading: ECharts is imported once by the Rocket definition and reused across instances.
- Reactive updates: Prop changes flow through Rocket watchers, which update the existing chart instance in place.
- 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<echarts-view 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}"></echarts-view>Rocket Component #
The key detail is that the template still renders once to create the container, but later option updates mutate the existing ECharts instance instead of asking Rocket to rebuild the shadow DOM. If a component only needs to skip re-renders for some props, renderOnPropChange can also be a predicate over the current change set.
1import { rocket } from 'datastar'
2
3const echarts = await import(
4 'https://cdn.jsdelivr.net/npm/[email protected]/dist/echarts.esm.js'
5)
6
7const resolveVars = (el, value) => {
8 if (Array.isArray(value)) return value.map((part) => resolveVars(el, part))
9 if (value && typeof value === 'object') {
10 return Object.fromEntries(
11 Object.entries(value).map(([key, part]) => [key, resolveVars(el, part)]),
12 )
13 }
14 if (typeof value !== 'string' || !value.includes('var(')) return value
15 return value.replace(/var\((--[\w-]+)\)/g, (_, name) => {
16 return getComputedStyle(el).getPropertyValue(name).trim() || `var(${name})`
17 })
18}
19
20rocket('echarts-view', {
21 renderOnPropChange: false,
22 props: ({ js, string, number }) => ({
23 option: js,
24 theme: string.default('default'),
25 resizeDelay: number.min(0).default(100),
26 }),
27 onFirstRender: ({ refs, cleanup, host, observeProps, props }) => {
28 let chart
29 let observer
30 let resizeTimer = 0
31
32 const update = () =>
33 chart?.setOption(resolveVars(host, props.option ?? {}), true)
34 const resize = () => {
35 clearTimeout(resizeTimer)
36 resizeTimer = setTimeout(() => chart?.resize(), props.resizeDelay)
37 }
38 const init = () => {
39 if (!(refs.container instanceof HTMLElement)) return
40 refs.container.style.height ||= '100%'
41 refs.container.style.minHeight ||= '400px'
42 chart?.dispose()
43 chart = echarts.init(
44 refs.container,
45 props.theme === 'default' ? undefined : props.theme,
46 )
47 update()
48 observer?.disconnect()
49 observer = new ResizeObserver(resize)
50 observer.observe(refs.container)
51 }
52
53 init()
54 observeProps(update, 'option')
55 observeProps(init, 'theme')
56 observeProps(resize, 'resizeDelay')
57
58 cleanup(() => {
59 observer?.disconnect()
60 clearTimeout(resizeTimer)
61 chart?.dispose()
62 })
63 },
64 render: ({ html }) => html`
65 <style>
66 :host {
67 display: block;
68 width: 100%;
69 height: 100%;
70 min-height: 400px;
71 }
72
73 .chart-container {
74 width: 100%;
75 height: 100%;
76 min-height: inherit;
77 }
78 </style>
79 <div class="chart-container" data-ref:container></div>
80 `,
81})