Draw a Line with Thick Base and Thin Top -Speedometer Needle - javascript

I am having a problem: I need to draw a Needle over a Speedometer. I am using simple Line Function in JS that draws a line/Needle. I want my Needle to be thick from base and thin from tip as shown below. Please advise how to draw such needle in JavaScript. The Desired and Current Needles are below.
Code of Line:
function drawNeedle(options) {
/* Draw the needle at the
* angle that represents the options.speed value.
*/
var iSpeedAsAngle = convertSpeedToAngle(options),
iSpeedAsAngleRad = degToRad(iSpeedAsAngle),
gaugeOptions = options.gaugeOptions,
innerTickX = gaugeOptions.radius - (Math.cos(iSpeedAsAngleRad) * 10),
innerTickY = gaugeOptions.radius - (Math.sin(iSpeedAsAngleRad) * 10),
fromX = (options.center.X - gaugeOptions.radius) + innerTickX,//+ innerTickX ,// /2,
fromY = (gaugeOptions.center.Y - gaugeOptions.radius) + innerTickY ,//+ innerTickY, // /2,
endNeedleX = gaugeOptions.radius - (Math.cos(iSpeedAsAngleRad) * gaugeOptions.radius),//+40,
endNeedleY = gaugeOptions.radius - (Math.sin(iSpeedAsAngleRad) * gaugeOptions.radius),//+60, // controlled height of nedle
toX = (options.center.X - gaugeOptions.radius) + endNeedleX,
toY = (gaugeOptions.center.Y - gaugeOptions.radius) + endNeedleY,
line = createLine(options.center.X + 80, options.center.Y + 60, toX+75, toY+60, "rgb(3,2,245)", 5, 0.6); //80.60.75.65
// line = createLine(fromX, fromY, toX, toY, "rgb(3,2,245)", 5, 0.6);
drawLine(options, line);
}
drawLine Function
function drawLine(options, line) {
// Draw a line using the line object passed in
options.ctx.beginPath();
// Set attributes of open
options.ctx.globalAlpha = line.alpha;
options.ctx.lineWidth = line.lineWidth;
options.ctx.fillStyle = line.fillStyle;
options.ctx.strokeStyle = line.fillStyle;
options.ctx.moveTo(line.from.X,
line.from.Y);
// Plot the line
options.ctx.lineTo(
(line.to.X),
line.to.Y
);
options.ctx.stroke();
}

Here's how to draw a needle guage:
Translate to the center of the guage,
Rotate to a specified angle,
Draw the needle as a triangle,
Un-rotate by the same specified angle,
Un-translate by the center coordinates.
Example code and a Demo:
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var degrees=-90;
var radians=degrees*Math.PI/180;
$myslider=$('#myslider');
$myslider.attr({min:-180,max:0}).val(degrees);
$myslider.on('input change',function(){
var degrees=parseInt($(this).val());
var radians=degrees*Math.PI/180;
drawNeedle(cw/2,ch/2,150,radians);
});
drawNeedle(cw/2,ch/2,150,radians);
function drawNeedle(cx,cy,radius,radianAngle){
ctx.clearRect(0,0,cw,ch);
ctx.translate(cx,cy);
ctx.rotate(radianAngle);
ctx.beginPath();
ctx.moveTo(0,-5);
ctx.lineTo(radius,0);
ctx.lineTo(0,5);
ctx.fillStyle='blue';
ctx.fill();
ctx.rotate(-radianAngle);
ctx.translate(-cx,-cy);
ctx.beginPath();
ctx.arc(cx,cy,10,0,Math.PI*2);
ctx.fill();
}
body{ background-color:white; }
#canvas{border:1px solid red; }
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
Needle angle&nbsp <input id=myslider type=range><br>
<canvas id="canvas" width=512 height=512></canvas>

Related

How to fit multiple rotating rectangle inside the canvas (Javascript)

as mentioned I am trying to fit a whole circle of rectangle inside a canvas, but as seen, I can only show a fourth of the circle. I made a basic html document with a canvas element. Relative to the size of the canvas element I made a single rectangel and centered it in the middle of the canvas. With that, I tried to make a for loop which should rotate the rectangle while making a full circle. But it didn't work.
body{
background-color: #000000;
}
canvas {
padding: 0;
margin: auto;
display: block;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
background-color: white;
border:1px solid
}
<html>
<body>
<link rel="stylesheet" href="canvas.css">
<canvas id="myCanvas" width="900" height="900" ></canvas>
<script>
// Get id from canvas element
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
// Change size of rectangle
var recWidth = 40
var recHeight = 40
// Position rectangle in the middle of the canvas
var xPos = (document.getElementById("myCanvas").width/2) - (recWidth/2);
var yPos = (document.getElementById("myCanvas").height/2) - (recHeight/2);
// Convert degree to radian
const degToRad = (degrees) => {
return degrees / 180 * Math.PI;
}
// Number of rectangles
const num = 36;
for (let i = 0; i<num; i++){
const slice = degToRad (360 / num);
const angle = slice * i;
context.fillStyle = 'green';
context.save();
context.rotate (angle);
context.beginPath();
context.rect(xPos,yPos,recWidth,recHeight);
context.fill();
context.restore();
}
</script>
</body>
</html>
I hope I'm not violating SO protocols by creating a separate answer,
but I have done so to avoid some clutter with the new code.
Here is a cleaned up version of your code.
Explanation:
Move the center of rotation to the center of the canvas:
context.translate(width/2, height/2)
Then specify the radius for drawing as a quarter of canvas dimension.
rad = xPos/2
Do the rotation one slice at a time, leaving it in place (no save/restore).
(incorrect statement removed)
<script>
// Get id from canvas element
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.font = "14pt Arial";
context.fillStyle = "green";
// Change size of rectangle
var recWidth = 30
var recHeight = 30
// Position rectangle in the middle of the canvas
var canvW = document.getElementById("myCanvas").width;
var canvH = document.getElementById("myCanvas").height;
var xPos = canvW/2;
var yPos = canvH/2;
var rad = xPos/2; // radius = 1/4 of canvas dimensions
context.translate(xPos,yPos); // rotate around the center
// Convert degree to radian
const degToRad = (degrees) => {
return degrees / 180 * Math.PI;
}
// Number of rectangles
const num = 36;
const slice = degToRad (360 / num);
const angle = slice;
// animates the drawing to help see what is going on
// by introducing 1/2 second delay between individual draws
function draw1Rect(i)
{
i++;
if (i < num) setTimeout("draw1Rect("+i+")", 500);
context.rotate (angle);
context.fillRect(rad,rad,recWidth,recHeight);
context.fillText(i, rad-30, rad-30);
}
draw1Rect(0);
</script>
The problem is that most of your squares are being drawn outside the boundaries of the canvas.
It appears the canvas rotates around (0, 0) rather than around the center.
Outside the loop:
// Use a different center and a smaller radius
var xPos = (document.getElementById("myCanvas").width/4) - (recWidth/4);
var yPos = (document.getElementById("myCanvas").height/4) - (recHeight/4);
// Move the center
context.translate(xPos * 2, yPos * 9/4);
context.font = "14pt Arial";
Inside the loop:
context.fill(); // existing line of code
context.fillText(i, xPos-30, yPos-30); // added
context.restore(); // existing
Also: instead of context.beginPath(), context.rect() and context.fill() you can use context.fillRect().

Square Analog Clock with Raphaeljs

I am trying to create a kind of square clock with Raphael JS, rather than 12 digits mine has 8 digits. I have found a great example for round clocks on this page;
Emanuele Feronato
I wanted to create my clock based on this one, but I am having trouble while aligning digit dashes to their places. For the round one the base example uses some cosines and sins for calculating their places. But on the square one I want all the dashes to be aligned so that they will touch to the border of square.
Aiming something like this;
I was wondering what is the easiest and best way to calculate these dashes' places for a square one? Is there also a clean mathematical solution as used in the round clock for square one?
Here is the fiddle of my current template;
fiddle
Here is also my current template;
function draw_square_clock(svgId) {
canvas = Raphael(svgId, 200, 200);
var clock = canvas.rect(3, 3, 194, 194);
clock.attr({
"fill": "#ffffff",
"stroke": "#000000",
"stroke-width": "4"
});
var hour_sign;
var text_sign;
for (i = 0.0; i < 12; i = i + 1.5) {
//How am I going to calculate these four values to align hour signs to the edges of square
var start_x = 100 + Math.round(80 * Math.cos(30 * i * Math.PI / 180));
var start_y = 100 + Math.round(80 * Math.sin(30 * i * Math.PI / 180));
var end_x = 100 + Math.round(90 * Math.cos(30 * i * Math.PI / 180));
var end_y = 100 + Math.round(90 * Math.sin(30 * i * Math.PI / 180));
hour_sign = canvas.path("M" + start_x + " " + start_y + "L" + end_x + " " + end_y);
hour_sign.attr({
"stroke-width": "6"
});
hour_sign.transform("t0,0 s2");
}
hour_hand = canvas.path("M100 100L100 50");
hour_hand.attr({
stroke: "#000000",
"stroke-width": 6
});
var pin = canvas.circle(100, 100, 8);
pin.attr("fill", "#000000");
}
$(document).ready(function() {
draw_square_clock('clock_id');
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.4/raphael-min.js"></script>
<div id="clock_id"></div>
Mathematically:
Calculate the length of a corner tick:
// length of tick a 0 degrees
var tickLengthAt0=30;
// calculate length of corner tick
var cornerTickLength=Math.sqrt(2*(tickLengthAt0*tickLengthAt0));
Use trigonometry to calc each of the tick line segments:
var PI=Math.PI;
var cx=150;
var cy=150;
var width=200;
var height=200;
function calcTickPoints(x0,y0,length,angle){
x1=x0+length*Math.cos(angle);
y1=y0+length*Math.sin(angle);
return({ x0:x0, y0:y0, x1:x1, y1:y1 });
}
// Use calcTickPoints to calc the points of each tick
// top-right
var TR=calcTickPoints(cx+width/2,cy-height/2,cornerTickLength,PI*3/4);
// bottom-right
var BR=calcTickPoints(cx+width/2,cy+height/2,cornerTickLength,PI*5/4);
// bottom-left
var BL=calcTickPoints(cx-width/2,cy+height/2,cornerTickLength,PI*7/4);
// top-left
var TL=calcTickPoints(cx-width/2,cy-height/2,cornerTickLength,PI*9/4);
// right
var R=calcTickPoints(cx+width/2,cy,tickLengthAt0,PI);
// bottom
var B=calcTickPoints(cx,cy+height/2,tickLengthAt0,PI*3/2);
// left
var L=calcTickPoints(cx-width/2,cy,tickLengthAt0,PI*2);
// top
var T=calcTickPoints(cx,cy-height/2,tickLengthAt0,PI/2);
Example code (drawn on Canvas, but you can use SVG if desired):
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
var PI=Math.PI;
var PI2=PI*2;
var cx=150;
var cy=150;
var width=200;
var height=200;
var tickLengthAt0=30;
var cornerTickLength=Math.sqrt(2*(tickLengthAt0*tickLengthAt0));
drawFace();
function drawFace(){
// rect
ctx.strokeRect(cx-width/2,cy-height/2,width,height);
// top-right
drawTick(cx+width/2,cy-height/2,cornerTickLength,PI*3/4);
// bottom-right
drawTick(cx+width/2,cy+height/2,cornerTickLength,PI*5/4);
// bottom-left
drawTick(cx-width/2,cy+height/2,cornerTickLength,PI*7/4);
// top-left
drawTick(cx-width/2,cy-height/2,cornerTickLength,PI*9/4);
// right
drawTick(cx+width/2,cy,tickLengthAt0,PI);
// bottom
drawTick(cx,cy+height/2,tickLengthAt0,PI*3/2);
// left
drawTick(cx-width/2,cy,tickLengthAt0,PI*2);
// top
drawTick(cx,cy-height/2,tickLengthAt0,PI/2);
}
function drawTick(x0,y0,length,angle){
x1=x0+length*Math.cos(angle);
y1=y0+length*Math.sin(angle);
ctx.beginPath();
ctx.moveTo(x0,y0);
ctx.lineTo(x1,y1);
ctx.stroke();
return({ x0:x0, y0:y0, x1:x1, y1:y1 });
}
body{ background-color: ivory; }
#canvas{border:1px solid red; margin:0 auto; }
<canvas id="canvas" width=300 height=300></canvas>

SVG: a circle of Circles

So to explain simply i've this SVG circle element as so:
var circle = function(cx,cy,r) {
var svgCircle = document.createElementNS(NS,"circle");
svgCircle.setAttributeNS(null,"cx", cx);
svgCircle.setAttributeNS(null,"cy", cy);
svgCircle.setAttributeNS(null,"r", r);
return svgCircle;
}
it does the job it needs todo however i now need to create an abitrary amount of circles I.E 7, and position each so that they look like the outline of a circle.
so a circle made up of circles.
I just can't seem to understand how it works. Also an explanation or some guidance would go a long way!
This will draw satellite circles around a center-point
The key is to convert your calculated polar (angle,radius) coordinates to cartesian (x,y). See the added function polarToCartesian() in the example below:
<svg id="my_svg" height="400" width="400"></svg>
<script type="text/javascript">
function drawCircle(cx,cy,r){
var svgCircle = document.createElementNS('http://www.w3.org/2000/svg',"circle");
svgCircle.setAttributeNS(null,"cx", cx);
svgCircle.setAttributeNS(null,"cy", cy);
svgCircle.setAttributeNS(null,"r", r);
svgCircle.setAttributeNS(null,"stroke",'blue')
svgCircle.setAttributeNS(null,"fill",'transparent')
return svgCircle;
}
function polarToCartesian(center_x, center_y, radius, angle_in_degrees) {
var return_value = {}
var angle_in_radians = angle_in_degrees * Math.PI / 180.0;
return_value.x = center_x + radius * Math.cos(angle_in_radians);
return_value.y = center_y + radius * Math.sin(angle_in_radians);
return return_value;
}
/* ==============================
Loop to Draw Satellite circles
============================== */
// The center is the same for all circles
var cx = 200
var cy = 200
var radius_of_satellites_from_center = 100
var radius_of_small_circles = 10
var number_of_satellite_circles = 7
// The angle increments for each circle drawn
for(var n=0; n<number_of_satellite_circles; n++){
// Find how many degrees separate each circle
var degrees_of_separation = 360/number_of_satellite_circles
var angle_as_degrees = degrees_of_separation * n
var coordinates = polarToCartesian(cx, cy, radius_of_satellites_from_center, angle_as_degrees)
document.getElementById('my_svg').appendChild( drawCircle(coordinates.x,coordinates.y,radius_of_small_circles) )
}
</script>
This will draw concentric circles:
<svg id="my_svg" height="400" width="400"></svg>
<script type="text/javascript">
function drawCircle(cx,cy,r){
var svgCircle = document.createElementNS('http://www.w3.org/2000/svg',"circle");
svgCircle.setAttributeNS(null,"cx", cx);
svgCircle.setAttributeNS(null,"cy", cy);
svgCircle.setAttributeNS(null,"r", r);
svgCircle.setAttributeNS(null,"stroke",'blue')
svgCircle.setAttributeNS(null,"fill",'transparent')
return svgCircle;
}
/* ===============================
Loop to Draw concentric circles
=============================== */
// The center is the same for all circles
var cx = 200
var cy = 200
// More settings you can change
var starting_radius = 50
var number_of_circles = 7
var gap_between_circles = 3
// The radius increments for each circle drawn
for(var n=number_of_circles; n>0; n--){
var radius = starting_radius + n * gap_between_circles
document.getElementById('my_svg').appendChild( drawCircle(cx,cy,radius) )
}
</script>

Skew, size and rotate a rectangle to fit triangle perfectly

I'm trying to make half of a rectangle - devided diagonally - to fit inside a triangle.
Rotation works well, so does sizing of the rectangle. But once I try to skew it, it all gets messed up. Basically I want to simulate a 3D surface.
That means I have to find the angle of abc, where b is the center point. And then apply this angle as a skew to the rectangle. But for some reason that doesn't work as intended.
Here is a simple illustration of what I want to accomplish:
You will probably understand more once you take a look at the fiddle: http://jsfiddle.net/p7g7Y/11/
EDIT: Got the width right at least: http://jsfiddle.net/p7g7Y/12/
The piece of code you need to look at is at line 63 - 95.
Try comment out the transform, and you will see that rotation and size works well.
function triangle(a, b, c){
context.save();
//Draw the triangle
context.beginPath();
context.moveTo(a[0], a[1]);
context.lineTo(b[0], b[1]);
context.lineTo(c[0], c[1]);
context.lineTo(a[0], a[1]);
context.closePath();
context.stroke();
//Lets find the distance between a and b to set height of the image
var imgHeight = lineDistance(a, b);
//And the width b to c
var imgWidth = lineDistance(b, c);
//Now we gotta skew it acording to the rad between ba and bc
var skewAngle = find_angle(a,c,b); //Find angle and make it rad
//Find the angle of b to a line
var theta = Math.atan2(a[1] - b[1], a[0] - b[0]);
context.translate(a[0], a[1]); //Set origin of rotation
context.rotate(theta + 1.57079633); //Had to rotate it some more 1.57079633 = 90deg
context.transform(1, skewAngle, 0, 1, 0, 0);
context.rect( 0, 0, imgHeight, imgWidth);
context.stroke();
context.restore();
}
If anything is unclear, please ask! I would love some help on this!
It's easier if you solve the problem more generally: find a, b, c, d, e and f so that
// (x0, y0) maps to (x_0, y_0)
a*x0 + b*y0 + c = x_0
d*x0 + e*y0 + f = y_0
// (x1, y1) maps to (x_1, y_1)
a*x1 + b*y1 + c = x_1
d*x1 + e*y1 + f = y_1
// (x2, y2) maps to (x_2, y_2)
a*x2 + b*y2 + c = x_2
d*x2 + e*y2 + f = y_2
This 6x6 linear system is composed of two independent 3x3 linear systems:
a*x0 + b*y0 + c = x_0
a*x1 + b*y1 + c = x_1
a*x2 + b*y2 + c = x_2
d*x0 + e*y0 + f = y_0
d*x1 + e*y1 + f = y_1
d*x2 + e*y2 + f = y_2
Solving them gives you the 6 numbers to pass to setTransform to map any three points to other three points.
delta = x0*y1 + y0*x2 + x1*y2 - y1*x2 - y0*x1 - x0*y2
delta_a = x_0*y1 + y0*x_2 + x_1*y2 - y1*x_2 - y0*x_1 - x_0*y2
delta_b = x0*x_1 + x_0*x2 + x1*x_2 - x_1*x2 - x_0*x1 - x0*x_2
delta_c = x0*y1*x_2 + y0*x_1*x2 + x_0*x1*y2 - x_0*y1*x2 - y0*x1*x_2 - x0*x_1*y2
delta_d = y_0*y1 + y0*y_2 + y_1*y2 - y1*y_2 - y0*y_1 - y_0*y2
delta_e = x0*y_1 + y_0*x2 + x1*y_2 - y_1*x2 - y_0*x1 - x0*y_2
delta_f = x0*y1*y_2 + y0*y_1*x2 + y_0*x1*y2 - y_0*y1*x2 - y0*x1*y_2 - x0*y_1*y2
a = delta_a / delta
b = delta_b / delta
c = delta_c / delta
d = delta_d / delta
e = delta_e / delta
f = delta_f / delta
For a full description of 3d texture mapping using 2d canvas context see this more detailed answer.
Here’s how to calculate transforms necessary to fit a rectangle to a triangle:
Translate to the “pivot point” of your triangle – point B.
Rotate by the angle of side BC.
Skew in the X direction by the angle of corner B.
So, first translate:
// transform translate = pt2
var translate = pt2;
Then rotate:
// transform rotation = angleBC (based on slope of BC)
var rotation = Math.atan2((pt3.y-pt2.y),(pt3.x-pt2.x));
Finally skewX:
// transform skewX, based on angleB
var skewX = Math.tan(angleB-Math.PI/2);
Here’s how to get angleB for use in skewX:
// calculate segment lengths
var AB = Math.sqrt(Math.pow(pt2.x-pt1.x,2)+ Math.pow(pt2.y-pt1.y,2));
var BC = Math.sqrt(Math.pow(pt2.x-pt3.x,2)+ Math.pow(pt2.y-pt3.y,2));
var AC = Math.sqrt(Math.pow(pt3.x-pt1.x,2)+ Math.pow(pt3.y-pt1.y,2));
// calculate angleB using law of cosines
var angleB = Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB));
You’ll also need the width and height of the rectangle to draw:
// rectangle height = triangle altitude
var rectHeight = AB * Math.sin(angleB);
// rectangle width = triangle BC
var rectWidth = BC;
A small “gotcha”:
Your translate point is B, but rectangles are drawn starting at top-left.
This means you must offset your rectangle vertically by the rectHeight:
ctx.rect(0, -rectHeight, rectWidth, rectHeight);
Also, not really a “gotcha”, but more of a natual limitation:
The angle at corner B must be <180.
So, if your triangle “inverts”, I you’ll have to compensate by flipping points A and C.
Interesting project you have there!
Would you share a bit when you’re done?
Here is code and a Fiddle: http://jsfiddle.net/m1erickson/KKELu/
<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red;}
</style>
<script>
$(function(){
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var pt1={x:100,y:100};
var pt2={x:150,y:225};
var pt3={x:250,y:150};
drawTriangle();
drawRectangle();
function drawRectangle(){
// calc transform info
var info=analyzeTriangle();
ctx.save();
ctx.translate(info.translate.x,info.translate.y);
ctx.rotate(info.rotation);
ctx.transform(1,0,info.skewX,1,0,0);
ctx.beginPath();
// since rects origin is top left, must offset y by -height
ctx.rect(0,-info.rectHeight,info.rectWidth,info.rectHeight);
ctx.strokeStyle="purple";
ctx.stroke();
ctx.restore();
}
function drawTriangle(){
ctx.beginPath();
ctx.strokeStyle="blue";
ctx.moveTo(pt1.x,pt1.y);
ctx.lineTo(pt2.x,pt2.y);
ctx.lineTo(pt3.x,pt3.y);
ctx.closePath();
ctx.stroke();
ctx.fillStyle="rgba(255,255,0,0.10)";
ctx.fill();
}
function analyzeTriangle(){
// segment lengths
var AB = Math.sqrt(Math.pow(pt2.x-pt1.x,2)+ Math.pow(pt2.y-pt1.y,2));
var BC = Math.sqrt(Math.pow(pt2.x-pt3.x,2)+ Math.pow(pt2.y-pt3.y,2));
var AC = Math.sqrt(Math.pow(pt3.x-pt1.x,2)+ Math.pow(pt3.y-pt1.y,2));
// angleB = using law of cosines
var angleB = Math.acos((BC*BC+AB*AB-AC*AC)/(2*BC*AB));
// transform translate = pt2
var translate = pt2;
// transform rotation = angleBC (based on slope of BC)
var rotation = Math.atan2((pt3.y-pt2.y),(pt3.x-pt2.x));
// transform skewX, based on angleB
var skewX = Math.tan(angleB-Math.PI/2);
// rectangle height = triangle altitude
var rectHeight = AB * Math.sin(angleB);
// rectangle width = triangle BC
var rectWidth = BC;
return({
translate:translate,
rotation:rotation,
skewX:skewX,
rectHeight:rectHeight,
rectWidth:rectWidth
});
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=350 height=350></canvas>
</body>
</html>

Draw an arrow on HTML5 Canvas between two objects

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>

Categories

Resources