apexcharts / apexcharts.js

📊 Interactive JavaScript Charts built on SVG
https://apexcharts.com
MIT License
14.22k stars 1.29k forks source link

Not able to use ApexCharts.js as web-component #1332

Closed ulshv closed 4 years ago

ulshv commented 4 years ago

Great lib! Anyways, I'm not able to use it in new project which is based on web-components with LitElement. It's throwing this error:

DetectElementResize.js:79 Uncaught (in promise) TypeError: Failed to execute 'getComputedStyle' on 'Window': parameter 1 is not of type 'Element'.
    at push../node_modules/apexcharts/src/utils/DetectElementResize.js.window.addResizeListener (DetectElementResize.js:79)
    at apexcharts.js:113
    at new Promise (<anonymous>)
    at ApexCharts.render (apexcharts.js:90)
    at HTMLElement.<anonymous> (hs-line-chart.ts:29)

Code example:

import { LitElement, html } from 'lit-element';
import ApexCharts from 'apexcharts';

class LineChart extends LitElement {
  static get is() {
    return 'line-chart';
  }

  async renderChart() {
    await this.updateComplete;

    var options = {
      theme: 'dark',
      chart: {
        type: 'line',
      },
      series: [{
        name: 'sales',
        data: [30,40,35,50,49,60,70,91,125]
      }],
      xaxis: {
        categories: [1991,1992,1993,1994,1995,1996,1997, 1998,1999]
      }
    }

    var chart = new ApexCharts(this.shadowRoot.querySelector("#container"), options);

    chart.render();
  }

  render() {
    return html`<div id="container"></div>`;
  }
}

window.customElements.define(LineChart.is, LineChart);

export default LineChart;
ulshv commented 4 years ago

DocumentFragment is passed instead of HTMLElement object in getComputedStyle. is there any workaround to fix it?

ulshv commented 4 years ago

Ok, found the workaround. It basically disables shadow-root for custom element (using LitElement). Issue resolved.

class LineChart extends LitElement {
  // ...

  createRenderRoot() {
    return this;
  }

  async renderChart({ series, xaxis }) {
    await this.updateComplete;

    var options = { /* ... */ };

    const div = document.createElement('div');
    this.appendChild(div);

    var chart = new ApexCharts(div, options);

    chart.render();
  }

  render() {}
}
oneezy commented 3 years ago

hi @ulshv , i've never worked with LitElement before but I have worked with Polymer a few years back. I heard the 2 are similar. Anyways, do you know how to take your code and use it within a vanilla js web component? It seems straight forward but I haven't gotten it to work. I was attempting to get your example above to work with LitElement first before I convert it into a a vanilla component but I can't even get that to work 😄

Do you know what I'm doing wrong? https://codepen.io/oneezy/pen/jOVyePM

dazraf commented 3 years ago

Hi @ulshv thanks for creating this issue. I'm trying to do something similar with LitElement. Did you run into any issues with ApexCharts css not being loaded / accessed? Thanks.

dazraf commented 3 years ago

I was able to directly load the css within the render method.

 render() {
    return html`
      <link rel="stylesheet" type="text/css" href="./node_modules/apexcharts/dist/apexcharts.css">
      <div class="chart-container"></div>
      `;
  }

However, this approach is strongly not recommended by LitElement.

I also tried copying the entire css into the elements static styles = css... block, but this didn't work.

Hi @ulshv thanks for creating this issue. I'm trying to do something similar with LitElement. Did you run into any issues with ApexCharts css not being loaded / accessed? Thanks.

LeaVerou commented 2 years ago

Ok, found the workaround. It basically disables shadow-root for custom element (using LitElement). Issue resolved.

class LineChart extends LitElement {
  // ...

  createRenderRoot() {
    return this;
  }

  async renderChart({ series, xaxis }) {
    await this.updateComplete;

    var options = { /* ... */ };

    const div = document.createElement('div');
    this.appendChild(div);

    var chart = new ApexCharts(div, options);

    chart.render();
  }

  render() {}
}

That is only a workaround if you don’t actually need the Shadow DOM. Also, this is not LitElement specific, I'm having the same issue with an entirely VanillaJS component I‘m writing. Here's a reduced testcase: https://codepen.io/leaverou/pen/WNEXzBe?editors=1010 I think the issue should be reopened.

LeaVerou commented 2 years ago

Here's a workaround that enables Apex Charts to work in Shadow DOM (but reduces browser support to those that support ResizeObserver and WeakMap, unless polyfills/shims are used):

let ros = new WeakMap();

window.addResizeListener = function(el, fn) {
    let called = false;

    let ro = new ResizeObserver(r => {
        if (called) { // We don't want to call it immediately
            fn.call(el, r);
        }
        called = true;
    });

    if (el.nodeType === Node.DOCUMENT_FRAGMENT_NODE){
        // Observe children instead
        Array.from(el.children).forEach(c => ro.observe(c));
    }
    else {
        ro.observe(el);
    }

    ros.set(fn, ro);
};

window.removeResizeListener = function(el, fn) {
    let ro = ros.get(fn);
    if (ro) {
        ro.disconnect();
        ros.delete(fn);
    }
}

The code would need to run after Apex Charts are imported, to correctly override the addResizeListener() and removeResizeListener() that ship with it.

It's just something I put together right now, and has not been extensively tested, but it's a starting point.

(You'd still need to work around the CSS not being imported properly, see #238)