Search code examples
javascriptchart.jssveltesvelte-component

How to extract a Chart.js chart with reactive attributes as a separate component in Svelte?


I have created a Skeleton Project using the npm create svelte@latest myapp command. Next, I installed the Chart.js with npm install svelte-chartjs chart.js and created a simple bar chart:

<script>
  import { Bar } from 'svelte-chartjs';
  import { Chart, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js';

  Chart.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale);

  let numbers = [10, 20, 30];
  let dates = ['1/1/2024', '1/2/2024', '1/3/2024'];

  $: data = {
    labels: dates,
    datasets: [
      {
        label: 'Random Number',
        data: numbers,
        backgroundColor: 'rgba(98,  182, 239,0.4)',
        borderColor: 'rgba(98,  182, 239, 1)',
        borderWidth: 2
      }
    ]
  };

  let options = {
    responsive: true,
    maintainAspectRatio: false
  };

  async function generate() {
    const response = await fetch('/generate');
    let fetchedData = await response.json();

    dates = fetchedData.dates;
    numbers = fetchedData.numbers;
  }
</script>

<h1>Fetch Data from API endpoint</h1>

<button on:click={generate}>Request</button>

<div>
  <Bar {data} {options} />
</div>

It fetches the data from the /generate API endpoint which generates from 1 to 10 random numbers with values ranging from 10 to 30. Additionally, the dates are produced according to the length of the random numbers. If the Request button is pressed, the bar chart will update its labels and data values. The output looks like this:

enter image description here

The content of the routes/generate/+server.js file (simulating the API endpoint) that updates the numbers and dates array is the following:

import { json } from '@sveltejs/kit';

function getRandomNumber(min, max) {
  return Math.floor(Math.random() * (max - min) + min);
}

function generateDates(numDates) {
  var today = new Date();
  var dates = []
  
  for (let i = 0; i < numDates; i++) {
    let dateString = today.toLocaleDateString('en-US');
    dates.push(dateString);
    today.setDate(today.getDate() + 1);
  }

  return dates;
}

export function GET() {
  let length = getRandomNumber(1, 10);
  let numberData = Array.from({ length: length }, () => getRandomNumber(10, 30));
  let dateData = generateDates(length);
  
  return json({dates: dateData, numbers: numberData});
}

The length of the data from the endpoint is randomly selected between 1 and 10 and the starting date begins with the current date. An example output of the API endpoint:

{"dates":["2/8/2024","2/9/2024","2/10/2024"],"numbers":[14,23,23]}

I would like to extract the Bar chart-specific code into a separate component for more readability and reusability. However, the labels and the datasets[0].data attributes reactively change when fetching (generating) new data.

How to extract a Chart.js chart with reactive attributes as a separate component?


Solution

  • Create a component (e.g. BarChart.svelte) and extract the chart-specific code:

    <script>
      import { Bar } from 'svelte-chartjs';
      import { Chart, Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale } from 'chart.js';
    
      Chart.register(Title, Tooltip, Legend, BarElement, CategoryScale, LinearScale);
    
      export let dates = [];
      export let numbers = [];
    
      $: data = {
        labels: dates,
        datasets: [
          {
            label: 'Random Number',
            data: numbers,
            backgroundColor: 'rgba(98,  182, 239,0.4)',
            borderColor: 'rgba(98,  182, 239, 1)',
            borderWidth: 2
          }
        ]
      };
    
      let options = {
        responsive: true,
        maintainAspectRatio: false
      };
    </script>
    
    <Bar {data} {options} />
    

    You need to export the dates and numbers variables so they can be updated from another component. Import the new component into the original component and assign the fetched values using the bar chart's attributes:

    <script>
      import BarChart from './BarChart.svelte';
    
      let numbers = [10, 20, 30];
      let dates = ['1/1/2024', '1/2/2024', '1/3/2024'];
    
      async function generate() {
        const response = await fetch('/generate');
        let data = await response.json();
    
        dates = data.dates;
        numbers = data.numbers;
      }
    </script>
    
    <h1>Fetch Data from API endpoint</h1>
    
    <button on:click={generate}>Request</button>
    
    <div>
      <BarChart {dates} {numbers} />
    </div>
    

    Note: The BarChart.svelte component is created next to the fetching component. In the future, it is advisable to create a components folder if there are multiple components.