0

I'm working on a React project where I need to display a Chart.js chart inside a parent component which is in grid layout. The chart should resize according to its parent container while maintaining a fixed aspect ratio of 3.5:1 (width: height). However, I'm struggling to make the chart responsive without hardcoding its dimensions.

Here's my current setup:

Parent Component

   <div className="graph_container">
      <h4>{title}</h4>
      <div className="graph_container_graph">
        <DynamicLineChart data={data} key={JSON.stringify(data)} />
      </div>
    </div>

Parent Component's Styles

.graph_container {
  width: 100%;
  max-width: fit-content;
  border-radius: 1.27rem;
  background-color: #fff;
  padding: 0.95rem;
  display: flex;
  flex-direction: column;
  gap: 0.63rem;
  font-size: 1.4rem;
}

.graph_container_graph {
  padding: 0 0.95rem;
}

DynamicLineChart Component

import React, { useEffect } from 'react';
import { Line } from 'react-chartjs-2';
import { Chart as ChartJS, CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend } from 'chart.js';

ChartJS.register(CategoryScale, LinearScale, PointElement, LineElement, Title, Tooltip, Legend);

const DynamicLineChart = ({ data }) => {
  const chartData = {
    labels: Object.keys(data),
    datasets: [
      {
        data: Object.values(data),
        fill: false,
        backgroundColor: '#FFF',
        borderColor: '#6497FC',
        borderWidth: 3,
        pointBackgroundColor: '#4D5C92',
        pointBorderColor: '#4D5C92',
        pointRadius: 5,
        pointHoverRadius: 5,
      },
    ],
  };

  const options = {
    aspectRatio: 3.5,
    maintainAspectRatio: true,
    layout: {
      padding: {},
    },
    plugins: {
      legend: {
        display: false,
      },
      tooltip: {
        enabled: true,
      },
    },
    scales: {
      x: {
        grid: {
          borderDash: [15, 5],
          color: '#CDD1DB',
          borderColor: '#CDD1DB',
        },
        ticks: {
          color: '#6B7280',
          font: {
            size: 10,
          },
        },
      },
      y: {
        min: 0,
        max: 100,
        ticks: {
          font: {
            size: 10,
          },
          stepSize: 25,
          color: '#6B7280',
        },
        grid: {
          display: false,
        },
      },
    },
  };

  return (
    <div style={{ width: '100%', height: 'auto' }}>
      <Line data={chartData} options={options} />
    </div>
  );
};

export default DynamicLineChart;

The **Problem **is that Currently, the chart is not responsive unless I hardcode its width and height. I want the chart to resize based on its parent container's width, maintaining the aspect ratio of 3.5:1. How can I achieve this?

What I've Tried

  1. Setting maintainAspectRatio to true in the chart options.
  2. Wrapping the Line component in a div with a percentage-based width.
  3. Using CSS to control the parent container's size. None of these solutions have worked as expected. The chart does not resize properly and often breaks the layout.

I need the chart to adjust its width dynamically based on the parent container while keeping the height proportional to the width with an aspect ratio of 3.5:1.

Any help or suggestions would be greatly appreciated!

1 Answer 1

0

By looking at your chart options I'd say that the chart should be responsive, since the option responsive: true is on (which is so by default); it means the chart will adjust its size to the container div.

Without a runnable example one cannot be sure, but I think it's quite probable that what is happening is that the container div doesn't resize as you expected.

Consider this:

  • first step (optional), you decrease the width of the container div's container; the chart (that was perfectly filling its container at aspectRatio: 3.5 - since the container has height: auto) will shrink, the container's height (auto) will follow and also decrease and the chart is always perfectly filling the container.
  • then, you increase the width of the container's container - the width of the container (100%) will follow and increase the same. Now, however, the height of the container has no reason to increase, it will stay the same and the chart, constrained by the height of the container will not change at all, creating the appearance of not being responsive

The following snippet, using parts of the code you posted, illustrates this dynamic:

const data = {
    labels: Array.from({length: 12}, (_, i)=>'L'+(i+1)),
    datasets: [{
        data: Array.from({length: 12}, ()=>5+90*Math.random()),
        fill: false,
        backgroundColor: '#FFF',
        borderColor: '#6497FC',
        borderWidth: 3,
        pointBackgroundColor: '#4D5C92',
        pointBorderColor: '#4D5C92',
        pointRadius: 5,
        pointHoverRadius: 5,
    }]
};

const options = {
    aspectRatio: 3.5,
    maintainAspectRatio: true,
    layout: {
        padding: {},
    },
    plugins: {
        legend: {
            display: false,
        },
        tooltip: {
            enabled: true,
        },
    },
    scales: {
        x: {
            grid: {
                borderDash: [15, 5],
                color: '#CDD1DB',
                borderColor: '#CDD1DB',
            },
            ticks: {
                color: '#6B7280',
                font: {
                    size: 10,
                },
            },
        },
        y: {
            min: 0,
            max: 100,
            ticks: {
                font: {
                    size: 10,
                },
                stepSize: 25,
                color: '#6B7280',
            },
            grid: {
                display: false,
            },
        },
    },
};

new Chart('myChart', {type: 'line', options, data});
const containerEl = document.querySelector('#container'),
    container2El = document.querySelector('#containerOfContainer'),
    chartEl = document.querySelector('#myChart'),
    textarea = document.querySelector('textarea'),
    shrinkBut = document.querySelector('#shrink'),
    enlargeBut = document.querySelector('#enlarge');


let count = 1;
function updateTextArea(){
    const idx = (count < 10 ? ' ' : '') + count++,
        wCont = containerEl.offsetWidth,
        hCont = containerEl.offsetHeight,
        wChart = chartEl.offsetWidth,
        hChart = chartEl.offsetHeight;
    textarea.value = `${idx}.  h = ${hCont} w = ${wCont} (div)  |   h = ${hChart} w = ${wChart} (chart)\n` + textarea.value;
}

let wInt = 3;
shrinkBut.onclick = function(){
    disableButtons();
    container2El.style.width = (container2El.offsetWidth - 30)+'px';
    wInt--;
    setTimeout(()=>{enableButtons(); updateTextArea();}, 1000);
}

enlargeBut.onclick = function(){
    disableButtons();
    container2El.style.width = (container2El.offsetWidth + 30)+'px';
    wInt++;
    setTimeout(()=>{enableButtons(); updateTextArea();}, 1000);
}

function disableButtons(){
    shrinkBut.setAttribute('disabled', 'd');
    enlargeBut.setAttribute('disabled', 'd');
}
function enableButtons(){
    if(wInt > 0){
        shrinkBut.removeAttribute('disabled');
    }
    if(wInt < 3){
        enlargeBut.removeAttribute('disabled');
    }
}

updateTextArea();
<div id="containerOfContainer" style="width: 90vw; border: 1px solid black">
    <div id="container" style="width: 100%; height: auto; border:1px solid red">
        <canvas style="border:1px solid blue" id="myChart"></canvas>
    </div>
</div>
<button id="shrink">Shrink</button> <button id="enlarge" disabled>Enlarge</button>
<br/> Container sizes (<strong>most recent on top</strong>) <br/>
<textarea style="width: 70ex; height: 10ex"></textarea>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

In this case, the solution is with the css of the container div - replace height property with aspect-ratio. I thought initially that the aspectRatio chart.js property refers to the chart area; however, after several attempts to compute the ideal container aspect ratio, I realized what is actually clearly stated in the docs that it refers to the canvas. This simplifies things a lot, since it doesn't matter what the chart canvas contains, does it have a legend on the right or a title on top, etc. The ideal css of the container is the same as the aspectRatio of the chart, and the solution is

  <div style={{ width: '100%', aspectRatio: 3.5 }}>
     <Line data={chartData} options={options} />
  </div>

Here's the above snippet with that modification:

const data = {
    labels: Array.from({length: 12}, (_, i)=>'L'+(i+1)),
    datasets: [{
        data: Array.from({length: 12}, ()=>5+90*Math.random()),
        fill: false,
        backgroundColor: '#FFF',
        borderColor: '#6497FC',
        borderWidth: 3,
        pointBackgroundColor: '#4D5C92',
        pointBorderColor: '#4D5C92',
        pointRadius: 5,
        pointHoverRadius: 5,
    }]
};

const options = {
    aspectRatio: 3.5,
    maintainAspectRatio: true,
    layout: {
        padding: {},
    },
    plugins: {
        legend: {
            display: false,
        },
        tooltip: {
            enabled: true,
        },
    },
    scales: {
        x: {
            grid: {
                borderDash: [15, 5],
                color: '#CDD1DB',
                borderColor: '#CDD1DB',
            },
            ticks: {
                color: '#6B7280',
                font: {
                    size: 10,
                },
            },
        },
        y: {
            min: 0,
            max: 100,
            ticks: {
                font: {
                    size: 10,
                },
                stepSize: 25,
                color: '#6B7280',
            },
            grid: {
                display: false,
            },
        },
    },
};

new Chart('myChart', {type: 'line', options, data});
const containerEl = document.querySelector('#container'),
    container2El = document.querySelector('#containerOfContainer'),
    chartEl = document.querySelector('#myChart'),
    textarea = document.querySelector('textarea'),
    shrinkBut = document.querySelector('#shrink'),
    enlargeBut = document.querySelector('#enlarge');


let count = 1;
function updateTextArea(){
    const idx = (count < 10 ? ' ' : '') + count++,
        wCont = containerEl.offsetWidth,
        hCont = containerEl.offsetHeight,
        wChart = chartEl.offsetWidth,
        hChart = chartEl.offsetHeight;
    textarea.value = `${idx}.  h = ${hCont} w = ${wCont} (div)  |   h = ${hChart} w = ${wChart} (chart)\n` + textarea.value;
}

let wInt = 3;
shrinkBut.onclick = function(){
    disableButtons();
    container2El.style.width = (container2El.offsetWidth - 30)+'px';
    wInt--;
    setTimeout(()=>{enableButtons(); updateTextArea();}, 1000);
}

enlargeBut.onclick = function(){
    disableButtons();
    container2El.style.width = (container2El.offsetWidth + 30)+'px';
    wInt++;
    setTimeout(()=>{enableButtons(); updateTextArea();}, 1000);
}

function disableButtons(){
    shrinkBut.setAttribute('disabled', 'd');
    enlargeBut.setAttribute('disabled', 'd');
}
function enableButtons(){
    if(wInt > 0){
        shrinkBut.removeAttribute('disabled');
    }
    if(wInt < 3){
        enlargeBut.removeAttribute('disabled');
    }
}

updateTextArea();
<div id="containerOfContainer" style="width: 90vw; border: 1px solid black">
    <div id="container" style="width: 100%; aspect-ratio: 3.5; border:1px solid red">
        <canvas style="border:1px solid blue" id="myChart"></canvas>
    </div>
</div>
<button id="shrink">Shrink</button> <button id="enlarge" disabled>Enlarge</button>
<br/> Container sizes (<strong>most recent on top</strong>) <br/>
<textarea style="width: 70ex; height: 10ex"></textarea>

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

1
  • 1
    Thanks buddy, Your solution worked for me. For someone who might face same problem i am providing changes here in Dynamicchartcomponent- i have set options as "aspectRatio: 3.5, maintainAspectRatio: true,"
    – Codder
    Commented Jun 16 at 5:21

Not the answer you're looking for? Browse other questions tagged or ask your own question.