angular-chart.js icon indicating copy to clipboard operation
angular-chart.js copied to clipboard

[BUG] Responsive chart does not resize when container gets smaller

Open JewelsJLF opened this issue 7 years ago • 21 comments

I am not sure if this is an issue with Chart.js or angular-chart so I posted this in both projects...

Expected Behavior

When responsive is set to true, chart should resize whether container size increases or decreases.

Current Behavior

When responsive is set to true, chart only resizes when container size increases and does not resize when it decreases.

Context

I'm using angular-chart with Chart.js and bootstrap:

<div class="container-fluid">
    <div class="row">
        <div class="col-xs-12">
            <div class="form-group">
                <canvas id="base" class="chart-bar"
                  chart-data="ctrl.chart.data"
                  chart-labels="ctrl.chart.labels"
                  chart-colors="ctrl.chart.colors"
                  chart-dataset-override="ctrl.chart.datasetOverride"
                  chart-options="ctrl.chart.options">
                </canvas>
            </div>
        </div>
    </div>
</div>

Environment

  • Angular-chart version: 1.1.1
  • Chart.js version: 2.5.0
  • Browser name/version: Firefox ESR 45.8.0 / Internet Explorer 11.0.9600.18499 / Chrome 56.0.2924.87

JewelsJLF avatar Mar 15 '17 16:03 JewelsJLF

Any solution of this question

DurgpalSingh avatar Sep 22 '17 08:09 DurgpalSingh

I would first try to use $scope.$emit('$resize') within your controller. This should activate the angular-chart $scope.$on('$resize') function within the source. You can put a break point here to see if its getting hit.

If that doesnt work then it may have to do with your flex not shrinking the canvas on page resize. Try to set min-width: 0 and see if that fixes your issue, if it does then you can adjust the min-width to be the appropriate size.

Adding the min-width on my canvas container (which was using flex) as well as calling $scope.$emit('$resize') solved the problem for me.

mattkwiecien avatar May 08 '18 22:05 mattkwiecien

I am using React, but have been running into this issue too and think this must be a compatibility issue with CSS Grid/CSS Flexbox.

I have a container that is set with max-height: 100%; max-width: 100%; width: 100%; height: 100%; and as the container grows, my chart expands to fill the extra space. But when I reduce the size of my window, my container (whose height/width is specified in fr units) will not respect my max-height: 100%; min-height: 100%; rules. Subsequently, an onResize() event won't fire because the parent container will maintain the height/width values from when the chart was expanded to it's largest state, and therefor the graph will remain the same size.

Again I am coming from React but believe this issue lies with CSS Grid/CSS Flexbox not respecting the max-height/max-width parameters. A workaround in React is to remove the chart element from the page entirely, and place it back immediately afterwards. This allows the flexible container to resize down to your new height and the chart to fill the contents of the resized flexible container. I am not sure how to go about implementing this in Angular but hope this might shed some light on ways you might go about achieving the same result.

SpencerDuball avatar Aug 02 '18 19:08 SpencerDuball

It's been 5mo since the last comment, has there been any official resolution to this issue?

dcp3450 avatar Feb 04 '19 19:02 dcp3450

In the resize: function(silent) the new width and height are calculated based on the size of the chart's container

var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));

The problem is that the container will never get to a lower size than the contained canvas.

desrocchi avatar Feb 28 '19 14:02 desrocchi

This is a solution that works for me

		resize: function(silent) {
			var me = this;
			var options = me.options;
			var canvas = me.canvas;
			var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;

		// Edited to add aspectratio support
		// I set the style as dynamic so that the container can actually shrink
			if (isNaN(aspectRatio) || aspectRatio == null) {
				canvas.style.width = '100%';
				canvas.style.height = '100%';
			}
			else if (aspectRatio>=1) {
				canvas.style.width = '100%';
				canvas.style.height = Math.floor(canvas.clientWidth / aspectRatio) + 'px';
			} else {
				canvas.style.width = Math.floor(canvas.clientHeight * aspectRatio) + 'px';
				canvas.style.height = '100%';
			}

		// I read the new size of the canvas			
			var newWidth = canvas.clientWidth;
			var newHeight = canvas.clientHeight;

			if (me.width === newWidth && me.height === newHeight) {
				return;
			}

			canvas.width = me.width = newWidth;
			canvas.height = me.height = newHeight;

			helpers.retinaScale(me, options.devicePixelRatio);

			if (!silent) {
				// Notify any plugins about the resize
				var newSize = {width: newWidth, height: newHeight};
				plugins.notify(me, 'resize', [newSize]);

				// Notify of resize
				if (me.options.onResize) {
					me.options.onResize(me, newSize);
				}

				me.stop();
				me.update({
					duration: me.options.responsiveAnimationDuration
				});
			}
		}

desrocchi avatar Feb 28 '19 15:02 desrocchi

As @SpencerDuball noted, this may have something to do with CSS Grid/Flex-Box.

I at least stumbled upon the same problem when using ChartJS inside a Grid.

This is my basic markup:

<div class="grid">
  <div class="chart">
       <div style="position:relative;width:100%;height:100%">
           <canvas id="0df68a4e2b53cf460531e053e9ba128489951b86"></canvas>
     </div>
  </div>
</div>

Here, div.grid defines a CSS grid.

I managed to make it work by explicitly assigning "overflow: auto" to the div surrounding the Chart.js canvas container:

.chart {
  overflow: auto;
}

gerdriesselmann avatar May 06 '19 13:05 gerdriesselmann

Hi @jtblin are you able to give any pointers/advice on this issue please? I am using CSS Grid and when resizing the page/grid up the charts grow fine but when resizing back down will break. If you refresh at the smaller resized viewport it works just fine until resizing smaller again.

Repro - CodePen

https://codepen.io/warrenbuckley/pen/VJVRKM

  • ✔️ Resize the page & graphs in grid cell items resize correctly
  • ❌ Resize the page back down & the graphs stay at size & breaks CSS grid

My Hacky Fix

A hacky fix I got from help of a colleague @fALKENdk was to specify/override the canvas width to be 100%

canvas
{
    width: 100% !important;
}

Hacky Canvas Fix - CodePen

https://codepen.io/warrenbuckley/pen/LKMZZx

Is this repository maintained anymore @jtblin as a proper fix would be nice to have please 👍

warrenbuckley avatar Jul 09 '19 13:07 warrenbuckley

I did it like below. And it worked perfectly on all resolutions.

.chart-container {
    position:relative;
    height:200px;
    width:200px;
}

/* add responsive css */

canvas
{
    width: 100% !important;
    height: 100% !important;
}
<div class="chart-container">
    <canvas id="chart"></canvas>
</div>

mustkem avatar Oct 10 '19 10:10 mustkem

This issue saved me after 3 straight hours of debugging. I'm going to leave some keywords here, so that other people can find it if they have my problem: Bootstrap chart.js not stacking vertically on resize col col-md col-sm row bootstrap 4. Sorry for barging in like this.

Both

canvas {
    width: 100% !important;
}

or

.chart-container {
  overflow: auto;
}

worked perfectly for me.

Thanks a lot!!

danielegrattarola avatar Oct 12 '19 22:10 danielegrattarola

As @SpencerDuball noted, this may have something to do with CSS Grid/Flex-Box.

I at least stumbled upon the same problem when using ChartJS inside a Grid.

This is my basic markup:

<div class="grid">
  <div class="chart">
       <div style="position:relative;width:100%;height:100%">
           <canvas id="0df68a4e2b53cf460531e053e9ba128489951b86"></canvas>
     </div>
  </div>
</div>

Here, div.grid defines a CSS grid.

I managed to make it work by explicitly assigning "overflow: auto" to the div surrounding the Chart.js canvas container:

.chart {
  overflow: auto;
}

THANK YOU

mamatela avatar Jun 17 '20 10:06 mamatela

As @SpencerDuball noted, this may have something to do with CSS Grid/Flex-Box.

I at least stumbled upon the same problem when using ChartJS inside a Grid.

This is my basic markup:

<div class="grid">
  <div class="chart">
       <div style="position:relative;width:100%;height:100%">
           <canvas id="0df68a4e2b53cf460531e053e9ba128489951b86"></canvas>
     </div>
  </div>
</div>

Here, div.grid defines a CSS grid.

I managed to make it work by explicitly assigning "overflow: auto" to the div surrounding the Chart.js canvas container:

.chart {
  overflow: auto;
}

This one worked for me! Thanks a lot!!

tiagomnferreira-zz avatar Jul 22 '20 09:07 tiagomnferreira-zz

I had this same problem, and none of the solutions here worked for me. I was using Flex and Grid to position all of my elements. The following solution worked for me:

chart = new Chart("chart", {
	data,
	options: {
		maintainAspectRatio: false
	}
}
<div class="Graph">
	<div>
		<canvas id="chart"></canvas>
	</div>
</div>
.Graph {
	position: relative;
}

.Graph > div {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
}

This forces the inner div to have no say in the DOM sizing. It just follows the parent div's size and position.

Zenith2198 avatar Oct 22 '20 13:10 Zenith2198

As @SpencerDuball noted, this may have something to do with CSS Grid/Flex-Box.

I at least stumbled upon the same problem when using ChartJS inside a Grid.

This is my basic markup:

<div class="grid">
  <div class="chart">
       <div style="position:relative;width:100%;height:100%">
           <canvas id="0df68a4e2b53cf460531e053e9ba128489951b86"></canvas>
     </div>
  </div>
</div>

Here, div.grid defines a CSS grid.

I managed to make it work by explicitly assigning "overflow: auto" to the div surrounding the Chart.js canvas container:

.chart {
  overflow: auto;
}

Thank this worked for me too. But I would like to clarify a bit. The trick is to set the grid container's direct child (the grid item) in which the chart.js canvas resides to overflow: auto.

BigPackie avatar Nov 06 '20 10:11 BigPackie

I had this same problem, and none of the solutions here worked for me. I was using Flex and Grid to position all of my elements. The following solution worked for me:

chart = new Chart("chart", {
	data,
	options: {
		maintainAspectRatio: false
	}
}
<div class="Graph">
	<div>
		<canvas id="chart"></canvas>
	</div>
</div>
.Graph {
	position: relative;
}

.Graph > div {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
}

This forces the inner div to have no say in the DOM sizing. It just follows the parent div's size and position.

It works for me in Bootstrap 5 (currently in beta).

cristiammercado avatar Dec 23 '20 19:12 cristiammercado

I fix my issue by setting time function.

setTimeout(() => { //charts here }, 1000);

jeffnyalik avatar May 22 '21 17:05 jeffnyalik

Unfortunately none of the solutions here work for me. I don't like at all the solution I ended up using, but here it goes (Angular):

this.chartOptions = {
      responsive: true,
      resizeDelay: 100,
      aspectRatio: 1.5,
      // ...
}

@HostListener('window:resize', [])
  onResize() {
    // when the window resize force the chart to resize to smaller sizes (-100 on both width/height worked for my example).
    // after the resize delay it will automatically re-adjust. Not having the resize delay will show a very visible flicker
    this.chart.resize(this.chart.chart.width - 100, this.chart.chart.height - 100);
  }

victorbadila avatar Feb 14 '22 16:02 victorbadila

In my case, I used flex , Angular and my chart was responsive

chart.html <canvas id="barChart" #barChart></canvas>

chart.scss :host { display: flex; align-items: center; justify-content: center; flex: 1; overflow: hidden; position: relative; }

#barChart { width: 100% !important; height: 100% !important; }

takarka avatar May 13 '22 05:05 takarka

This is the key, because chart.js explicitly states they are sizing the canvas based on the parent element of the canvas. But if the parent is not forced to shrink, then it won't, meaning whatever size the canvas was stretched/expanded, it will keep the parent also "big" and not let it shrink. Having the parent of the canvas anchored to the edges of the "parent parent" is the key I think. Edit: thus I think this is "by design" as it is a constraint coming from chart.js itself, not any wrapping lib (I had this same issue with the plain js lib not the angular/vue/whatever wrapper)

seekingtheoptimal avatar Aug 05 '22 19:08 seekingtheoptimal

I had this same problem, and none of the solutions here worked for me. I was using Flex and Grid to position all of my elements. The following solution worked for me:

chart = new Chart("chart", {
	data,
	options: {
		maintainAspectRatio: false
	}
}
<div class="Graph">
	<div>
		<canvas id="chart"></canvas>
	</div>
</div>
.Graph {
	position: relative;
}

.Graph > div {
	position: absolute;
	top: 0;
	left: 0;
	right: 0;
	bottom: 0;
}

This forces the inner div to have no say in the DOM sizing. It just follows the parent div's size and position.

It works for me in Bootstrap 5 (currently in beta).

This solution (which is explained just above here by @seekingtheoptimal ) is the one that worked for me. Thank you @Rollinc2198 you saved the day!

IsabellaRey avatar Nov 22 '22 17:11 IsabellaRey

I came up with some 'special' solution for react. That kind work for me

  const options = {
    responsive: true,
    layout: {
      padding: 45,
      cutoutPercentage: 100,
    },
    plugins: {
      datalabels: {
      },
      legend: {
        display: false
      },
      tooltip: {
        enabled: false
      }
    }
  };

return (
    <div>
      <Doughnut
        style={{
          display: 'unset'
        }}
        data={chartData}
        options={options}
      />
    </div >
  );

Messa1 avatar Oct 11 '23 05:10 Messa1