Publié le 20/10/2014
Auteur fobec
Réseaux sociaux
0 partages
0 tweets
2 plus
1 commentaires

Dessiner un graph sur un Canvas HTML5

Le format HTML5 a introduit une nouvelle fonctionnalité: le canvas qui permet de dessiner sur la page HTML. Comment utiliser cette propriété pour crée un graphique directement sur la page web ?

Principes du Canvas HTML5

Le canvas est une une zone de dessin incluse dans la page HTML sur laquelle on peut dessiner des formes à l'aide de Javascript.

Page HTML
L’élément de dessin est déclaré dans le code HTML avec la balise <canvas>. En général, 3 attributs doivent être renseignés:
- id: identifiant de l’élément servant à l'appel en js,
- width: largeur de la zone de dessin,
- height: hauteur de la zone de dessin.
<!DOCTYPE html>
<html>
    <head>
        <title>Exemple de graph avec canvas</title>
    </head>
    <body>
        <canvas id="myGraph" width="300" height="200"></canvas>
    </body>
</html>
Code javascript
Le canvas se compose de 2 parties: objet canvas que l'on peut comparer à un Div par exemple et le context qui est la zone de dessin à proprement parler. Pour ajouter des formes ou une animation sur le canvas, les fonctions essentielles sont les suivantes:
- getContext('2d'): retourne l'objet de dessin comparable à handle sous Windows,
- beginPath() et stroke(): prépare puis dessine une nouvelle forme sur le canvas,
- fillRect(), moveTo(), lineTo(), arc(), ...: fonctions classiques de dessins sur un canvas.

Dessiner dynamiquement un graph dans une page HTML

1153_html5_canvasLes présentations étant faites, revenons au but initiale: dessiner un graphique de type ligne pour une serie de donnée.
Le graphique ci-contre a été généré à partir directement avec l'objet Canvas en HTML5.
Pour simplifier les développements futurs, l'ensemble des fonctions de générations de graphique a été regroupée dans un objet Javascript. La structure de l'objet a été prévu pour ajouter les données à partir d'un résultat en JSON.

Class js LineGraph
function LineGraph(canvasId) {
    /**
     * Dessiner un graphique sur un Canvas HTML5
     * @author: http://www.fobec.com/tuto/1153/dessiner-graph-sur-canvas-html5.html
     */
    //Interne - ne pas modifier ces valeurs
    var canvas_ctx = null;
    var canvas_obj = null;
    var dataset = {serie: []};
    var xLabelStep;
    var yMax = 0;
 
    //const: marge gauche et droite
    var marginX = 30;
    //const: marge en haut et en bas
    var marginY = 20;
    //nombre de valeur a afficher sur axe X
    var yStep = 5;
 
 
    //Constructor
    var canvas_obj = document.getElementById(canvasId);
    canvas_ctx = canvas_obj.getContext('2d');
    //Dessiner un fond blanc
    canvas_ctx.beginPath();
    canvas_ctx.fillStyle = '#fff';
    canvas_ctx.fillRect(0, 0, canvas_obj.width, canvas_obj.height);
    canvas_ctx.stroke();
 
    /**
     * Dessiner les axes verticaux
     * @returns {undefined}
     */
    this.drawAxis = function() {
        canvas_ctx.lineWidth = 2;
        canvas_ctx.strokeStyle = '#333';
        canvas_ctx.fillStyle = '#333';
        canvas_ctx.beginPath();
        canvas_ctx.textAlign = "right"
        canvas_ctx.textBaseline = "middle";
        //label de l'axe Y
        var yVal;
        for (var i = 0; i <= yStep; i++) {
            yVal = (yMax / yStep) * i;
            canvas_ctx.fillText(Math.round(yVal), marginX - 10, yValuetoGraph(yVal));
        }
        canvas_ctx.stroke();
        //Barre verticales
        canvas_ctx.beginPath();
        canvas_ctx.strokeStyle = '#666';
        canvas_ctx.lineWidth = .5;
        for (var i = 0; i <= yStep; i++) {
            yVal = (yMax / yStep) * i;
            canvas_ctx.moveTo(marginX, yValuetoGraph(yVal) + 2);
            canvas_ctx.lineTo(canvas_obj.width - marginX, yValuetoGraph(yVal) + 2);
        }
 
        canvas_ctx.stroke();
        calcXStep();
    }
 
    /**
     * Ajouter une valeur X,Y à la serie
     * @param {string} x
     * @param {int} y 
     */
    this.addPoint = function(x, y) {
        dataset.serie.push({X: x, Y: y});
        if (y > yMax) {
            yMax = y;
        }
    }
 
    /**
     * Dessiner la courbe
     */
    this.drawLine = function() {
        canvas_ctx.beginPath();
        canvas_ctx.strokeStyle = '#DF4926';
        canvas_ctx.lineWidth = 3;
        canvas_ctx.moveTo(xValuetoGraph(0), yValuetoGraph(dataset.serie[0].Y));
        //line
        for (var i = 1; i < dataset.serie.length; i++) {
            canvas_ctx.lineTo(xValuetoGraph(i), yValuetoGraph(dataset.serie[i].Y));
        }
        canvas_ctx.stroke();
        //point
        canvas_ctx.fillStyle = '#333';
        for (var i = 0; i < dataset.serie.length; i++) {
            /* canvas_ctx.beginPath();
             canvas_ctx.arc(xValuetoGraph(i), yValuetoGraph(dataset.serie[i].Y), 4, 0, Math.PI * 2, true);
             canvas_ctx.fill();*/
        }
 
        //label x
        canvas_ctx.beginPath();
        var ilabelstep = xLabelStep;
        for (var i = 0; i < dataset.serie.length; i++) {
            //Bidouillage pour eviter la superposition de label sur l'axe X
            if (xLabelStep > 1) {
                if (ilabelstep >= xLabelStep) {
                    canvas_ctx.fillText(dataset.serie[i].X, xValuetoGraph(i) + (marginX / 2), yValuetoGraph(0) + (marginY / 2) + 5);
                    ilabelstep = 0;
                }
                ilabelstep++;
            } else {
                canvas_ctx.fillText(dataset.serie[i].X, xValuetoGraph(i) + (marginX / 2), yValuetoGraph(0) + (marginY / 2) + 5);
            }
        }
        canvas_ctx.stroke();
    }
 
    /**
     * Regle par 3 pour X
     * @param {type} xIdx
     */
    function xValuetoGraph(xIdx) {
        var serielen = dataset.serie.length;
        var xArea = canvas_obj.width - (marginX * 2);
        var xPos = ((xArea / serielen) * xIdx) + marginX;
        return xPos;
    }
 
    /**
     * Regle par 3 pour Y
     * @param {type} xIdx
     */
    function yValuetoGraph(yVal) {
        var yArea = canvas_obj.height - (marginY * 2);
        var yPos = canvas_obj.height - marginY - ((yVal * yArea) / yMax);
        return yPos;
    }
 
    /**
     * Définir le nombre de label sur l'axe X
     * @returns {undefined}
     */
    function calcXStep() {
        var text;
        var xArea = canvas_obj.width - (marginX * 2);
        for (var i = 0; i < dataset.serie.length; i++) {
            text += ' ' + dataset.serie[i].X + ' ';
        }
        var metrics = canvas_ctx.measureText(text);
        var width = metrics.width;
        xLabelStep = Math.round(width / xArea);
    }
}

Comment générer un graphique dynamiquement

Le premier exemple est une serie de nombre aléatoire que l'on souhaite affiche sous forme de graphique. La limite de la class js est de toujours commencer par ajouter les données dans le dataset interne, puis de dessiner les axes verticaux pour finir par dessiner la courbe.
var lineGraph = new LineGraph("graph");
lineGraph.addPoint("janv", Math.floor((Math.random() * 100) + 1));
lineGraph.addPoint("fevr", Math.floor((Math.random() * 100) + 1));
lineGraph.addPoint("mars", Math.floor((Math.random() * 100) + 1));
lineGraph.addPoint("avr", Math.floor((Math.random() * 100) + 1));
lineGraph.addPoint("mai", Math.floor((Math.random() * 100) + 1));
lineGraph.addPoint("juin", Math.floor((Math.random() * 100) + 1));
lineGraph.addPoint("juil", Math.floor((Math.random() * 100) + 1));
lineGraph.addPoint("aout", Math.floor((Math.random() * 100) + 1));
lineGraph.addPoint("sept", Math.floor((Math.random() * 100) + 1));
lineGraph.addPoint("oct", Math.floor((Math.random() * 100) + 1));
lineGraph.addPoint("nov", Math.floor((Math.random() * 100) + 1));
lineGraph.addPoint("dec", Math.floor((Math.random() * 100) + 1));
lineGraph.drawAxis();
lineGraph.drawLine();
Et oui, c'est le code source qui a servi à générer le graph exemple !

Le deuxième exemple est l'utilisation de données issues d'un tableau JSON. Le cas classique est une requête en Ajax vers un serveur MySql, les valeurs sont parsées en javascript puis le graph est généré dynamiquement.

new ajax().request('http://xxx.com',
        {
            request: 'GET',
            onresponse: function(response, success) {
                var n = JSON.parse(response);
 
                var lineGraph = new LineGraph("myGraph");
                for (var i = n.length - 1; i >= 0; i--) {
                    lineGraph.addPoint(n[i]['DATE'], parseInt(n[i]['POSITION']));
                }
                lineGraph.drawAxis();
                lineGraph.drawLine();
            }
        });

Générer des graphiques sans librairies js

Pour la petite histoire, j'étais à la recherche d'une librairie js pour dessiner une courbe à partir de résultats JSON. Sur le web, on trouve de multiples class qui bien souvent sont surdimensionnées par rapport à mon besoin. C'est ainsi qu'est venu l'idée d'utiliser la canvas HTML5 pour afficher une représentation graphique des données.
La class est en place dans notre projet, je partage en espérant qu'elle vous sera utile dans les projets où l'on souhaite afficher une courbe simple, sans librairie ou api externe et rapidement sans développement supplémentaire.

Ajouter un commentaire

Les champs marqués d'un * sont obligatoires, les adresses emails se sont pas publiées.
avatar Vincent

Publié par Vincent

le 08/04/2016 à 07:49:39

Sympa votre petit script :) Oui il existe des bibliotheques deja toutes faites mais elles sont en general 50x plus lourdes que ce petit bout de code qui fait exactement ce qu'il faut. En plus votre code est bien construit, bien commente, tres comprehensible. Merci beaucoup pour ce partage ;)

A lire aussi

Réseaux sociaux
Présentation de l'article
Catégorie
tuto - webmaster
Mise a jour
20/10/2014
Visualisation
vu 3573 fois
Public
Internaute
Auteur de la publication
Fobec
Admin
Auteur de 261 articles
|BIO_PSEUDO|
Commentaires récents

Publié par y__fe dans java

Impeccable merci !

Publié par iliass dans CMS

aebgksdktu hsmu yailsh iliass jqd ou azmwt wa ikraame

Publié par claude77 dans tuto

Super application, fonctionne bien
exemple sur une image

Publié par Loualfcha dans CMS

Je n'arrive pas &Atilde;&nbsp; installer le logiciel<br>Message d'erreur: DeleteFile a &Atilde;&copy;chou&Atilde;&copy;; code 5<br>Acc&Atilde;&um...

Publié par Fobec dans php5

a priori il manque simplement le R dans le nom de la constante
PDO::ATTR_ERRMODE en remplacement de PDO::ATT_ERRMODE