Logo Spiria

Comment exporter des graphiques HTML5 en PDF gratuitement

8 septembre 2015.

Dans leur travail, de nombreux développeurs ont dû créer des graphiques pour exportation dans un rapport PDF. La création de ces graphiques est onéreuse : créer une image pour visionnement en code HTML, re-création de cette même image côté serveur, enfin insertion dans un document PDF créé en cours de route.

Heureusement, tout ceci n’est plus qu’un mauvais souvenir. Grâce à HTML 5, il est désormais possible de créer des graphiques sans avoir à créer d’image d’abord, à l’aide d'une nouvelle interface de programmation en Javascript. Il suffit d’utiliser une librarie gratuit qui convertira une page HTML en page PDF en interprétant HTML, Javascript et les feuilles de style en cascade (« CSS »).

D’abord, choisissez une bibliothèque graphique. 

J’ai choisi Google Charts, que l’on trouve à l’adresse https://developers.google.com/chart/.

J’aime Google Charts pour la fiabilité de Google, parce que c’est gratuit, qu’ils offrent tout un tas de graphiques originaux et que je suis un fervent du « Material Design » de Google. Notez que Google emploie des graphiques SVG, alors que d’autres bibliothèques utilisent les graphiques Canvas. Pour vérifier la compatibilité avec le fureteur, allez au site Web caniuse.com, qui vous dira si la fonctionnalité que vous recherchez est compatible avec tel ou tel fureteur. Quant à moi, je teste la compatibilité SVG des fureteurs les plus répandus.

Voici un example en cinq étapes.

Étape 1 : Ajouter une référence à Google Charts

<script type="text/javascript" src="https://www.google.com/jsapi"></script>

Étape 2 : Télécharger les classes

google.load("visualization", "1", { packages: ["corechart"] });

Étape 3 : Implémenter et définir le téléchargement de chaque graphique

    google.setOnLoadCallback(drawChatter);
    google.setOnLoadCallback(drawPie);
    google.setOnLoadCallback(drawDonut);
    google.setOnLoadCallback(drawLines);
    google.setOnLoadCallback(drawStacked);

    function drawChatter() {
        var data = google.visualization.arrayToDataTable([
            ['Age', 'Weight'],
            [8, -12],
            [-4, 5.5],
            [11, 14],
            [4, 5],
            [-3, -3.5],
            [6.5, -7]
        ]);

        var options = {
            title: 'Scatter chart',
            hAxis: { title: 'X', minValue: -20, maxValue: 20 },
            vAxis: { title: 'Y', minValue: -20, maxValue: 20 },
            legend: 'none'
        };

        var chart = new google.visualization.ScatterChart(document.getElementById('chartContainer5'));

        chart.draw(data, options);
    }
    
    function drawPie() {

        var data = google.visualization.arrayToDataTable([
            ['Task', 'Hours per Day'],
            ['Work', 11],
            ['Eat', 2],
            ['Commute', 2],
            ['Watch TV', 2],
            ['Sleep', 7]
        ]);

        var options = {
            title: 'Pie chart'
        };

        var chart = new google.visualization.PieChart(document.getElementById('chartContainer4'));

        chart.draw(data, options);
    }

    function drawDonut() {
        var data = google.visualization.arrayToDataTable([
          ['Task', 'Hours per Day'],
          ['Work', 11],
          ['Eat', 2],
          ['Commute', 2],
          ['Watch TV', 2],
          ['Sleep', 7]
        ]);

        var options = {
            title: 'Donut Chart',
            pieHole: 0.4,
        };

        var chart = new google.visualization.PieChart(document.getElementById('chartContainer3'));
        chart.draw(data, options);
    }

    function drawLines() {

        var data = new google.visualization.DataTable();
        data.addColumn('number', 'X');
        data.addColumn('number', 'Y');
        var y = 0;
        var x = 0;
        for (var i = 0; i < 1000; i += 1) {
            y += (Math.random() * 10 - 5);
            x = i - 1000 / 2;
            data.addRows();
        }
       
        var options = {
            hAxis: {
                title: 'Y'
            },
            vAxis: {
                title: 'X'
            }
        };

        var chart = new google.visualization.LineChart(document.getElementById('chartContainer2'));

        chart.draw(data, options);
    }

    function drawStacked() {
        var data = new google.visualization.DataTable();
        data.addColumn('timeofday', 'X');
        data.addColumn('number', 'Motivation Level');
        data.addColumn('number', 'Energy Level');
        data.addColumn('number', 'Another');

        data.addRows([
          [{ v: [8, 0, 0], f: '8' }, 1, 1.25, 1.25],
        [{ v: [9, 0, 0], f: '9' }, 2, 1.5, 1.75],
        [{ v: [10, 0, 0], f: '10' }, 3, 2, 2.25],
        [{ v: [11, 0, 0], f: '11' }, 4, 3.25, 1.5],
        [{ v: [12, 0, 0], f: '12' }, 5, 3.25, 1.25],
        [{ v: [13, 0, 0], f: '13' }, 6, 4, 1.25],
        [{ v: [14, 0, 0], f: '14' }, 7, 5, 3.25],
        [{ v: [15, 0, 0], f: '15' }, 8, 6.25, 1.75],
        [{ v: [16, 0, 0], f: '16' }, 9, 8.5, 4.25],
        [{ v: [17, 0, 0], f: '17' }, 10, 11, 2.75],
        ]);

        var options = {
            title: 'Stacked Bar',
            isStacked: true,
            hAxis: {
                title: 'Y',
                viewWindow: {
                    min: [7, 30, 0],
                    max: [17, 30, 0]
                }
            },
            vAxis: {
                title: 'x'
            }
        };

        var chart = new google.visualization.ColumnChart(document.getElementById('chartContainer1'));
        chart.draw(data, options);

 

Étape 4 : Créer un caractère de remplissage html (« placeholder ») pour chaque graphique

<style type="text/css">.myblock { padding: 5px; text-align: center; vertical-align: middle; }</style>

Voici le résultat : http://chartssample.azurewebsites.net/

Deuxièmement, téléchargez une trousse NuGet gratuite ici : Select.Pdf

La documentation se trouve ici : http://selectpdf.com/

Example côté serveur (ASP.NET MVC C#) : 

using System;
using System.Web.Mvc;
using SelectPdf;
using PdfPageOrientation = SelectPdf.PdfPageOrientation;
using PdfPageSize = SelectPdf.PdfPageSize;

namespace WebApplication1.Controllers
{
    public class HomeController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }

        public ActionResult Report()
        {
            SelectPdf.GlobalProperties.HtmlEngineFullPath = HttpContext.ApplicationInstance.Server.MapPath("~/App_Data/Select.Html.dep");
            try
            {
                string pdf_page_size = "A4";
                PdfPageSize pageSize = (PdfPageSize) Enum.Parse(typeof (PdfPageSize),
                    pdf_page_size, true);

                string pdf_orientation = "Portrait";
                PdfPageOrientation pdfOrientation =
                    (PdfPageOrientation) Enum.Parse(typeof (PdfPageOrientation),
                        pdf_orientation, true);

                int webPageWidth = 1600;

                int webPageHeight = 0;

                // instantiate a html to pdf converter object
                HtmlToPdf converter = new HtmlToPdf();

                // set converter options
                converter.Options.PdfPageSize = pageSize;
                converter.Options.PdfPageOrientation = pdfOrientation;
                converter.Options.WebPageWidth = webPageWidth;
                converter.Options.WebPageHeight = webPageHeight;

                // create a new pdf document converting an url
                PdfDocument doc =
                    converter.ConvertUrl(string.Format("http://{0}:{1}/Home/Index", HttpContext.Request.Url.Host,
                        HttpContext.Request.Url.Port));

                // save pdf document
                doc.Save(HttpContext.ApplicationInstance.Response, false, "Sample.pdf");

                // close pdf document
                doc.Close();

                return Content("");
            }
            catch(Exception e)
            {
                return Content(string.Format("{0}


{1}", e.Message, (null!=e.InnerException)?e.InnerException.Message:""));
            }

        }

    }
}

Le fichier pdf produit est accessible ici : sample.pdf

Qu’en pensez-vous? :)