Javascript: How to do a VML line with an arrow at the end dynamicaly

Hi,

  I would like to do a VML line in JavaScript with an arrow at the end. Let says I have 4 coordinate that do 3 straight line and at the end an arrow. Basically I want to link two box with an line with an arrow at the end.

Lets says I have X1,Y1,X2,Y2,X3,Y3,X4,Y4

X1,Y1 are the start coordinate and X4,Y4 are then end coordinate

X1 = 100
Y1 = 100

X2 = 200
Y2 = 100

X3 = 200
Y3 = 150

X4 = 300
Y4 = 150

So at the end coordinate that is X4,Y4 it will put an arrow of in the right direction depending of X3,Y3 and X4,Y4 coordinate.

I use dynamic DHTML and VML in JavaScript so that is not a problem but to do specifically that I am not sure. I check on the web and the documentation to do that is very sporadic.

I use IE6 on an intranet so don't need to be compatible with the other browser anyway I use an HTA so.
DogofwarsAsked:
Who is Participating?
I wear a lot of hats...

"The solutions and answers provided on Experts Exchange have been extremely helpful to me over the last few years. I wear a lot of hats - Developer, Database Administrator, Help Desk, etc., so I know a lot of things but not a lot about one thing. Experts Exchange gives me answers from people who do know a lot about one thing, in a easy to use platform." -Todd S.

TimYatesCommented:
Is the problem with the actual drawing, or the algorithm for drawing an arrow head pointing in the right direction?

I have code for drawing an arrow, but it's in Java, so won't help with the VML required to draw a line :-/

Tim
0
TimYatesCommented:
Right, I had a free 20 minutes, so knocked this up for you:

Hope it's ok?
<html xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  <style>
    v\:* {behavior:url(#default#VML);}
  </style>
  <script language="JavaScript">
  function drawLine( container, x, y, xx, yy )
  {
    var line = document.createElement("v:line");
    line.style.position = "absolute";
    line.from = x + "px," + y + "px";
    line.to = xx + "px," + yy + "px";
    line = container.appendChild( line ) ;
  }
 
  function drawArrow( container, x, y, xx, yy )
  {
    var arrowWidth = 10.0 ; // change these two values
    var theta = 0.423 ;     // to change the size of the head of the arrow
    var xPoints = [] ;
    var yPoints = []
    var vecLine = [] ;
    var vecLeft = [] ;
    var fLength;
    var th;
    var ta;
    var baseX, baseY ;
 
    xPoints[ 0 ] = xx ;
    yPoints[ 0 ] = yy ;
 
    // build the line vector
    vecLine[ 0 ] = xPoints[ 0 ] - x ;
    vecLine[ 1 ] = yPoints[ 0 ] - y ;
 
    // build the arrow base vector - normal to the line
    vecLeft[ 0 ] = -vecLine[ 1 ] ;
    vecLeft[ 1 ] = vecLine[ 0 ] ;
 
    // setup length parameters
    fLength = Math.sqrt( vecLine[0] * vecLine[0] + vecLine[1] * vecLine[1] ) ;
    th = arrowWidth / ( 2.0 * fLength ) ;
    ta = arrowWidth / ( 2.0 * ( Math.tan( theta ) / 2.0 ) * fLength ) ;
 
    // find the base of the arrow
    baseX = xPoints[ 0 ] - ta * vecLine[0] ;
    baseY = yPoints[ 0 ] - ta * vecLine[1] ;
 
    // build the points on the sides of the arrow
    xPoints[ 1 ] = baseX + th * vecLeft[0] ;
    yPoints[ 1 ] = baseY + th * vecLeft[1] ;
    xPoints[ 2 ] = baseX - th * vecLeft[0] ;
    yPoints[ 2 ] = baseY - th * vecLeft[1] ;
 
    drawLine( container, x, y, xPoints[ 0 ], yPoints[ 0 ] ) ;
    drawLine( container, xPoints[ 0 ], yPoints[ 0 ], xPoints[ 1 ], yPoints[ 1 ] ) ;
    drawLine( container, xPoints[ 0 ], yPoints[ 0 ], xPoints[ 2 ], yPoints[ 2 ] ) ;
  }  
 
  function makeCall()
  {
    var canvas = document.getElementById( 'canvas' ) ;
    var x1 = document.getElementById( 'x1' ).value ;
    var y1 = document.getElementById( 'y1' ).value ;
    var x2 = document.getElementById( 'x2' ).value ;
    var y2 = document.getElementById( 'y2' ).value ;
    drawArrow( canvas, x1, y1, x2, y2 ) ;
  }
  </script>
</head>
<body>
  <div id="canvas" style="width: 600px ; height: 400px">
  </div>
  X1: <input type="text" size="4" id="x1" value="10"> Y1: <input type="text" size="4" id="y1" value="10"> -&gt;
  X2: <input type="text" size="4" id="x2" value="300"> Y2: <input type="text" size="4" id="y2" value="120">
  <button onclick="makeCall()">DRAW!</button>
 
</body>
</html>

Open in new window

0
DogofwarsAuthor Commented:
That is really great, is it possible to do it with the shape or polyline fonction, question to improve the speed.
0
The Ultimate Tool Kit for Technolgy Solution Provi

Broken down into practical pointers and step-by-step instructions, the IT Service Excellence Tool Kit delivers expert advice for technology solution providers. Get your free copy for valuable how-to assets including sample agreements, checklists, flowcharts, and more!

TimYatesCommented:
Errr...  Maybe...  I've not looked into VML before today
0
TimYatesCommented:
Sure...if you close the arrowhead, it could be a polyline..  Just create a polyline instead of a line and set the points attribute to:

  poly.points = x + ',' + y + ',' + xPoints[0] + ',' + yPoints[0] + ',' + xPoints[1] + ',' + yPoints[1] + ',' + xPoints[2] + ',' + yPoints[2] ;

Or errr... something ;-)
0
DogofwarsAuthor Commented:
Strange when I use the polyline I can draw it at the first instance but when I try to update it's coordinate it screw up it need to use the path option instead of the points.Ppolyline is just a overlapped(wrapper) of shape to make it easier to initiate but when you want to update it's position and path you need to use it the same way as shape and it's a bit confusing.
0
TimYatesCommented:
I'll have a look....hold on...
0
TimYatesCommented:
Here's the same thing using a polyline
<html xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  <style>
    v\:* {behavior:url(#default#VML);}
  </style>
  <script language="JavaScript">
  function drawPolyLine( container, pts )
  {
    var line = document.createElement( "v:polyLine" ) ;
    var spts = '' ;
    for( i = 0 ; i < pts.length ; i += 2 )
    {
      if( i > 0 )
        spts += ',' ;
      spts += pts[i] + ',' + pts[i+1] ;
    }
    line.points = spts ;
    container.appendChild( line ) ;
  }
  
  function drawArrow( container, x, y, xx, yy )
  {
    var arrowWidth = 10.0 ;
    var theta = 0.423 ;
    var xPoints = [] ;
    var yPoints = []
    var vecLine = [] ;
    var vecLeft = [] ;
    var fLength;
    var th;
    var ta;
    var baseX, baseY ;
 
    xPoints[ 0 ] = xx ;
    yPoints[ 0 ] = yy ;
 
    // build the line vector
    vecLine[ 0 ] = xPoints[ 0 ] - x ;
    vecLine[ 1 ] = yPoints[ 0 ] - y ;
 
    // build the arrow base vector - normal to the line
    vecLeft[ 0 ] = -vecLine[ 1 ] ;
    vecLeft[ 1 ] = vecLine[ 0 ] ;
 
    // setup length parameters
    fLength = Math.sqrt( vecLine[0] * vecLine[0] + vecLine[1] * vecLine[1] ) ;
    th = arrowWidth / ( 2.0 * fLength ) ;
    ta = arrowWidth / ( 2.0 * ( Math.tan( theta ) / 2.0 ) * fLength ) ;
 
    // find the base of the arrow
    baseX = xPoints[ 0 ] - ta * vecLine[0] ;
    baseY = yPoints[ 0 ] - ta * vecLine[1] ;
 
    // build the points on the sides of the arrow
    xPoints[ 1 ] = baseX + th * vecLeft[0] ;
    yPoints[ 1 ] = baseY + th * vecLeft[1] ;
    xPoints[ 2 ] = baseX - th * vecLeft[0] ;
    yPoints[ 2 ] = baseY - th * vecLeft[1] ;
 
    drawPolyLine( container, [ x, y, baseX, baseY, xPoints[ 1 ], yPoints[ 1 ], xPoints[ 0 ], yPoints[ 0 ], xPoints[ 2 ], yPoints[ 2 ], baseX, baseY ] ) ;
  }  
 
  function makeCall()
  {
    var canvas = document.getElementById( 'canvas' ) ;
    var x1 = document.getElementById( 'x1' ).value ;
    var y1 = document.getElementById( 'y1' ).value ;
    var x2 = document.getElementById( 'x2' ).value ;
    var y2 = document.getElementById( 'y2' ).value ;
    drawArrow( canvas, x1, y1, x2, y2 ) ;
  }
  </script>
</head>
<body>
  <div id="canvas" style="width: 600px ; height: 400px">
  </div>
  X1: <input type="text" size="4" id="x1" value="10"> Y1: <input type="text" size="4" id="y1" value="10"> -&gt;
  X2: <input type="text" size="4" id="x2" value="300"> Y2: <input type="text" size="4" id="y2" value="120">
  <button onclick="makeCall()">DRAW!</button>
 
</body>
</html>

Open in new window

0

Experts Exchange Solution brought to you by

Your issues matter to us.

Facing a tech roadblock? Get the help and guidance you need from experienced professionals who care. Ask your question anytime, anywhere, with no hassle.

Start your 7-day free trial
TimYatesCommented:
Sorry, you can change the drawPolyLine function to this...  I had a bit of early morning brain freeze ;-)
  function drawPolyLine( container, pts )
  {
    var line = document.createElement( "v:polyLine" ) ;
    line.points = pts.join( ',' ) ;
    container.appendChild( line ) ;
  }

Open in new window

0
DogofwarsAuthor Commented:
What is nice with VML is that you can update it's position when it's already declared once but that does not work that is what I am struggling with, anyway I will give you the point since the great effort you have give, I thank you very much.
0
TimYatesCommented:
Ahhh, i see!  I thought you just wanted to create a line...  I'll have a quick go to see if I can work out how to do it...

One way should be to store these polyline elements in an array and access them that way, the other would be to give them an ID and do it that way...

Tim
0
TimYatesCommented:
How's this?
<html xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  <style>
    v\:* {behavior:url(#default#VML);}
  </style>
  <script language="JavaScript">
 
  function makeNewPolyLine( pts )
  {
    var line = document.createElement( "v:polyLine" ) ;
    line.points = pts.join( ',' ) ;
    line.style.position = 'absolute' ;
    line.stroked = true ;
    line.filled = true ;
    line.fillcolor = 'black' ;
    return line ;
  }
  
  function drawArrow( container, lineId, x, y, xx, yy )
  {
    var arrowWidth = 10.0 ;
    var theta = 0.423 ;
    var xPoints = [] ;
    var yPoints = []
    var vecLine = [] ;
    var vecLeft = [] ;
    var fLength;
    var th;
    var ta;
    var baseX, baseY ;
 
    xPoints[ 0 ] = xx ;
    yPoints[ 0 ] = yy ;
 
    // build the line vector
    vecLine[ 0 ] = xPoints[ 0 ] - x ;
    vecLine[ 1 ] = yPoints[ 0 ] - y ;
 
    // build the arrow base vector - normal to the line
    vecLeft[ 0 ] = -vecLine[ 1 ] ;
    vecLeft[ 1 ] = vecLine[ 0 ] ;
 
    // setup length parameters
    fLength = Math.sqrt( vecLine[0] * vecLine[0] + vecLine[1] * vecLine[1] ) ;
    th = arrowWidth / ( 2.0 * fLength ) ;
    ta = arrowWidth / ( 2.0 * ( Math.tan( theta ) / 2.0 ) * fLength ) ;
 
    // find the base of the arrow
    baseX = xPoints[ 0 ] - ta * vecLine[0] ;
    baseY = yPoints[ 0 ] - ta * vecLine[1] ;
 
    // build the points on the sides of the arrow
    xPoints[ 1 ] = baseX + th * vecLeft[0] ;
    yPoints[ 1 ] = baseY + th * vecLeft[1] ;
    xPoints[ 2 ] = baseX - th * vecLeft[0] ;
    yPoints[ 2 ] = baseY - th * vecLeft[1] ;
 
    var ptArr = [ x, y, baseX, baseY, xPoints[ 1 ], yPoints[ 1 ], xPoints[ 0 ], yPoints[ 0 ], xPoints[ 2 ], yPoints[ 2 ], baseX, baseY ] ;
    var oldLine = document.getElementById( lineId ) ;
 
    if( oldLine ) // a line with this ID exists, so just change it
    {
      oldLine.points.value = ptArr.join( ',' ) ;  // need to use points.value here for some mental reason ;)
    }
    else // add a new line
    {
      var newLine = makeNewPolyLine( ptArr ) ;
      newLine.setAttribute( "id", lineId ) ;
      container.appendChild( newLine ) ;
    }
  }  
 
  function makeCall()
  {
    var id  = document.getElementById( 'theid' ).value ;
    var canvas = document.getElementById( 'canvas' ) ;
    var x1 = document.getElementById( 'x1' ).value ;
    var y1 = document.getElementById( 'y1' ).value ;
    var x2 = document.getElementById( 'x2' ).value ;
    var y2 = document.getElementById( 'y2' ).value ;
    drawArrow( canvas, id, x1, y1, x2, y2 ) ;
  }
  </script>
</head>
<body>
  <div id="canvas" style="width: 600px ; height: 400px">
  </div>
  LineID: <input type="text" size="4" id="theid" value="line1">
  X1: <input type="text" size="4" id="x1" value="10"> Y1: <input type="text" size="4" id="y1" value="10"> -&gt;
  X2: <input type="text" size="4" id="x2" value="300"> Y2: <input type="text" size="4" id="y2" value="120">
  <button onclick="makeCall()">DRAW!</button>
 
</body>
</html>

Open in new window

0
DogofwarsAuthor Commented:
Usually it make 3 line like a S shape if you want, I have been able to draw those line but once the object has been instanciate with an ID of course well you cannot change their position dynamically and that is what I am struggling with.
0
TimYatesCommented:
That's what that latest code does...

Type a dfferent ID to draw a new arrow, or type an existing id to change the existing arrow

Tim
0
DogofwarsAuthor Commented:
Hmmm that's not the way to do with dynamic object, you will create a closure that will do a memory leak, you need to dispose of them or update them. Thank you anyway, I just need to find a way to update the path now.  I have make great progress because of you, I was stuck at that point, just need to fine tune my code :) To give more precision about what I am doing it's basicaly two drag and drop "box" and the lines interconnect the two "box" but it redraw itself everytime the "box" is moving(in real time)
0
TimYatesCommented:
>> you will create a closure that will do a memory leak

Can you explain how (just for my own peace of mind) ;-)

Ta
0
DogofwarsAuthor Commented:
Well the DHTML or VML element is tag back to a javascript variable creating a closure because both have a reference of the other and so on. If you just create over it will just dump the old data and both would not be disosiate therefor creating a memory leak. You have to get rid of the Javascript variable and the DHTML element variable in order to cleanly delete both assosication. So it mean that I have to delete the DHTML element and do over if I want to do that, creating and overload on the system that is useless and also it shows on the display, it's better to update the polyline instead of getting rid of and so on. It's a bit tricky to understand, you can find some information on closure and javascript and DHTML/VML elements with our friend google :)
0
TimYatesCommented:
But I am updating the VML line

And I am not storing a reference to the element

Have you looked at the latest code?  Or are you just assuming it's the same as the other code?

I find the old vml line by using document.getElementById to fetch it out of the DOM which is the same as with any other html element...

I'm confused as to where exactly the latest code "leaks memory"

Tim
0
DogofwarsAuthor Commented:
oups you're right sorry for that though with that I have not being able to update it they way you do it

oldLine.points.value

though the path sub-element exist because basically the polyline is basically an initiator(wrapper) of a shape element which does not have a point element.  I will retest your code just to see how it work.

As for the memory leaking check for DHTML closure there is a lot of information on the web and it's kinda complicate to understand or at least explain.
0
BobSiemensCommented:
Great job TimYates.  I was searching the web for line drawing and this code is a great help
0
TimYatesCommented:
:-D  Glad I could help :-D
0
Tul02Commented:
Hi,

For the solution below....is it possible to capture the screen coordinates and draw the line, instead of accepting the input from 4 text boxes?

Thanks

====================================

<html xmlns:v="urn:schemas-microsoft-com:vml">
<head>
  <style>
    v\:* {behavior:url(#default#VML);}
  </style>
  <script language="JavaScript">
 
  function makeNewPolyLine( pts )
  {
    var line = document.createElement( "v:polyLine" ) ;
    line.points = pts.join( ',' ) ;
    line.style.position = 'absolute' ;
    line.stroked = true ;
    line.filled = true ;
    line.fillcolor = 'black' ;
    return line ;
  }
 
  function drawArrow( container, lineId, x, y, xx, yy )
  {
    var arrowWidth = 10.0 ;
    var theta = 0.423 ;
    var xPoints = [] ;
    var yPoints = []
    var vecLine = [] ;
    var vecLeft = [] ;
    var fLength;
    var th;
    var ta;
    var baseX, baseY ;
 
    xPoints[ 0 ] = xx ;
    yPoints[ 0 ] = yy ;
 
    // build the line vector
    vecLine[ 0 ] = xPoints[ 0 ] - x ;
    vecLine[ 1 ] = yPoints[ 0 ] - y ;
 
    // build the arrow base vector - normal to the line
    vecLeft[ 0 ] = -vecLine[ 1 ] ;
    vecLeft[ 1 ] = vecLine[ 0 ] ;
 
    // setup length parameters
    fLength = Math.sqrt( vecLine[0] * vecLine[0] + vecLine[1] * vecLine[1] ) ;
    th = arrowWidth / ( 2.0 * fLength ) ;
    ta = arrowWidth / ( 2.0 * ( Math.tan( theta ) / 2.0 ) * fLength ) ;
 
    // find the base of the arrow
    baseX = xPoints[ 0 ] - ta * vecLine[0] ;
    baseY = yPoints[ 0 ] - ta * vecLine[1] ;
 
    // build the points on the sides of the arrow
    xPoints[ 1 ] = baseX + th * vecLeft[0] ;
    yPoints[ 1 ] = baseY + th * vecLeft[1] ;
    xPoints[ 2 ] = baseX - th * vecLeft[0] ;
    yPoints[ 2 ] = baseY - th * vecLeft[1] ;
 
    var ptArr = [ x, y, baseX, baseY, xPoints[ 1 ], yPoints[ 1 ], xPoints[ 0 ], yPoints[ 0 ], xPoints[ 2 ], yPoints[ 2 ], baseX, baseY ] ;
    var oldLine = document.getElementById( lineId ) ;
 
    if( oldLine ) // a line with this ID exists, so just change it
    {
      oldLine.points.value = ptArr.join( ',' ) ;  // need to use points.value here for some mental reason ;)
    }
    else // add a new line
    {
      var newLine = makeNewPolyLine( ptArr ) ;
      newLine.setAttribute( "id", lineId ) ;
      container.appendChild( newLine ) ;
    }
  }  
 
  function makeCall()
  {
    var id  = document.getElementById( 'theid' ).value ;
    var canvas = document.getElementById( 'canvas' ) ;
    var x1 = document.getElementById( 'x1' ).value ;
    var y1 = document.getElementById( 'y1' ).value ;
    var x2 = document.getElementById( 'x2' ).value ;
    var y2 = document.getElementById( 'y2' ).value ;
    drawArrow( canvas, id, x1, y1, x2, y2 ) ;
  }
  </script>
</head>
<body>
  <div id="canvas" style="width: 600px ; height: 400px">
  </div>
  LineID: <input type="text" size="4" id="theid" value="line1">
  X1: <input type="text" size="4" id="x1" value="10"> Y1: <input type="text" size="4" id="y1" value="10"> ->
  X2: <input type="text" size="4" id="x2" value="300"> Y2: <input type="text" size="4" id="y2" value="120">
  <button onclick="makeCall()">DRAW!</button>
 
</body>
</html>
 
Open in New Window Select All

   
0
cupCommented:
Do you really need a draw arrow - VML already has built in arrowheads


<v:line from="112,112" to="137,137" arrowok="true">
  <v:stroke weight="2" endarrow="classic" endarrowwidth="medium" endarrowlength="long"/>
</v:line>
0
It's more than this solution.Get answers and train to solve all your tech problems - anytime, anywhere.Try it for free Edge Out The Competitionfor your dream job with proven skills and certifications.Get started today Stand Outas the employee with proven skills.Start learning today for free Move Your Career Forwardwith certification training in the latest technologies.Start your trial today
Web Languages and Standards

From novice to tech pro — start learning today.