Dogofwars
asked on
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.
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.
Right, I had a free 20 minutes, so knocked this up for you:
Hope it's ok?
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"> ->
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>
ASKER
That is really great, is it possible to do it with the shape or polyline fonction, question to improve the speed.
Errr... Maybe... I've not looked into VML before today
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 ;-)
poly.points = x + ',' + y + ',' + xPoints[0] + ',' + yPoints[0] + ',' + xPoints[1] + ',' + yPoints[1] + ',' + xPoints[2] + ',' + yPoints[2] ;
Or errr... something ;-)
ASKER
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.
I'll have a look....hold on...
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
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 ) ;
}
ASKER
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.
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
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
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"> ->
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>
ASKER
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.
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
Type a dfferent ID to draw a new arrow, or type an existing id to change the existing arrow
Tim
ASKER
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)
>> you will create a closure that will do a memory leak
Can you explain how (just for my own peace of mind) ;-)
Ta
Can you explain how (just for my own peace of mind) ;-)
Ta
ASKER
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 :)
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
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
ASKER
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.
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.
Great job TimYates. I was searching the web for line drawing and this code is a great help
:-D Glad I could help :-D
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-micro soft-com:v ml">
<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
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-micro
<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!
</body>
</html>
Open in New Window Select All
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>
<v:line from="112,112" to="137,137" arrowok="true">
<v:stroke weight="2" endarrow="classic" endarrowwidth="medium" endarrowlength="long"/>
</v:line>
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