在HTML5画布上绘制旋转文本

77

我正在开发的 Web 应用程序的一部分需要我创建条形图以显示各种信息。如果用户的浏览器支持,我会使用 HTML5 canvas 元素来绘制它们。在绘制图表的线和条时没有问题,但当涉及到标记轴、条或线时,我遇到了一些麻烦。如何将旋转的文本绘制到画布元素上,使其与所标记的项对齐?几个示例包括:

  • 将文本逆时针旋转 90 度以标记 y 轴
  • 将文本逆时针旋转 90 度以标记垂直柱状图上的条形
  • 旋转文本任意角度以标记折线图上的线

如有指导意见,敬请赐教。


你是否考虑过研究现有的图形解决方案,而不是尝试构建自己的解决方案?flot(http://code.google.com/p/flot/)是一个使用canvas的例子。 - Bartek
5个回答

157

分享这篇内容是为了帮助其他遇到类似问题的人。我通过五步法解决了这个问题--保存上下文, 翻译上下文, 旋转上下文, 绘制文本, 然后将上下文恢复到保存的状态。

我认为将上下文进行翻译和变换就像是在画布上叠加坐标网格一样。默认情况下,原点(0,0)位于画布的左上角。X轴从左到右增加,Y轴从上到下增加。如果你用左手食指和拇指做出一个“L”形并举在你面前,大拇指朝下,则大拇指指向递增的Y方向,食指指向递增的X方向。虽然很基础,但我发现在考虑翻译和旋转时它很有帮助。这就是为什么:

当你翻译上下文时,你将坐标网格的原点移动到画布上的一个新位置。当你旋转上下文时,想象你以顺时针方向旋转你左手的“L”形,并绕原点按指定的弧度旋转相应的角度。当你使用strokeText或fillText绘制文本时,请相对于新对齐的坐标轴指定你的坐标。为了使文本从下往上可读,你需要将其翻译到一个低于你想要开始标记的位置,然后旋转-90度并填充或描边文本,在旋转后的x轴上沿着偏移每个标签。像这样做应该可以解决问题:

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

.restore() 重置上下文到调用 .save() 时的状态,有助于将事物恢复为“正常”。


4
非常好的翻译/旋转解释。+1 - roman m
12
如果您想将角度设置为特定值,可以执行以下操作: context.rotate(angle * (Math.PI / 180)); - PaulBGD
1
进一步澄清一下:上面的 -Math.PI / 2.0 旋转代码会将您的文本逆时针旋转...大概很多人都想在图表左侧绘制垂直文本,所以...:将其翻译到图表的左下角(没有填充或任何东西),进行 -Math.PI / 2.0 旋转,以图表中心 Y 为 X 位置进行居中绘制(当旋转时为垂直),并将文本大小的负一半用于 Y 位置。(您也可以事先将额外的正文本大小的一半进行平移,但我认为这样更容易理解。) - Andrew
我个人不需要翻译,但还是谢谢你! - Jona

45

像其他人提到的一样,您可能希望考虑重用现有的图形解决方案,但旋转文本并不太困难。有点令人困惑(对我来说)的是,您旋转整个上下文,然后在其上绘制:

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

角度以弧度为单位。这段代码取自于此示例,我相信它是为MDC画布教程的变换部分制作的。

请参见下面的答案以获得更完整的解决方案。


6
弧度 = (角度 * Math.PI / 180) - ashleedawg

36

虽然这有点是前一个答案的跟进,但增加了一点(希望如此)。

主要想澄清的是,通常我们会考虑像“在10, 3处画一个矩形”的绘图方式。

因此,如果我们把它看作是:“将原点移动到10, 3”,然后“在0, 0处画一个矩形”。 那么我们只需要在两者之间添加旋转即可。

另一个重要的点是文本的对齐方式。最好将文本绘制在0, 0处,因此使用正确的对齐方式可以让我们无需测量文本宽度即可实现。

我们仍应该通过一定的偏移量移动文本,以使其垂直居中,并且不幸的是,画布没有很好的行高支持,因此这是一个猜测和检查的过程(如果有更好的方法,请纠正我)。

我创建了三个示例,其中提供了一个点和三种对齐方式的文本,以显示字体将放置在屏幕上的实际点。

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);

这一行代码:this.context.fillText('right', 0, lineHeight / 2); 基本上相当于 0, 0,只是我们稍微移动一下文本位置,使其在点附近居中显示。


7
Funkodebat提供了一个很好的解决方案,我已经多次参考过。 然而,每次需要时我仍然会编写自己的工作模型。 所以,这是我的工作模型......增加了一些清晰度。
首先,文本的高度等于像素字体大小。 现在,这是我一段时间前读到的,它在我的计算中起作用。我不确定这是否适用于所有字体,但它似乎适用于Arial、sans-serif字体。
此外,为了确保您将所有文本都适合在画布上(并且不要修剪掉“p”的尾巴),您需要设置context.textBaseline。
在代码中,您将看到我们围绕其中心旋转文本。 要做到这一点,我们需要设置context.textAlign =“ center”和context.textBaseline为bottom,否则,我们就会裁剪掉文本的某些部分。
为什么要调整画布大小? 我通常有一个没有附加到页面的canvas。我使用它来绘制所有旋转的文本,然后将其绘制到另一个我显示的canvas上。 例如,您可以使用此canvas逐个绘制图表的所有标签,并将隐藏的canvas绘制到您需要标签的图表canvas上(context.drawImage(hiddenCanvas, 0,0))。
重要提示:在测量文本之前设置字体,并在调整画布大小后重新应用所有样式到上下文中。当画布被调整大小时,canvas的上下文会完全重置。
希望这有所帮助!

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;">


1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接