Javascript 在 HTML5 画布上绘制旋转文本
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/3167928/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Drawing rotated text on a HTML5 canvas
提问by Sparafusile
Part of a web application I'm developing requires me to create bar graphs to display various information. I figured, if the user's browser is capable, I would draw them using the HTML5 canvas element. I have no problem drawing lines and bars for my graphs, but when it comes to labeling the axes, the bars, or the lines I ran into a snag. How do I draw rotated text onto a canvas element so that it lines up with the item it is labeling? A couple examples include:
我正在开发的 Web 应用程序的一部分要求我创建条形图来显示各种信息。我想,如果用户的浏览器有能力,我会使用 HTML5 画布元素绘制它们。我为我的图形绘制线条和条形没有问题,但是在标记轴、条形或线条时,我遇到了障碍。如何将旋转的文本绘制到画布元素上,使其与它所标记的项目对齐?几个例子包括:
- Rotate text 90 degrees counter clockwise to label the y-axis
- Rotate text 90 degrees counter clockwise to label bars on a vertical bar graph
- Rotate text an arbitrary amount to label lines on a line graph
- 将文本逆时针旋转 90 度以标记 y 轴
- 将文本逆时针旋转 90 度以标记垂直条形图上的条形
- 将文本旋转任意量以标记折线图上的线条
Any pointers would be appreciated.
任何指针将不胜感激。
采纳答案by robertc
Like others have mentioned, you probably want to look at reusing an existing graphing solution, but rotating text isn't too difficult. The somewhat confusing bit (to me) is that you rotate the whole context and then draw on it:
就像其他人提到的那样,您可能想考虑重用现有的图形解决方案,但旋转文本并不太困难。有点令人困惑的一点(对我来说)是你旋转整个上下文然后在它上面绘制:
ctx.rotate(Math.PI*2/(i*6));
The angle is in radians. The code is taken from this example, which I believe was made for the transformations part of the MDC canvas tutorial.
的角度是弧度。代码取自这个示例,我相信它是为MDC 画布教程的转换部分制作的。
Please see the answer belowfor a more complete solution.
请参阅下面的答案以获得更完整的解决方案。
回答by user631644
Posting this in an effort to help others with similar problems. I solved this issue with a five step approach -- save the context, translate the context, rotate the context, draw the text, then restore the context to its saved state.
发布此信息是为了帮助其他有类似问题的人。我用五步方法解决了这个问题——保存上下文、翻译上下文、旋转上下文、绘制文本,然后将上下文恢复到其保存状态。
I think of translations and transforms to the context as manipulating the coordinate grid overlaid on the canvas. By default the origin (0,0) starts in the upper left hand corner of the canvas. X increases from left to right, Y increases from top to bottom. If you make an "L" w/ your index finger and thumb on your left hand and hold it out in front of you with your thumb down, your thumb would point in the direction of increasing Y and your index finger would point in the direction of increasing X. I know it's elementary, but I find it helpful when thinking about translations and rotations. Here's why:
我认为翻译和转换到上下文就像操纵覆盖在画布上的坐标网格。默认情况下,原点 (0,0) 从画布的左上角开始。X 从左到右增加,Y 从上到下增加。如果你用左手的食指和拇指做一个“L”,然后将它放在你面前,拇指朝下,你的拇指会指向 Y 增加的方向,而你的食指会指向 Y 增加的方向增加 X。我知道这是基本的,但我发现在考虑平移和旋转时它很有帮助。原因如下:
When you translate the context, you move the origin of the coordinate grid to a new location on the canvas. When you rotate the context, think of rotating the "L" you made with your left hand in a clockwise direction the amount indicated by the angle you specify in radians about the origin. When you strokeText or fillText, specify your coordinates in relation to the newly aligned axes. To orient your text so it's readable from bottom to top, you would translate to a position below where you want to start your labels, rotate by -90 degrees and fill or strokeText, offsetting each label along the rotated x axis. Something like this should work:
平移上下文时,会将坐标网格的原点移动到画布上的新位置。当您旋转上下文时,请考虑将您用左手制作的“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() resets the context back to the state it had when you called .save() -- handy for returning things back to "normal".
.restore() 将上下文重置回它在您调用 .save() 时的状态——方便将事情恢复到“正常”。
回答by Funkodebat
While this is sort of a follow up to the previous answer, it adds a little (hopefully).
虽然这是对上一个答案的跟进,但它增加了一点(希望如此)。
Mainly what I want to clarify is that usually we think of drawing things like draw a rectangle at 10, 3.
我主要想澄清的是,通常我们会想到绘制诸如draw a rectangle at 10, 3.
So if we think about that like this: move origin to 10, 3, then draw rectangle at 0, 0.
Then all we have to do is add a rotate in between.
因此,如果我们这样考虑:move origin to 10, 3,那么draw rectangle at 0, 0. 然后我们要做的就是在两者之间添加一个旋转。
Another big point is the alignment of the text. It's easiest to draw the text at 0, 0, so using the correct alignment can allow us to do that without measuring the text width.
另一个重点是文本的对齐。在 0, 0 处绘制文本是最容易的,因此使用正确的对齐方式可以让我们在不测量文本宽度的情况下做到这一点。
We should still move the text by an amount to get it centered vertically, and unfortunately canvas does not have great line height support, so that's a guess and check thing ( correct me if there is something better ).
我们仍然应该将文本移动一定量以使其垂直居中,不幸的是画布没有很好的行高支持,所以这是一个猜测和检查的事情(如果有更好的地方请纠正我)。
I've created 3 examples that provide a point and a text with 3 alignments, to show what the actual point on the screen is where the font will go.
我创建了 3 个示例,它们提供了一个点和一个具有 3 个对齐方式的文本,以显示屏幕上的实际点是字体所在的位置。


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);
The line this.context.fillText('right', 0, lineHeight / 2);is basically 0, 0, except we move slightly for the text to be centered near the point
这条线this.context.fillText('right', 0, lineHeight / 2);基本上0, 0是
回答by WebWanderer
Funkodebat posted a great solution which I have referenced many times. Still, I find myself writing my own working model each time I need this. So, here is my working model... with some added clarity.
Funkodebat 发布了一个很棒的解决方案,我已经多次引用了它。尽管如此,每次我需要它时,我都会发现自己在编写自己的工作模型。所以,这是我的工作模型......增加了一些清晰度。
First of all, the height of the text is equal to the pixel font size. Now, this was something I read a while ago, and it has worked out in my calculations. I'm not sure if this works with all fonts, but it seems to work with Arial, sans-serif.
首先,文本的高度等于像素字体大小。现在,这是我前段时间读到的东西,并且在我的计算中得到了解决。我不确定这是否适用于所有字体,但它似乎适用于 Arial、sans-serif。
Also, to make sure that you fit all of the text in your canvas (and don't trim the tails off of your "p"'s) you need to set context.textBaseline*.
此外,为了确保您适合画布中的所有文本(并且不要修剪掉“p”的尾部),您需要设置context.textBaseline*。
You will see in the code that we are rotating the text about its center. To do this, we need to set context.textAlign = "center"and the context.textBaselineto bottom, otherwise, we trim off parts of our text.
您将在代码中看到我们正在围绕其中心旋转文本。为此,我们需要将context.textAlign = "center"和context.textBaseline 设置为底部,否则,我们会修剪掉部分文本。
Why resize the canvas?
I usually have a canvas that isn't appended to the page. I use it to draw all of my rotated text, then I draw it onto another canvas which I display.
For example, you can use this canvas to draw all of the labels for a chart (one by one) and draw the hidden canvas onto the chart canvas where you need the label (context.drawImage(hiddenCanvas, 0, 0);).
为什么要调整画布大小?我通常有一个未附加到页面的画布。我用它来绘制所有旋转的文本,然后将其绘制到另一个显示的画布上。例如,您可以使用此画布(一个接一个)绘制图表的所有标签,并将隐藏的画布绘制到需要标签的图表画布上 ( context.drawImage(hiddenCanvas, 0, 0);)。
IMPORTANT NOTE:Set your font before measuring your text, and re-apply all of your styling to the context after resizing your canvas. A canvas's context is completely reset when the canvas is resized.
重要说明:在测量文本之前设置字体,并在调整画布大小后将所有样式重新应用于上下文。调整画布大小时,画布的上下文将完全重置。
Hope this helps!
希望这可以帮助!
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;">
回答by bpeterson76
Here's an HTML5 alternative to homebrew: http://www.rgraph.net/You might be able to reverse engineer their methods....
这是自制软件的 HTML5 替代品:http: //www.rgraph.net/您也许可以对他们的方法进行逆向工程....
You might also consider something like Flot (http://code.google.com/p/flot/) or GCharts: (http://www.maxb.net/scripts/jgcharts/include/demo/#1) It's not quite as cool, but fully backwards compatible and scary easy to implement.
你也可以考虑像 Flot ( http://code.google.com/p/flot/) 或 GCharts : ( http://www.maxb.net/scripts/jgcharts/include/demo/#1) 它不是非常酷,但完全向后兼容并且易于实现。

