Rocket ECharts Pro

Rocket is currently in beta.

An ECharts integration, loaded from the ESM bundle inside an Rocket component.

Demo

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:

Simplicity Through Rocket #

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

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:

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})