Frontend Development 17 min read

How to Build Plugin‑Based Custom Charts with Web Components in a React Data Platform

This article explains why a data‑analysis platform needs a plugin architecture for custom charts, compares iframe‑based and Web Components approaches, shows how to implement a lightweight ECharts‑based chart plugin, and provides step‑by‑step code samples and technical selection criteria for frontend developers.

GuanYuan Data Tech Team
GuanYuan Data Tech Team
GuanYuan Data Tech Team
How to Build Plugin‑Based Custom Charts with Web Components in a React Data Platform

Background

As more customers adopt the data‑analysis platform across diverse industries, a single standardized product can no longer satisfy varied business scenarios. The platform must support multiple deployment environments (private, cloud, SaaS) and allow features to be picked for specific versions without forcing users to upgrade.

To meet these needs, a plugin solution is required where custom logic can be updated in real time without depending on the main project’s release cycle. The custom‑chart feature is presented as such a plugin.

Chart Plugin Solution

Users edit custom charts by writing HTML, CSS, and JavaScript, which are rendered in an

iframe

. The code can be packaged as a JSON file and installed as a plugin, enabling users to create charts quickly by selecting view data.

Custom chart editing page
Custom chart editing page

Communication between the parent page and the

iframe

allows data transfer, but this approach has drawbacks: high developer skill requirement, limited extensibility (e.g., data formatting), and performance overhead due to iframe isolation. Therefore, a lightweight version called Custom Chart Lite was introduced.

Chart Plugin Upgrade

Custom Chart Lite is built on ECharts and only requires developers to provide the

option

object, dramatically reducing effort compared to writing full HTML/CSS/JS. The Lite version uses a built‑in

BaseChart

component instead of an

iframe

, simplifying data interaction and enabling additional capabilities.

Custom Chart Lite editing interface
Custom Chart Lite editing interface

Technical Selection for Plugin Architecture

Environment isolation : Plugins must not pollute the host page’s styles or variables.

Technology maturity : Broad browser support and active community are essential.

Adaptability : The solution should work across platforms and frameworks.

Communication simplicity : Complex messaging increases implementation difficulty.

DOM structure sharing : Useful for scenarios like centered modal dialogs.

Dynamic loading and updating : Plugins should be loadable and updatable at runtime.

While

iframe

provides isolation, it suffers from performance and integration issues. Articles such as “Why Not Iframe” and the micro‑frontend framework Qiankun discuss these drawbacks.

Web Components have been adopted by many companies and open‑source projects (e.g., YouTube, GitHub). They offer a promising alternative.

What Are Web Components?

Web Components are a set of standards that enable the creation of reusable custom elements. Introduced in 2011 and standardized in 2014, they consist of three core technologies:

custom elements

,

shadow DOM

, and

HTML templates

.

<code>class MyElement extends HTMLElement {
  constructor() {
    super();
    // create a shadow root
    const shadowRoot = this.attachShadow({ mode: 'open' });
    const container = document.createElement('div');
    container.setAttribute('id', 'container');
    container.innerText = "hello, my custom element";
    shadowRoot.appendChild(container);
  }
}
customElements.define('my-element', MyElement);
</code>

The custom element can be used in HTML as follows:

<code>&lt;html&gt;
  &lt;head&gt;
    &lt;script src="./my-element.js"&gt;&lt;/script&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;my-element&gt;&lt;/my-element&gt;
  &lt;/body&gt;
&lt;/html&gt;
</code>

CSS selectors such as

:host

and

:host-context()

allow styling of the shadow root. Example:

<code>:host-context(h1) {
  background-color: red;
}
</code>

Validation of a Web Components‑Based Plugin

A React project created with

create‑react‑app

is used for testing. The

my-element.js

file implements a custom element that renders an icon; clicking the icon opens a modal displaying attributes

x

and

y

. The element is added to

public/index.html

and used in

App.js

.

Demo of the custom element in React
Demo of the custom element in React

The full implementation of

my-element.js

is shown below:

<code>class MyElement extends HTMLElement {
  constructor() {
    super();
    this.init();
    this.open = false;
    this.triggerOpen = this.triggerOpen.bind(this);
    this.triggerClose = this.triggerClose.bind(this);
  }
  init() {
    const shadowRoot = this.attachShadow({ mode: 'open' });
    const style = document.createElement('style');
    style.textContent = `
      #container { height: 100% }
      .icon-wrapper { display: flex; align-items: center; justify-content: center; height: 40px; width: 40px; border-radius: 100%; overflow: hidden; background-color: #fff; box-shadow: 0 2px 4px rgb(206,224,245); cursor: pointer; }
      .icon-wrapper:hover { box-shadow: 0 4px 6px rgba(57,85,163,0.8); }
      .icon-wrapper svg { width: 20px; height: 20px; }
      .modal-wrapper { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0,0,0,0.3); visibility: hidden; transform: scale(0); transition: opacity 0.25s 0s, transform 0.25s; }
      .modal-wrapper.show { visibility: visible; transform: scale(1.0); }
      .modal-content { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); width: 300px; background-color: white; border-radius: 2px; padding: 12px; max-height: 300px; }
    `;
    const container = document.createElement('div');
    container.setAttribute('id', 'container');
    const iconWrapper = document.createElement('div');
    iconWrapper.setAttribute('class', 'icon-wrapper');
    iconWrapper.innerHTML = `
      <svg viewBox="0 0 1024 1024" width="200" height="200"><path d="..."/></svg>
    `;
    const modalWrapper = document.createElement('div');
    modalWrapper.setAttribute('class', 'modal-wrapper');
    const content = document.createElement('div');
    content.setAttribute('class', 'modal-content');
    modalWrapper.appendChild(content);
    container.appendChild(iconWrapper);
    container.appendChild(modalWrapper);
    shadowRoot.appendChild(style);
    shadowRoot.appendChild(container);
  }
  connectedCallback() {
    const iconWrapper = this.shadowRoot.querySelector('#container .icon-wrapper');
    iconWrapper.addEventListener('click', this.triggerOpen);
    const maskWrapper = this.shadowRoot.querySelector('#container .modal-wrapper');
    maskWrapper.addEventListener('click', this.triggerClose);
  }
  disconnectedCallback() {
    const wrapper = this.shadowRoot.querySelector('#container .icon-wrapper');
    wrapper && wrapper.removeEventListener('click', this.triggerOpen);
    const maskWrapper = this.shadowRoot.querySelector('#container .modal-wrapper');
    maskWrapper && maskWrapper.removeEventListener('click', this.triggerClose);
  }
  triggerOpen() {
    const modalWrapper = this.shadowRoot.querySelector('#container .modal-wrapper');
    if (modalWrapper) {
      const maskContent = modalWrapper.querySelector('.modal-content');
      maskContent.innerHTML = `
        <p>x: ${this.getAttribute('x')}</p>
        <p>y: ${this.getAttribute('y')}</p>
      `;
      modalWrapper.classList.add('show');
    }
  }
  triggerClose() {
    const modalWrapper = this.shadowRoot.querySelector('#container .modal-wrapper');
    modalWrapper.classList.remove('show');
  }
}
customElements.define('my-element', MyElement);
</code>

Because writing native Web Components can be verbose, libraries such as Lit allow developers to author components with a React‑like syntax, improving productivity. Other component libraries like Wired Elements and Lithops UI are also available.

Summary

Shadow DOM provides style isolation while allowing shared DOM structures.

Data can be passed via attributes (strings) or properties (complex types).

Loading a JavaScript file for a custom element enables dynamic updates and cache negotiation.

The team will continue exploring Web Components to enable more plugin scenarios beyond charting.

JavaScriptfrontend developmentreactplugin architectureWeb ComponentsEChartsCustom Charts
GuanYuan Data Tech Team
Written by

GuanYuan Data Tech Team

Practical insights from the GuanYuan Data Tech Team

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.