[Chartjs]-Resize chart.js canvas for printing

10👍

The problem is the chart does not fit the new print width dimension. Fortunately we can perform a redraw when print is initiated.

The solution is to render your chart to an image using the canvas.toDataURL() method when printing is invoked, then switch it back to the canvas chart after printing. More on toDataURL().

To detect when to invoke your functions webkit provide the method window.matchMedia('print') (which will be true during print or false again afterwards) while other browsers use window.onbeforeprint and window.onafterprint. More on print methods.

All that is then left to do is ensure the image is responsive using CSS, i.e. scales to 100% width.

32👍

If your charts are simple, try using just using css

@media print {
    canvas.chart-canvas {
        min-height: 100%;
        max-width: 100%;
        max-height: 100%;
        height: auto!important;
        width: auto!important;
    }
}

0👍

I used a custom print style for the canvas with a custom right padding:

@media print {
  .chartCanvas {
    padding-right: 1cm;
  }
}

0👍

Following the links and Reggie Pinkham’s example I was able to make this work in Angular 2. The example is in typescript but reader’s should be able to adapt this to regular JavaScript fairly easily to work in other projects. Note I have only tested this in the latest version of Chrome on a Linux box as that is just fine for my internal company project.

// imports left out here
export class ScaleChartComponent implements OnInit {
private chart:Chart;
private chartNode:HTMLCanvasElement;
private chartImage:HTMLImageElement;
private mediaQueryListener:MediaQueryListListener;

// el is a reference to the root node of my angular html component think of it as just a div container.
constructor(private el:ElementRef) { }
ngOnDestroy() {
  this.chart.clear();
  this.chart.destroy();
  this.chart = null;
  this.cleanupPrint();
}

ngAfterViewInit() {
  this.drawChart();
  this.setupPrint();
}
// need to setup the event listeners here and hold a reference to cleanup afterwards.
public setupPrint() {
  if (!window.matchMedia) {
    return;
  }

  var mediaQueryList = window.matchMedia('print');
  this.mediaQueryListener = this.handlePrintMediaChange.bind(this);
  mediaQueryList.addListener(this.mediaQueryListener);
}
// make sure to cleanup the reference after the fact.
public cleanupPrint() {
  if (!window.matchMedia) {
    return;
  }
  var mediaQueryList = window.matchMedia('print');
  mediaQueryList.removeListener(this.mediaQueryListener);
  this.mediaQueryListener = null;
}

// here's where the magic happens. I first grab the image
// then 
public handlePrintMediaChange(mql) {
  if (mql.matches) {
    let dataUrl = this.chartNode.toDataURL('image/png');
    
    if (this.chartNode && this.chartNode.parentNode) {
      this.chartImage = new Image();
      this.chartImage.src = dataUrl;
      this.chartNode.parentNode.appendChild(this.chartImage);
      this.chartService.destroyChart(this.chart);
      this.chartNode.parentNode.removeChild(this.chartNode);
    }

  } else {
  // here is where we switch back to non print mode.
    if (this.chartImage) {
      if (this.chartImage.parentNode) {
        this.chartImage.parentNode.removeChild(this.chartImage);
      }
      this.chartImage = null;
    }
    // go through and recreate the entire chart again.
    this.drawChart();
  }
}

public drawChart() {
  var chartData = {}; // setup your chart data here.
  this.chartNode = this.createChartNode();
  if (this.chartNode) {
    this.chart = ; // execute your chart.js draw commands here.
  }
}
private createChartNode() {
   let componentElement = this.el.nativeElement as Element;
   let element = componentElement.querySelector(".scale-chart-container");
  if (!element) {
    console.error("could not find element with class .scale-chart-container");
    return null;
  }

  let chartNode = document.createElement("canvas") as HTMLCanvasElement;
  element.appendChild(chartNode);
  chartNode = chartNode;
  return chartNode;
}
}

Note that I’ve left out the Chart.js commands and data as that will vary based upon the person. I have an external service that deals with that which I’ve left out.

I hope this helps anyone who’s scratching their head on how to go off of Reggie’s answer.

0👍

Here’s an easy fix based on Reggie’s answer which is a little easier to implement than Stephen’s answer:

//Fix chartjs printing:
window.onbeforeprint = (ev) => {
    for (var id in Chart.instances) {
        let instance = Chart.instances[id];
        let b64 = instance.toBase64Image();
        let i = new Image();
        //Instead of the below, you could save something on the
        //chart that decides what to change this to. This worked for me
        //however:
        i.style.maxWidth = "100vw";
        i.src = b64;
        let parent = instance.canvas.parentNode;
        instance.tempImage = i;
        instance.oldDisplay = instance.canvas.style.display;
        instance.canvas.style.display = "none";
        parent.appendChild(i);
    }
};

window.onafterprint = (ev) => {
    for (var id in Chart.instances) {
        let instance = Chart.instances[id];
        if (instance.tempImage) {
            let parent = instance.canvas.parentNode;
            parent.removeChild(instance.tempImage);
            instance.canvas.style.display = instance.oldDisplay;
            delete instance.oldDisplay;
            delete instance.tempImage;
        }
    }
};

Leave a comment