I managed to draw a line on a canvas using html5:
ctx.moveTo(x1, y1);
ctx.lineTo(x2, y2);
ctx.stroke();
This works. I now want to "annotate" the line with text. So basically, I want there to be custom (e.g. whatever I pass in) text appearing along the length of the line. The difficulty is that the line can appear in any orientation (e.g. have any slope) so the text needs to be oriented accordingly. Any ideas how to start?
I have created an example of this on my website. In general, you want to:
translate the context to the anchor point of the text, then
rotate the context by the amount (in radians) you desire, and then
fillText as normal.
I have included the relevant portion of my example below; I leave it as an exercise to the reader to detect when the text is upside down and handle it as desired.
Edit: view the source on my site for additional code that keeps the text upright and also auto-truncates it.
function drawLabel( ctx, text, p1, p2, alignment, padding ){
if (!alignment) alignment = 'center';
if (!padding) padding = 0;
var dx = p2.x - p1.x;
var dy = p2.y - p1.y;
var p, pad;
if (alignment=='center'){
p = p1;
pad = 1/2;
} else {
var left = alignment=='left';
p = left ? p1 : p2;
pad = padding / Math.sqrt(dx*dx+dy*dy) * (left ? 1 : -1);
}
ctx.save();
ctx.textAlign = alignment;
ctx.translate(p.x+dx*pad,p.y+dy*pad);
ctx.rotate(Math.atan2(dy,dx));
ctx.fillText(text,0,0);
ctx.restore();
}
For Firefox only you also have the option of using mozTextAlongPath. (Deprecated)
I used it and it worked =) I just changed something so that when I make the node spin, the label is always in a good position to be read:
In my redraw function I put something like this:
particleSystem.eachEdge(function(edge, pt1, pt2){
// edge: {source:Node, target:Node, length:#, data:{}}
// pt1: {x:#, y:#} source position in screen coords
// pt2: {x:#, y:#} target position in screen coords
// draw a line from pt1 to pt2
var dx = (pt2.x - pt1.x);
var dy = (pt2.y - pt1.y);
var p, pad;
var alignment = "center";
//ctx.label(edge.data.role,dx,dy,5,90,14);
ctx.strokeStyle = "rgba(0,0,0, .333)";
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(pt1.x, pt1.y);
ctx.lineTo(pt2.x, pt2.y);
ctx.stroke();
p = pt1;
pad = 1/2;
ctx.save();
ctx.textAlign = alignment;
ctx.translate(p.x+dx*pad,p.y+dy*pad);
if(dx < 0)
{
ctx.rotate(Math.atan2(dy,dx) - Math.PI); //to avoid label upside down
}
else
{
ctx.rotate(Math.atan2(dy,dx));
}
ctx.fillStyle = "black"
ctx.fillText(edge.data.role,0,0);
ctx.restore();
})
Thanks,
Dámaris.
Related
I'm placing text in an HTML5 canvas. I'm setting the context textAlign value to center and the textBaseline to "hanging"
I'm confused, because safari and chrome and both webkit and the text placement is diffrent even when told to be in the same spot.
Chrome looks correct:
as does firefox
however safari places the text lower (same code)
any idea how safari calculates the position differently so I can build an exception for it?
Text to fit is a pain. Here is a none standard way to make text fit almost pixel perfect. (width may come inside requested a little and some fonts are just way to out there to measure).
This method renders the text at a large size "100px" and then measures its various parts. You can the fit to a rectangle, with four options that you can see in the demo. T, H and letters like that tend to be slightly shorter than O, G and rounded letters, you can opt to keep the round bits inside or not, you can ignore the tails 'y,j,g' or keep them inside.
The measuring function renders text several times to find the various parts, you can overlap several letters if you want a better sample or a specific font is a pain.
This solution will fit all browsers that have good canvas support.
Sorry about the naming but this particular issue does me in, we get great text in every other browser API so why not canvas????? (secret conspiracy of the SVG camp LOL ;) me thinks
Two functions. First measures the text, and returns an object containing the relevant info, and the second renders text to fit a rectangle. See the code at the bottom how to use it and set the two flags that control the hanging tail and roundy bits.
var canvas = document.createElement("canvas");
canvas.width = 800;
canvas.height = 800;
var ctx = canvas.getContext("2d");
document.body.appendChild(canvas);
function superWTFCanvasTextMessure(font){ // just the font name please.....
var c = document.createElement("canvas"); // make canvas
var ctx1 = c.getContext("2d"); // get the thing that draw the stuff
ctx1.font = "100px "+font; // big font
ctx1.textAlign = "center"; // put it in the middle
ctx1.textBaseline = "middle";
// draw text nice and solid...
ctx1.fillText("lp",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("lp",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("lp",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("lq",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("lg",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("lj",Math.floor(c.width/2),Math.floor(c.height/2));
// get the pixels as words
var data= new Uint32Array(ctx1.getImageData(0,0,canvas.width,canvas.height).data.buffer);
var top = 0;
while(data[top++] === 0); // find the first pixel on from the top;
var tail = data.length - 1;
while(data[tail--] === 0); // find the first pixel on from the bottom;
ctx1.clearRect(0,0,canvas.width,canvas.height); // clear up the mess
// and now for the Base draw text nice and solid...
ctx1.fillText("T",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("T",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("T",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("T",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("T",Math.floor(c.width/2),Math.floor(c.height/2));
// get the pixels as words
data= new Uint32Array(ctx1.getImageData(0,0,canvas.width,canvas.height).data.buffer);
var bum = data.length - 1;
while(data[bum--] === 0); // find the first pixel on from the bottom;
// and the round bits the poke out in all the wrong places.
ctx1.clearRect(0,0,canvas.width,canvas.height); // clear up the mess
// and now for the Base draw text nice and solid...
ctx1.fillText("O",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("J",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("?",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("G",Math.floor(c.width/2),Math.floor(c.height/2));
ctx1.fillText("O",Math.floor(c.width/2),Math.floor(c.height/2));
// get the pixels as words
data= new Uint32Array(ctx1.getImageData(0,0,canvas.width,canvas.height).data.buffer);
var head = 0;
while(data[head++] === 0); // find the first pixel on from the bottom;
var theOtherBit = data.length - 1;
while(data[theOtherBit--] === 0); // find the first pixel on from the bottom;
return {
body: Math.floor(bum / canvas.width) - Math.floor(top / canvas.width)+1,
all : Math.floor(tail / canvas.width) - Math.floor(top / canvas.width)+1,
offset : Math.floor(c.height/2) - Math.floor(top / canvas.width),
t2A : Math.floor(theOtherBit / canvas.width) - Math.floor(head / canvas.width)+1,
t2t : Math.floor(tail / canvas.width) - Math.floor(head / canvas.width)+1,
offsetHead : Math.floor(c.height/2) - Math.floor(head / canvas.width),
font : ctx1.font,
};
}
function drawPixelPerfectTextTheHardWay(text,left,top,width,height,sWTFDesc){
var sy,offy;
ctx.font = sWTFDesc.font; // set up the same font as measured. (dont worry it will be scaled and can be any size but if not measure at 100 px this will not work as well)
ctx.textAlign = "center";
ctx.textBaseline = "middle";
var w = ctx.measureText(text).width; // get the width
if(sWTFDesc.bumsDown){
if(sWTFDesc.extrasIn){
sy = height/ sWTFDesc.t2A; // get the height scale
}else{
sy = height/ sWTFDesc.body; // get the height scale
}
}else{
if(sWTFDesc.extrasIn){
sy = height/ sWTFDesc.t2t; // get the height scale
}else{
sy = height/ sWTFDesc.all; // get the height scale
}
}
var sx = width / w; // get the x scale
if(sWTFDesc.extrasIn){
offy = sWTFDesc.offset * sy; // get top offset
}else{
offy = sWTFDesc.offset * sy; // get the correct offset
}
// set up the tranform
ctx.setTransform(sx,0,0,sy,left + width / 2, top + offy);
ctx.fillText(text,0,0);
ctx.setTransform(1,0,0,1,0,0); // reset the tranform to the default..
// all diddly done..
}
ctx.clearRect(0,0,canvas.width,canvas.height)
var superFontDesc = superWTFCanvasTextMessure("arial");
ctx.strokeStyle = "black";
ctx.fillStyle = "red";
ctx.strokeRect(10,200,700,140);
drawPixelPerfectTextTheHardWay("mind p & q's ? j,g",10,200,700,140,superFontDesc);
ctx.fillStyle = "green";
ctx.strokeRect(20,400,700,120);
superFontDesc.bumsDown = true;
drawPixelPerfectTextTheHardWay("test this Jimmy!!",20,400,700,120,superFontDesc);
ctx.fillStyle = "blue";
ctx.strokeRect(20,20,700,140);
superFontDesc.bumsDown = true;
superFontDesc.extrasIn= true;
drawPixelPerfectTextTheHardWay("test this Jimmy!!",20,20,700,140,superFontDesc);
ctx.fillStyle = "#a50";
ctx.strokeRect(20,570,700,140);
superFontDesc.bumsDown = false;
superFontDesc.extrasIn= true;
drawPixelPerfectTextTheHardWay("????&GGhjqgy",20,570,700,140,superFontDesc);
ctx.font = "20px arial";
ctx.textAlign = "left";
ctx.fillStyle = "black";
ctx.fillText("Round bits in and tails hanging.",10,174);
ctx.fillText("Round bits out and tails in.",10,354);
ctx.fillText("Round bits out and tails hanging.",10,540);
ctx.fillText("Round bits out and tails in.",10,724);
I am trying to draw lines with canvas and I am changing the coordinates with a for loop.
here is my canvas element:
<canvas id="c" width="300px" height="300px"></canvas>
and here is the js codes:
var c = document.getElementById('c');
ci = c.getContext('2d');
for(var a = 18; a < 300; a +=18){
fnc(a, ci);
}
function fnc(x, ci){
ci.strokeStyle = 'red';
ci.moveTo(0, x);
ci.lineTo(300, x); ci.lineWidth = 0.2; ci.stroke();
}
As you can see I am trying to draw these lines with 18px spaces between them. But the thickness of the lines and the color(or opacity, I am not sure) are changing from top to bottom.
Here is a fiddle : http://jsfiddle.net/J6zzD/1/
So what is wrong with that I can't find my mistake. Why are the color and the thicknesses are different?
UPDATE :
I just wrote these lines out of the function and now all the lines becomes faded but thicknesses are same. So strange :
ci.strokeStyle = 'red';
ci.lineWidth = 0.2; ci.stroke();
here is demo : http://jsfiddle.net/J6zzD/4/
That's again the eternal issue of forgetting to call beginPath.
Each time you call moveTo then lineTo, you create a new *sub*path, which adds to the current Path.
Then each time you call stroke(), the current path, so all the current subpaths get re-drawn, when the last added path is drawn for the first time.
Since opacities will add-up, top lines will reach 100% opacity (alpha=255) when the bottom line, drawn once, will have a 20% opacity (lineWidth=0.2) .
In your second fiddle, you stroke only once, so all lines have 20% opacity, which is correct for the 0.2 lineWidth.
So : use beginPath before drawing a new figure.
In this case you have two choices :
• draw line by line
OR
• draw once a path with all lines as subpath.
(see code below).
TIP : To get clean lines remember that pixels's center is at the (+0.5, +0.5) coordinates of each pixels, so
a 'trick' is to translate by 0.5, 0.5 on app start, then only use rounded coordinates and lineWidth.
1) draw line by line
http://jsfiddle.net/gamealchemist/J6zzD/6/
var c = document.getElementById('c');
var ctx = c.getContext('2d');
ctx.translate(0.5, 0.5);
ctx.lineWidth = 1;
for (var y = 18; y < 300; y += 18) {
strokeLine(ctx, y);
}
function strokeLine(ctx, y) {
ctx.beginPath();
ctx.strokeStyle = 'red';
ctx.moveTo(0, y);
ctx.lineTo(300, y);
ctx.stroke();
}
2) draw multiple subPath :
(you can have only one color for one stroke() )
http://jsfiddle.net/gamealchemist/J6zzD/7/
var c = document.getElementById('c');
var ctx = c.getContext('2d');
ctx.translate(0.5, 0.5);
ctx.lineWidth = 1;
ctx.strokeStyle = 'red';
ctx.beginPath();
for (var y = 18; y < 300; y += 18) {
addLineSubPath(ctx, y);
}
ctx.stroke();
function addLineSubPath(ctx, y) {
ctx.moveTo(0, y);
ctx.lineTo(300, y);
}
See: https://developer.mozilla.org/en-US/docs/Web/Guide/HTML/Canvas_tutorial/Applying_styles_and_colors#A_lineWidth_example
Because canvas coordinates do not directly reference pixels, special care must be taken to obtain crisp horizontal and vertical lines.
Basically, because you're trying to draw a line that's 0.2 pixels wide, the browser does some math to approximate a continuous number into discrete units and you get your "fading" lines.
So now we can fix up your code by changing context.lineWidth to 1 (I actually remove it because it defaults to 1) and shifting everything down by half a pixel.
var c = document.getElementById('c');
ci = c.getContext('2d');
for(var a = 18.5; a < 300.5; a +=18)
{
fnc(a, ci);
}
function fnc(x, ci)
{
ci.strokeStyle = 'red';
ci.moveTo(0, x);
ci.lineTo(300, x);
ci.stroke();
}
Demo
I'm working on concept maps application, which has a set of nodes and links. I have connected the links to nodes using the center of the node as reference. Since I have nodes with different size and shapes, it is not advisable to draw arrow-head for the link by specifying height or width of the shape. My approach is to draw a link, starting from one node, pixel by pixel till the next node is reached(here the nodes are of different color from that of the background), then by accessing the pixel value, I want to be able to decide the point of intersection of link and the node, which is actually the co-ordinate for drawing the arrow-head.
It would be great, if I could get some help with this.
Sample Code:
http://jsfiddle.net/9tUQP/4/
Here the green squares are nodes and the line starting from left square and entering into the right square is the link. I want the arrow-head to be drawn at the point of intersection of link and the right square.
I've created an example that does this. I use Bresenham's Line Algorithm to walk the line of whole canvas pixels and check the alpha at each point; whenever it crosses a 'threshold' point I record that as a candidate. I then use the first and last such points to draw an arrow (with properly-rotated arrowhead).
Here's the example: http://phrogz.net/tmp/canvas_shape_edge_arrows.html
Refresh the example to see a new random test case. It 'fails' if you have another 'shape' already overlapping one of the end points. One way to solve this would be to draw your shapes first to a blank canvas and then copy the result (drawImage) to the final canvas.
For Stack Overflow posterity (in case my site is down) here's the relevant code:
<!DOCTYPE html>
<html><head>
<meta charset="utf-8">
<title>HTML5 Canvas Shape Edge Detection (for Arrow)</title>
<style type="text/css">
body { background:#eee; margin:2em 4em; text-align:center; }
canvas { background:#fff; border:1px solid #666 }
</style>
</head><body>
<canvas width="800" height="600"></canvas>
<script type="text/javascript">
var ctx = document.querySelector('canvas').getContext('2d');
for (var i=0;i<20;++i) randomCircle(ctx,'#999');
var start = randomDiamond(ctx,'#060');
var end = randomDiamond(ctx,'#600');
ctx.lineWidth = 2;
ctx.fillStyle = ctx.strokeStyle = '#099';
arrow(ctx,start,end,10);
function arrow(ctx,p1,p2,size){
ctx.save();
var points = edges(ctx,p1,p2);
if (points.length < 2) return
p1 = points[0], p2=points[points.length-1];
// Rotate the context to point along the path
var dx = p2.x-p1.x, dy=p2.y-p1.y, len=Math.sqrt(dx*dx+dy*dy);
ctx.translate(p2.x,p2.y);
ctx.rotate(Math.atan2(dy,dx));
// line
ctx.lineCap = 'round';
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(-len,0);
ctx.closePath();
ctx.stroke();
// arrowhead
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(-size,-size);
ctx.lineTo(-size, size);
ctx.closePath();
ctx.fill();
ctx.restore();
}
// Find all transparent/opaque transitions between two points
// Uses http://en.wikipedia.org/wiki/Bresenham's_line_algorithm
function edges(ctx,p1,p2,cutoff){
if (!cutoff) cutoff = 220; // alpha threshold
var dx = Math.abs(p2.x - p1.x), dy = Math.abs(p2.y - p1.y),
sx = p2.x > p1.x ? 1 : -1, sy = p2.y > p1.y ? 1 : -1;
var x0 = Math.min(p1.x,p2.x), y0=Math.min(p1.y,p2.y);
var pixels = ctx.getImageData(x0,y0,dx+1,dy+1).data;
var hits=[], over=null;
for (x=p1.x,y=p1.y,e=dx-dy; x!=p2.x||y!=p2.y;){
var alpha = pixels[((y-y0)*(dx+1)+x-x0)*4 + 3];
if (over!=null && (over ? alpha<cutoff : alpha>=cutoff)){
hits.push({x:x,y:y});
}
var e2 = 2*e;
if (e2 > -dy){ e-=dy; x+=sx }
if (e2 < dx){ e+=dx; y+=sy }
over = alpha>=cutoff;
}
return hits;
}
function randomDiamond(ctx,color){
var x = Math.round(Math.random()*(ctx.canvas.width - 100) + 50),
y = Math.round(Math.random()*(ctx.canvas.height - 100) + 50);
ctx.save();
ctx.fillStyle = color;
ctx.translate(x,y);
ctx.rotate(Math.random() * Math.PI);
var scale = Math.random()*0.8 + 0.4;
ctx.scale(scale,scale);
ctx.lineWidth = 5/scale;
ctx.fillRect(-50,-50,100,100);
ctx.strokeRect(-50,-50,100,100);
ctx.restore();
return {x:x,y:y};
}
function randomCircle(ctx,color){
ctx.save();
ctx.beginPath();
ctx.arc(
Math.round(Math.random()*(ctx.canvas.width - 100) + 50),
Math.round(Math.random()*(ctx.canvas.height - 100) + 50),
Math.random()*20 + 10,
0, Math.PI * 2, false
);
ctx.fillStyle = color;
ctx.fill();
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
}
</script>
</body></html>
How do I create circle text (text in a circle shape) with canvas?
Letters should now be properly oriented:
CanvasRenderingContext2D.prototype.fillTextCircle = function(text,x,y,radius,startRotation){
var numRadsPerLetter = 2*Math.PI / text.length;
this.save();
this.translate(x,y);
this.rotate(startRotation);
for(var i=0;i<text.length;i++){
this.save();
this.rotate(i*numRadsPerLetter);
this.fillText(text[i],0,-radius);
this.restore();
}
this.restore();
}
Sample usage:
var ctx = document.getElementById('canvas').getContext('2d');
ctx.font = "bold 30px Serif";
ctx.fillTextCircle("Circle Text ",150,150,75,Math.PI / 2);
The extra space at the end of the string adds some extra padding.
Sample output:
It can technically be done, but there is no built in way. You'd have to calculate an arc, and draw each letter individually along that arc, figuring out the angle and positioning yourself.
Many people end up making their own methods (like the above) for text. Heck, multiline text can't even be done by default!
EDIT: Here is a working example, piggybacking off of cmptrgeekken's work. If you upvote me, upvote him too :P
http://jsfiddle.net/c3Y8M/1/
What it looks like:
On my blog, I take a fairly close look at creating circular text using HTML5 Canvas:
html5graphics.blogspot.com
In the example, options include rounded text alignment (left, center and right) from a given angle, inward and outward facing text, kerning (adjustable gap between characters) and text inside or outside the radius.
There is also a jsfiddle with a working example.
It is as follows:
document.body.appendChild(getCircularText("ROUNDED TEXT LOOKS BEST IN CAPS!", 250, 0, "center", false, true, "Arial", "18pt", 2));
function getCircularText(text, diameter, startAngle, align, textInside, inwardFacing, fName, fSize, kerning) {
// text: The text to be displayed in circular fashion
// diameter: The diameter of the circle around which the text will
// be displayed (inside or outside)
// startAngle: In degrees, Where the text will be shown. 0 degrees
// if the top of the circle
// align: Positions text to left right or center of startAngle
// textInside: true to show inside the diameter. False to show outside
// inwardFacing: true for base of text facing inward. false for outward
// fName: name of font family. Make sure it is loaded
// fSize: size of font family. Don't forget to include units
// kearning: 0 for normal gap between letters. positive or
// negative number to expand/compact gap in pixels
//------------------------------------------------------------------------
// declare and intialize canvas, reference, and useful variables
align = align.toLowerCase();
var mainCanvas = document.createElement('canvas');
var ctxRef = mainCanvas.getContext('2d');
var clockwise = align == "right" ? 1 : -1; // draw clockwise for aligned right. Else Anticlockwise
startAngle = startAngle * (Math.PI / 180); // convert to radians
// calculate height of the font. Many ways to do this
// you can replace with your own!
var div = document.createElement("div");
div.innerHTML = text;
div.style.position = 'absolute';
div.style.top = '-10000px';
div.style.left = '-10000px';
div.style.fontFamily = fName;
div.style.fontSize = fSize;
document.body.appendChild(div);
var textHeight = div.offsetHeight;
document.body.removeChild(div);
// in cases where we are drawing outside diameter,
// expand diameter to handle it
if (!textInside) diameter += textHeight * 2;
mainCanvas.width = diameter;
mainCanvas.height = diameter;
// omit next line for transparent background
mainCanvas.style.backgroundColor = 'lightgray';
ctxRef.fillStyle = 'black';
ctxRef.font = fSize + ' ' + fName;
// Reverse letters for align Left inward, align right outward
// and align center inward.
if (((["left", "center"].indexOf(align) > -1) && inwardFacing) || (align == "right" && !inwardFacing)) text = text.split("").reverse().join("");
// Setup letters and positioning
ctxRef.translate(diameter / 2, diameter / 2); // Move to center
startAngle += (Math.PI * !inwardFacing); // Rotate 180 if outward
ctxRef.textBaseline = 'middle'; // Ensure we draw in exact center
ctxRef.textAlign = 'center'; // Ensure we draw in exact center
// rotate 50% of total angle for center alignment
if (align == "center") {
for (var j = 0; j < text.length; j++) {
var charWid = ctxRef.measureText(text[j]).width;
startAngle += ((charWid + (j == text.length-1 ? 0 : kerning)) / (diameter / 2 - textHeight)) / 2 * -clockwise;
}
}
// Phew... now rotate into final start position
ctxRef.rotate(startAngle);
// Now for the fun bit: draw, rotate, and repeat
for (var j = 0; j < text.length; j++) {
var charWid = ctxRef.measureText(text[j]).width; // half letter
// rotate half letter
ctxRef.rotate((charWid/2) / (diameter / 2 - textHeight) * clockwise);
// draw the character at "top" or "bottom"
// depending on inward or outward facing
ctxRef.fillText(text[j], 0, (inwardFacing ? 1 : -1) * (0 - diameter / 2 + textHeight / 2));
ctxRef.rotate((charWid/2 + kerning) / (diameter / 2 - textHeight) * clockwise); // rotate half letter
}
// Return it
return (mainCanvas);
}
It's my modification of this: http://jsfiddle.net/Brfp3/3/
But feature allows you to display text clockwise and counterclockwise.
function textCircle(text,x,y,radius,space,top){
space = space || 0;
var numRadsPerLetter = (Math.PI - space * 2) / text.length;
ctx.save();
ctx.translate(x,y);
var k = (top) ? 1 : -1;
ctx.rotate(-k * ((Math.PI - numRadsPerLetter) / 2 - space));
for(var i=0;i<text.length;i++){
ctx.save();
ctx.rotate(k*i*(numRadsPerLetter));
ctx.textAlign = "center";
ctx.textBaseline = (!top) ? "top" : "bottom";
ctx.fillText(text[i],0,-k*(radius));
ctx.restore();
}
ctx.restore();
}
Sample usage:
ctx.font = "bold 30px Courier";
textCircle("Half circle Text",150,150,75,Math.PI/12,1);
textCircle("Half circle Text",150,150,75,Math.PI/12);
A version in which the size of characters is counted. The spaces between the letters are therefore always the same size.
function drawTextAlongArc(context, str, centerX, centerY, radius, angle) {
var len = str.length, s, letterAngle;
context.save();
context.textAlign = 'center';
context.translate(centerX, centerY);
context.rotate(angle + Math.PI / 2);
for (var n = 0; n < len; n++) {
s = str[n];
letterAngle = 0.5*(context.measureText(s).width / radius);
context.rotate(letterAngle);
context.save();
context.translate(0, -radius);
context.fillText(s, 0, 0);
context.restore();
context.rotate(letterAngle);
}
context.restore();
}
CircleType.js doesn't use canvas but achieves the same effect: http://circletype.labwire.ca - also works well in fluid layouts.
Just for context, I'm using html5, css, and jquery.
I'd like to be able to draw curved lines with arrowheads between two elements on a web page. I've nearly got it, but just can't wrap my head around the maths to draw the arrow heads. Could just be the manipulation of the canvas widget that's getting me.
Here's the javascript I'm using at the minute. It certainly feels the long way around, but it seems to get the job done except for those pesky arrowheads.
The 'influence' variable is to vary the lines a bit so that if there are multiple lines they will show one behind the other.
var start = $('#firstobject');
var last = $('#lastobject');
var stpos = start.position();
var lastpos = last.position();
var influence = Math.random*20+5;
var maxx = Math.max(stpos.left,lastpos.left);
var minx = Math.min(stpos.left,lastpos.left);
var maxy = Math.max(stpos.top,lastpos.top);
var miny = Math.min(stpos.top,lastpos.top);
var w = maxx - minx;
var h = maxy - miny;
var cname = "samplename";
var cpad = 30;
var cstr = "<canvas id='"+cname+"' class='huntgroupcanvas' style='z-index:2;position:absolute;left:"+(0+minx-cpad)+"px;top:"+(0+miny-cpad)+"px;' id='"+cname+"' width="+(2*cpad+w)+" height="+(2*cpad+h)+"></canvas>"
start.before(cstr);
var canvas = document.getElementById(cname);
var ctx = canvas.getContext("2d");
var dx = stpos.left - lastpos.left;
var dy = stpos.top - lastpos.top;
var dir = dy/dx;
var sx,sy,ex,ey;
if (dir<0) {
ex=w+cpad;
ey=cpad;
sx=cpad;
sy=h+cpad;
} else {
ex=w+cpad;
ey=h+cpad;
sx=cpad;
sy=cpad;
}
ctx.lineWidth=1;
ctx.strokeStyle = '#000';
ctx.beginPath();
ctx.moveTo(sx,sy);
ctx.quadraticCurveTo(((w/2)-(h/2))+cpad-influence,((h/2)-(w/2))+cpad,ex,ey);
ctx.stroke();
if (st==i-1) {
first=0;
ctx.beginPath();
if (dx<0) {
ctx.arc(sx,sy,6,0,Math.PI*2,0);
} else {
ctx.arc(ex,ey,6,0,Math.PI*2,0);
}
ctx.stroke();
}
/* Here's where I've been trying to draw the arrowheads.
* I know that I can get the slope of the line like so:
var m = (y2-y1)/(x2-x1);
* I know I can get the angle in radians as
var angle=Math.atan(m);
* I'm trying to use rotation to draw the arrowhead
*/
ctx.save();
ctx.beginPath();
angle = Math.atan(dir);
if (dx<0) {
sx=ex;
sy=ey;
angle = Math.atan(-dir);
}
ctx.moveTo(sx,sy);
ctx.translate(sx,sy);
ctx.rotate(angle - 0.05);
ctx.lineTo(20,0);
ctx.rotate(angle + 0.05);
ctx.lineTo(20,0);
ctx.closePath();
ctx.fill();
ctx.restore();
That is a lot of code to read, but it would seem that you are messing up your trigonometry when generating the angle of the arrowhead. Take a look at the atan2 function, it takes a 2D vector and converts it to it's angle. So you would want to use angle=Math.atan2(dy,dx)