Solved

Add arrowheads to drawn lines HTML 5 canvas

Posted on 2012-03-17
8
1,673 Views
Last Modified: 2012-03-30
I am trying to add arrowheads to the ends of lines drawn by the user on HTML 5 Canvas. Of course, the big problem is figuring out which way the line is pointing.   I have tried creating a stack keeping track of the points along the line then popping 5-20 points off the stack to get the direction of the end of the line. This method has two problems.  If the stack is two short, no arrow is drawn (I might be able to handle this by first checking the size of the stack and adjust the number of pops).  The bigger problem is if the user moves the mouse a little as he releases the button, the arrow may point the wrong way (see the image.)  You can see this solution in action at www.barnwellmd.com/PainDiagram/PainDiagram.html (click on referred pain to see the lines with the arrowheads). Below is the code using the stack (just the pertinent code).

 Any suggestions? TIA
Arrows
    // This is called when you start holding down the mouse button.
    // This starts the pencil drawing.
    this.mousedown = function (ev) {
	    stackx = new Array();
	    stacky = new Array();
	    context.lineWidth = 10;
	    context.beginPath();
            tool.started = true;
    };

    // This function is called every time you move the mouse. 
    this.mousemove = function (ev) {
      if (tool.started) {
	        stackx.push(ev._x);
		stacky.push(ev._y);
		context.lineTo(ev._x, ev._y);
	        context.stroke();
      }

    // This is called when you release the mouse button.
    this.mouseup = function (ev) {
		if (tool.started) 
		{
			for (var i = 0; i < 20; i++)
			{
				stackx.pop();
				stacky.pop();
			}
			var angle = Math.atan2(ev._y-stacky.pop(),ev._x-stackx.pop());
		  	context.beginPath();
			context.moveTo(ev._x, ev._y);
			context.lineTo(ev._x-20*Math.cos(angle-Math.PI/6),ev._y-20*Math.sin(angle-Math.PI/6));
		  	context.lineTo(ev._x-20*Math.cos(angle+Math.PI/6),ev._y-20*Math.sin(angle+Math.PI/6)); 
			context.closePath();
			context . fillStyle  = color_value;
			context.fill();
			}
			tool.started = false;
			tool.mousemove(ev);
		}

Open in new window

0
Comment
Question by:thenelson
  • 4
  • 4
8 Comments
 
LVL 38

Expert Comment

by:Tom Beck
ID: 37737411
I think you are giving the end user too much freedom to draw a line with an arrow at the end. If Referred Pain is pain that "starts in one area which causes pain elsewhere", then a simple straight line with an arrow at the end would be the best graphic illustration of that. Why let them draw a curved line at all? This would simplify the task for you. Just record the mousedown coordinates and on mouseup, draw a line between the two points and add the arrow head.
0
 
LVL 39

Author Comment

by:thenelson
ID: 37740360
Actually, a curved line is needed.A classic referred pain you may have heard of is the pain running down the left arm from a heart attack. In this case the line would be drawn from the center of the chest over and down the left arm.  Another you may have heard about is sciatica.which starts at the lower spine and runs down the back of the leg. We have added this type of pain to determine the new patient's depth of understanding of their problem and also for the doctor to record her findings.  Your response got me thinking, however.. Suppose I code the script to create connected line segments of, say, 20 pixels long.  In other words, as the user moves the mouse, the code creates a line segment every 20 pixels. I'm thinking about how to accomplish that.
0
 
LVL 38

Expert Comment

by:Tom Beck
ID: 37740572
That would be easy to do.
this.mousemove = function (ev) {
      if (tool.started) {
	        stackx.push(ev._x);
			stacky.push(ev._y);
			if(stackx.length % 20 == 0){  //draw a line every time the array length is divisible by 20
			    context.lineTo(ev._x, ev._y);
	                    context.stroke();
			}
      }
    };

Open in new window

0
Industry Leaders: We Want Your Opinion!

We value your feedback.

Take our survey and automatically be enter to win anyone of the following:
Yeti Cooler, Amazon eGift Card, and Movie eGift Card!

 
LVL 38

Assisted Solution

by:Tom Beck
Tom Beck earned 500 total points
ID: 37740732
Complete solution:
var lastXseg, lastYseg, prevXseg, prevYseg;
    this.mousemove = function (ev) {
      if (tool.started) {
	        stackx.push(ev._x);
			stacky.push(ev._y);
			if (color_value == "#FF7F50")	 //referred pain
			{
			    if(stackx.length % 20 == 0){
			        if(stackx.length == 20){
			            prevXseg = stackx[0];
			            prevYseg = stacky[0];
			        }else{
			            prevXseg = lastXseg;
			            prevYseg = lastYseg;
			        }
			        lastXseg = ev._x;
			        lastYseg = ev._y;				
			        context.lineTo(ev._x, ev._y);
	                context.stroke();
			    }
			}else{
			    context.lineTo(ev._x, ev._y);
	            context.stroke();
			}
      }
    };

Open in new window


I am saving the beginning and end points of each 20 pixel long line segment and using them later in the mouseup event to get the segment's angle so I can apply the arrow head in the correct orientation. Because the segments are straight for every 20 pixels and any ending short segments are not recorded, the arrows are consistently pointing in the proper direction to the line.

B.T.W, I am using the canvasutilities.js file from this site to draw the arrow heads. Your method is clever, but because I flunked trigonometry, I don't understand it : )
http://www.dbp-consulting.com/tutorials/canvas/CanvasArrow.html
Referred Pain Arrows
0
 
LVL 39

Author Comment

by:thenelson
ID: 37742945
With your code, I am not getting the line drawn on the screen starting where I am starting the line with my mouse (where i click the mouse button).  I am using a very slow computer (mine got a bad virus) so I think what is happening is the first pixels are not getting captured. Perhaps reducing the modulo form 20 to 5 might help.  If not, I think we need to draw the line without the modulo operation and keep track of the 20 pixel long line segments for the arrow direction. I got several meetings today (and fixing my main computer) so I won't get to it until tonight. I guess it is a good thing I am using this slow computer.  

The arrowheads draw by the other routine are very nice.  I think I'll switch to them.
0
 
LVL 38

Expert Comment

by:Tom Beck
ID: 37743095
My script will not draw the first line segment until you have traveled at least 20 pixels. Maybe you could allow the first segment to draw immediately until it gets to 20 and then switch to drawing in 20 pixel segments. Reducing the modulo below the size of the arrow head (I'm using a 15 pixel arrow head) will put you right back to the original problem; i.e. if the last 5 pixel segment is skewed off the general direction of the drawn line, the arrow head will orient to that skewed segment instead of the general direction.
0
 
LVL 39

Accepted Solution

by:
thenelson earned 0 total points
ID: 37762872
I optimized the code you suggested to create line segments of a specific length. It now runs fine on a Pentium 500 mhz computer with 500 mb of ram. I also changed the shape of the arrows and now the arrow draws itself with the base of the arrow attached to the end of the line instead of the point of the arrow. With all that, I find using a line segment length of 7 works right.

Thanks again for your help. I have one more question I would appreciate your help with: http:Q_27647700.html.
arrows
this.mousedown = function (ev) {
   segCount = 0;
   if (color_value == "#FF7F50")	 //referred pain
   {
      context.lineWidth = 2;
      reptSeg = 7;
   }
   else
   {
      context.lineWidth = 10;
      reptSeg = 1;
   }
}:

 this.mousemove = function (ev)
{
   if (tool.started)
  {
      if(!segCount)
      {
         prevX = lastX;
         prevY = lastY;
         lastX = ev._x;
         lastY = ev._y;
         segCount = reptSeg;
         context.lineTo(ev._x, ev._y);
         context.stroke();
      }
      segCount--;
   }
};

this.mouseup = function (ev) {
   if (tool.started) 
   {
      if (color_value == "#FF7F50")
      {
         var angle = Math.atan2(lastY-prevY,lastX-prevX);
         context.beginPath();
         context.moveTo(lastX+15*Math.cos(angle),lastY+15*Math.sin(angle));
         context.lineTo(lastX-7*Math.cos(angle-Math.PI/4),lastY-7*Math.sin(angle-Math.PI/4));
         context.lineTo(lastX-7*Math.cos(angle+Math.PI/4),lastY-7*Math.sin(angle+Math.PI/4)); 
         context.closePath();
         context . fillStyle  = color_value;
         context.fill();
      }
...

Open in new window

0
 
LVL 39

Author Closing Comment

by:thenelson
ID: 37786119
tommyBoy's suggestion pointed me to a very workable solution.
0

Featured Post

Creating Instructional Tutorials  

For Any Use & On Any Platform

Contextual Guidance at the moment of need helps your employees/users adopt software o& achieve even the most complex tasks instantly. Boost knowledge retention, software adoption & employee engagement with easy solution.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Title # Comments Views Activity
Two decimal 5 30
Why is my $_POST not going to results page 10 42
CSS for Popup in ASP.NET 4 22
sort Multi-dimensional array 6 19
Today, the web development industry is booming, and many people consider it to be their vocation. The question you may be asking yourself is – how do I become a web developer?
Originally, this post was published on Monitis Blog, you can check it here . Websites are getting bigger and more complicated by the day. Video, images and custom fonts are all great for showcasing your product or service. But the price to pay in…
Learn how to set up basic frames and paths in Prezi and understand the open space that Prezi allows you to create presentations in.
It’s easy to embed any of your public Prezi presentations on your website or social network to share with others. Learn how simple it is in this tutorial.

696 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question