Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people, just like you, are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
Solved

Add arrowheads to drawn lines HTML 5 canvas

Posted on 2012-03-17
8
1,613 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
Free Tool: IP Lookup

Get more info about an IP address or domain name, such as organization, abuse contacts and geolocation.

One of a set of tools we are providing to everyone as a way of saying thank you for being a part of the community.

 
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

Free Tool: ZipGrep

ZipGrep is a utility that can list and search zip (.war, .ear, .jar, etc) archives for text patterns, without the need to extract the archive's contents.

One of a set of tools we're offering as a way to say thank you for being a part of the community.

Question has a verified solution.

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

Use these top 10 tips to master the art of email signature design. Create an email signature design that will easily wow recipients, promote your brand and highlight your professionalism.
International Data Corporation (IDC) prognosticates that before the current the year gets over disbursing on IT framework products to be sent in cloud environs will be $37.1B.
The viewer will learn the basics of jQuery including how to code hide show and toggles. Reference your jQuery libraries: (CODE) Include your new external js/jQuery file: (CODE) Write your first lines of code to setup your site for jQuery…
Learn how to create interesting presentations by including videos to keep your audience engaged using Prezi. Select "Insert" from the top menu in your Prezi editor: Select "YouTube Video": Paste the video URL into the prompt: "Select "Insert":…

790 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