72 votes

Dessiner un texte pivoté sur un canevas HTML5

Dans le cadre d'une application Web que je suis en train de développer, je dois créer des graphiques à barres pour afficher diverses informations. Je me suis dit que, si le navigateur de l'utilisateur en est capable, je pourrais les dessiner à l'aide de l'élément HTML5 canvas. Je n'ai aucun problème à dessiner des lignes et des barres pour mes graphiques, mais lorsqu'il s'agit d'étiqueter les axes, les barres ou les lignes, je rencontre un problème. Comment dessiner du texte pivoté sur un élément canvas de façon à ce qu'il soit aligné avec l'élément à étiqueter ? Voici quelques exemples :

  • Faites pivoter le texte de 90 degrés dans le sens inverse des dans le sens des aiguilles d'une montre pour étiqueter l'axe des y
  • Faire pivoter le texte de 90 degrés en sens inverse dans le sens des aiguilles d'une montre pour étiqueter les barres d'un graphique à barres
  • Faites pivoter le texte de façon arbitraire pour étiqueter les lignes d'un graphique linéaire

Toute indication serait appréciée.

0 votes

Avez-vous envisagé d'examiner les solutions graphiques existantes plutôt que d'essayer de créer la vôtre ? flot ( code.google.com/p/flot ) est un exemple qui utilise le canevas.

149voto

user631644 Points 573

Je poste ce message dans le but d'aider d'autres personnes confrontées à des problèmes similaires. J'ai résolu ce problème avec une approche en cinq étapes : enregistrer le contexte, traduire le contexte, faire pivoter le contexte, dessiner le texte, puis restaurer le contexte à son état enregistré.

Je considère les translations et les transformations dans le contexte comme une manipulation de la grille de coordonnées superposée à la toile. Par défaut, l'origine (0,0) commence dans le coin supérieur gauche de la toile. X augmente de gauche à droite, Y augmente de haut en bas. Si vous faites un "L" avec votre index et votre pouce sur votre main gauche et que vous le tenez devant vous avec le pouce vers le bas, votre pouce pointera dans la direction de l'augmentation de Y et votre index pointera dans la direction de l'augmentation de X. Je sais que c'est élémentaire, mais je trouve cela utile lorsque vous pensez à des translations et des rotations. Voici pourquoi :

Lorsque vous traduisez le contexte, vous déplacez l'origine de la grille de coordonnées vers un nouvel emplacement sur le canevas. Lorsque vous faites pivoter le contexte, pensez à faire tourner le "L" que vous avez fait avec votre main gauche dans le sens des aiguilles d'une montre de la quantité indiquée par l'angle que vous spécifiez en radians autour de l'origine. Lorsque vous effectuez un strokeText ou un fillText, spécifiez vos coordonnées par rapport aux axes nouvellement alignés. Pour orienter votre texte de manière à ce qu'il soit lisible de bas en haut, vous devez effectuer une translation jusqu'à une position située sous l'endroit où vous souhaitez commencer vos étiquettes, effectuer une rotation de -90 degrés et remplir ou remplir le texte, en décalant chaque étiquette le long de l'axe x pivoté. Quelque chose comme ceci devrait fonctionner :

 context.save();
 context.translate(newx, newy);
 context.rotate(-Math.PI/2);
 context.textAlign = "center";
 context.fillText("Your Label Here", labelXposition, 0);
 context.restore();

.restore() remet le contexte dans l'état où il se trouvait lorsque vous avez appelé .save() -- pratique pour ramener les choses à la "normale".


4 votes

Excellente explication de la traduction/rotation. +1

11 votes

Si vous voulez définir l'angle sur quelque chose de spécifique, vous pouvez le faire : context.rotate(angle * (Math.PI / 180));

0 votes

Pour clarifier davantage... : le -Math.PI / 2.0 Le code de rotation ci-dessus fait tourner votre texte dans le sens inverse des aiguilles d'une montre... On peut supposer que beaucoup de gens voudraient faire tout cela pour dessiner du texte vertical sur le côté gauche d'un graphique, donc... : translater jusqu'au coin inférieur gauche du graphique (pas de padding ou autre), faire ça -Math.PI / 2.0 rotation, dessinez avec un centrage sur le centre Y du graphique pour la position X (qui est verticale lors de la rotation) et la moitié négative de la taille du texte pour la position Y. (Vous pourriez aussi traduire au préalable une moitié positive supplémentaire de la taille du texte à la place, mais ceci est plus facile à comprendre imo).

42voto

robertc Points 35382

Comme d'autres l'ont mentionné, vous devriez probablement envisager de réutiliser une solution graphique existante, mais la rotation du texte n'est pas trop difficile. Ce qui est un peu déroutant (pour moi), c'est que vous faites pivoter l'ensemble du contexte, puis vous dessinez dessus :

ctx.rotate(Math.PI*2/(i*6));

Le site l'angle est en radians . Le code est tiré de cet exemple qui, je crois, a été conçu pour le partie transformations du tutoriel sur le canevas MDC .

Veuillez consulter la réponse ci-dessous pour une solution plus complète.

4 votes

radians = (angle * Math.PI / 180)

31voto

Funkodebat Points 1133

Il s'agit en quelque sorte d'une suite à la réponse précédente, mais elle apporte un petit plus (espérons-le).

Ce que je veux surtout clarifier, c'est que d'habitude on pense à dessiner des choses comme draw a rectangle at 10, 3 .

Donc si nous pensons à ça comme ça : move origin to 10, 3 entonces draw rectangle at 0, 0 . Il ne nous reste plus qu'à ajouter une rotation entre les deux.

Un autre point important est l'alignement du texte. Il est plus facile de dessiner le texte à 0, 0, donc utiliser l'alignement correct peut nous permettre de le faire sans mesurer la largeur du texte.

Nous devrions toujours déplacer le texte d'un certain montant pour le centrer verticalement, et malheureusement, le canevas ne supporte pas bien la hauteur de ligne, donc c'est une question de supposition et de vérification (corrigez-moi s'il y a quelque chose de mieux).

J'ai créé 3 exemples qui fournissent un point et un texte avec 3 alignements, pour montrer quel est le point réel sur l'écran où la police ira.

enter image description here

var font, lineHeight, x, y;

x = 100;
y = 100;
font = 20;
lineHeight = 15; // this is guess and check as far as I know
this.context.font = font + 'px Arial';

// Right Aligned
this.context.save();
this.context.translate(x, y);
this.context.rotate(-Math.PI / 4);

this.context.textAlign = 'right';
this.context.fillText('right', 0, lineHeight / 2);

this.context.restore();

this.context.fillStyle = 'red';
this.context.fillRect(x, y, 2, 2);

// Center
this.context.fillStyle = 'black';
x = 150;
y = 100;

this.context.save();
this.context.translate(x, y);
this.context.rotate(-Math.PI / 4);

this.context.textAlign = 'center';
this.context.fillText('center', 0, lineHeight / 2);

this.context.restore();

this.context.fillStyle = 'red';
this.context.fillRect(x, y, 2, 2);

// Left
this.context.fillStyle = 'black';
x = 200;
y = 100;

this.context.save();
this.context.translate(x, y);
this.context.rotate(-Math.PI / 4);

this.context.textAlign = 'left';
this.context.fillText('left', 0, lineHeight / 2);

this.context.restore();

this.context.fillStyle = 'red';
this.context.fillRect(x, y, 2, 2);

La ligne this.context.fillText('right', 0, lineHeight / 2); est en fait 0, 0 sauf que nous déplaçons légèrement le texte pour qu'il soit centré près du point.

5voto

WebWanderer Points 324

Funkodebat a publié une excellente solution à laquelle j'ai fait référence à de nombreuses reprises. Pourtant, je me retrouve à écrire mon propre modèle de travail chaque fois que j'en ai besoin. Voici donc mon modèle de travail... avec quelques précisions supplémentaires.

Tout d'abord, la hauteur du texte est égale à la taille de la police en pixels . C'est quelque chose que j'ai lu il y a quelque temps, et cela a fonctionné dans mes calculs. Je ne sais pas si cela fonctionne avec toutes les polices, mais cela semble fonctionner avec Arial, sans-serif.

De plus, pour être sûr de faire tenir tout le texte dans votre canevas (et de ne pas couper les queues de vos "p"), vous devez configurer context.textBaseline* .

Vous verrez dans le code que nous faisons tourner le texte autour de son centre. Pour ce faire, nous devons définir context.textAlign = "center" et le context.textBaseline de bas en haut, sinon, nous coupons des parties de notre texte.

Pourquoi redimensionner la toile ? J'ai généralement un canevas qui n'est pas annexé à la page. Je l'utilise pour dessiner tout mon texte pivoté, puis je le dessine sur un autre canevas que j'affiche. Par exemple, vous pouvez utiliser ce canevas pour dessiner toutes les étiquettes d'un graphique (une par une) et dessiner le canevas caché sur le canevas du graphique où vous avez besoin de l'étiquette ( context.drawImage(hiddenCanvas, 0, 0); ).

NOTE IMPORTANTE : Définissez votre police avant de mesurer votre texte, et réappliquez tous vos styles au contexte après avoir redimensionné votre toile. Le contexte d'un canevas est complètement réinitialisé lorsque le canevas est redimensionné.

J'espère que cela vous aidera !

var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var font, text, x, y;

text = "Mississippi";

//Set font size before measuring
font = 20;
ctx.font = font + 'px Arial, sans-serif';
//Get width of text
var metrics = ctx.measureText(text);
//Set canvas dimensions
c.width = font;//The height of the text. The text will be sideways.
c.height = metrics.width;//The measured width of the text
//After a canvas resize, the context is reset. Set the font size again
ctx.font = font + 'px Arial';
//Set the drawing coordinates
x = font/2;
y = metrics.width/2;
//Style
ctx.fillStyle = 'black';
ctx.textAlign = 'center';
ctx.textBaseline = "bottom";
//Rotate the context and draw the text
ctx.save();
ctx.translate(x, y);
ctx.rotate(-Math.PI / 2);
ctx.fillText(text, 0, font / 2);
ctx.restore();

<canvas id="myCanvas" width="300" height="150" style="border:1px solid #d3d3d3;">

1voto

bpeterson76 Points 9560

Voici une alternative HTML5 à homebrew : http://www.rgraph.net/ Vous pourriez être en mesure d'inverser leurs méthodes. ....

Vous pouvez également envisager quelque chose comme Flot ( http://code.google.com/p/flot/ ) ou GCharts : ( http://www.maxb.net/scripts/jgcharts/include/demo/#1 ) Ce n'est pas aussi cool, mais c'est totalement rétrocompatible et très facile à mettre en œuvre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X