Home > Software engineering >  How to pass DOM elements for libraries (eg. ChartJS, Hightcharts) in Virtual DOMs (such as Qwik)?
How to pass DOM elements for libraries (eg. ChartJS, Hightcharts) in Virtual DOMs (such as Qwik)?

Time:12-13

Background

I have personally used React, Vue and Angular extensively in the past. And a lot of times I need to create applications with charts generated within them from selective data. I'm recently trying out Qwik due to its promise of speed and attempted to create charts within it using ChartJs. But while ChartJs has separate libraries available for React, Vue, Angular, Svelte, etc. it does not have one for Qwik understandably.

Issue

Many plugins such as Highcharts and ChartJs often require a DOM element to be sent to its functions to identify where to render their output. But when we are dealing with virtual DOMs, I can't run JS selector scripts to fetch DOM elements and pass them into a function within a component. Therefore, as of now, I have not been able to use ChartJs in my Qwik project.

Attempts

I have only looked for solutions for this issue and not found any workable approaches. From ChartJs docs the following code is their raw JS way of implementing charts:

new Chart(
    document.getElementById('acquisitions'),
    {
      type: 'bar',
      data: {
        labels: data.map(row => row.year),
        datasets: [
          {
            label: 'Acquisitions by year',
            data: data.map(row => row.count)
          }
        ]
      }
    }
  );

As expected document.getElementById does not work inside a component and that is where I'm stuck. I've only created the useMount$() function where I expect to place the logic for generating my chart and also looked around for React solutions by perhaps using references and what not. But, other than that, I have been unable to find anything more.

I understand that looking at the source code of the React library for ChartJs would provide me clues but while I investigate a library (which I find difficult at my current level) I was hoping for a pointer to the solution from the Stack Overflow community.

Searching "ref" on the Qwik docs does not return any search results but I had found the git project from another developer online and tried to replicate the use of references from his approach:

Child component code:

import { component$, useMount$, Ref, useStylesScoped$ } from "@builder.io/qwik";
import { Chart } from 'chart.js/auto';

interface GraphProps {
  data: object[];
  reference: Ref<Element>;
}

export default component$((props: GraphProps) => {
  useStylesScoped$(styles);

  useMount$(() => {
    new Chart(
      props.reference.value,
      {
        <... options here ...>
      }
    );
  });

  return (
    <div id="chartContent">
    </div>
  );
});

Parent component code:

import { component$, useRef } from "@builder.io/qwik";
import ContentCard from "../components/contentCard/contentCard";
import ChartJSGraph from "../components/chartJSGraph/chartJSGraph";
...

export default component$(() => {
  const leftChartContainer = useRef();

  return (
    <div>
        <div className="row">
            <ContentCard>
                <div className="graph-container">
                    <ChartJSGraph
                        data={[
                        { year: 2010, count: 10 },
                        ...
                        ]}
                        reference={leftChartContainer}
                    />
                </div>
            </ContentCard>
        </div>
    </div>
  )
});

As these are just findings from a YouTuber's code it could be outdated so is certainly not necessarily a reliable source. But so far searching the official docs have not led me to any official approach for references.

CodePudding user response:

The DOM element that is passed to the charting library can only be accessed once it has been mounted to the page. Qwik/Vue/React all provide component mounted hooks.

Inside these mounted hooks you can reference your DOM element via id or querySelector or using the internal DOM reference feature of Qwuik/Vue/React and then use that when initialising the chart. The latter is the cleaner approach.

For example, in Vue:

<template>
<div id="acquisitions" ref="chartEl"></div>
</template>
<script setup>
import Chart from 'chart.js/auto';
import { ref, onMounted } from 'vue';
const chartEl = ref(null)


onMounted(() => {

 const chartOpts = {
      type: 'bar',
      data: {
        labels: data.map(row => row.year),
        datasets: [
          {
            label: 'Acquisitions by year',
            data: data.map(row => row.count)
          }
        ]
      }
    }

 new Chart(
    chartEl.value,
    chartOpts    
  );
})

</script>

CodePudding user response:

Solution

Sadly this was a silly issue of perhaps on my network side or god knows what why the search engine on the Qwik doc never suggested anything for me when I looked up "Ref" in their docs. But my problem has been solved after finding the following link:

https://qwik.builder.io/tutorial/hooks/use-signal/#example

For future reference for myself or any beginners facing the similar issue, I'm writing down my implementation below:

// Root component
import { component$, useSignal } from "@builder.io/qwik";
...
import ChartJSGraph from "../components/chartJSGraph/chartJSGraph";

export default component$(() => {
  const chartData1 = useSignal({
    labels: ["January", "February", "March", "April", "May", "June", "July"],
    datasets: [{
      label: 'Inventory Value per Outlet',
      data: [65, 59, 80, 81, 56, 55, 40],
      fill: false,
      borderColor: 'rgb(75, 192, 192)',
      tension: 0.1
    }]
  });

  return (
    <div >
    ...
        <ChartJSGraph
          width={'100%'}
          height={'25px'}
          chartData={chartData1.value}
        />
    </div>
  );
});

And here's the code for my ChartJSGraph component that uses the data supplied to generate the chart while using the reference of the canvas element to point to ChartJS where to create the chart.

// ChartJSGraph component
import { component$, useClientEffect$, useSignal } from "@builder.io/qwik";
import { Chart } from 'chart.js/auto';
...

interface GraphProps {
  height: string;
  width: string;
  chartData: object;
}

export default component$((props: GraphProps) => {
  const outputRef = useSignal<Element>();

  useClientEffect$(() => {
    new Chart(
      outputRef.value,
      {
        type: 'line',
        data: props.chartData
      }
    );
  });

  return (
    <>
      <canvas ref={outputRef} width={props.width} height={props.height}>
      </canvas>
    </>
  );
});
  • Related