This document explains how you can manipulate a polygon using ECMAScript. It is hoped that the reader will discover more than just how to manipulate polygons, but how to uncover similar such actions in the literature. In general, there are bindings between SVG and ECMAScript to manipulate tags and attributes which are difficult to manipulate textually (by setting attributes as we have been).
The tutorial will lead up to the following example, which allow the user to manipulate the vertices of a polygon.
This example assumes an arbitrary polygon has been provided with id="polygon". Our first step is to create a collection of <use> tags copying a circle and places them at the vertices.
01: <svg version="1.1" baseProfile="full" 02: xmlns="http://www.w3.org/2000/svg" 03: width="500px" height="500px" viewBox="-250 -250 500 500" 04: onload="setup()"> 05: 06: <script> 07: // <![CDATA[ 08: 09: var svgNS = "http://www.w3.org/2000/svg"; 10: var xlinkNS = "http://www.w3.org/1999/xlink"; 11: var polygon, points_group; 12: 13: function setup() { 14: polygon = document.getElementById("polygon"); 15: points_group = document.getElementById("points"); 16: 17: var vertex_list = polygon.points; 18: var i=0; 19: while (i < vertex_list.numberOfItems) { 20: var newusetag = document.createElementNS(svgNS, "use"); 21: newusetag.setAttributeNS(xlinkNS, "xlink:href", "#vertex"); 22: var vertex = vertex_list.getItem(i); 23: newusetag.setAttributeNS(null, "x", ""+vertex.x); 24: newusetag.setAttributeNS(null, "y", ""+vertex.y); 25: newusetag.setAttributeNS(null, "id", "v"+i); 26: points_group.appendChild(newusetag); 27: i=i+1; 28: } 29: } 30: 31: // ]]> 32: </script> 33: <defs> 34: <circle id="vertex" r="5" cx="0" cy="0" fill="yellow" stroke="black" stroke-width="1px"/> 35: </defs> 36: <polygon id="polygon" points="200,0 0,200 -200,0 0,-200 0,0" stroke="lightblue" stroke-width="3px" fill="blue"/> 37: <g id="points"> 38: </g> 39: </svg>
The new thing here is that we are calling some methods that will not work in general (say on a <rect> tag). Consider the line:
This line selects the element whose id="polygon". The type of the object returned is a SVGPolygonElement, as can be seen in the description of the <polygon> element in the SVG Recommendation. (See the line under Dom Interfaces.)
An SVGPolygonElement is an ECMAScript object which must have certain properties and methods (functions). This is promised by the SVG Recommendation's description of SVGPolygonElement. In particular, the object must implement a list of interfaces. This list is SVGElement, SVGTests, SVGLangSpace, SVGExternalResourcesRequired, SVGStylable, SVGTransformable and SVGAnimatedPoints. All but the last one are standard interfaces implemented by every graphical SVG element.
All these interfaces are described by the SVG Recommendation. In particular, you can find the SVG Recommendation's description of SVGAnimatedPoints. (The word Animated comes from the fact that the positions of points can be animated using XML markup.) You can see there that an object implementing the SVGPolygonElement must have an attribute called points, which is of type SVGPointList. In particular, we get store a reference to this object on line 17, where we call:
The purpose of an SVGPointList is to store a list of SVGPoints. You can see how it does this by looking at the the SVG Recommendation's description of SVGPointList. There we can see that we can call numberOfItems and getItem(i), which returns an SVGPoint. The SVGPoint is also of course described by the Recommendation. There we can see how to get the x- and y-coordinates as we did.
A closer look at the the SVG Recommendation's description of SVGPointList reveals that we can also insert points, replace points and remove points from the list. We use this in our final version to allow us to manipulate points.
01: <svg version="1.1" baseProfile="full" 02: xmlns="http://www.w3.org/2000/svg" 03: width="500px" height="500px" viewBox="-250 -250 500 500" 04: onload="setup()" onmousemove="drag(evt)" onmouseup="release(evt)"> 05: 06: <script> 07: // <![CDATA[ 08: 09: var svgNS = "http://www.w3.org/2000/svg"; 10: var xlinkNS = "http://www.w3.org/1999/xlink"; 11: var polygon, points_group; 12: 13: // global variables for handling dragging: 14: var dragging = false; 15: var drag_i = 0; 16: var lastx = 0, lasty = 0; 17: var drag_element; 18: 19: function setup() { 20: polygon = document.getElementById("polygon"); 21: points_group = document.getElementById("points"); 22: 23: var vertex_list = polygon.points; 24: var i=0; 25: while (i < vertex_list.numberOfItems) { 26: var newusetag = document.createElementNS(svgNS, "use"); 27: newusetag.setAttributeNS(xlinkNS, "xlink:href", "#vertex"); 28: var vertex = vertex_list.getItem(i); 29: newusetag.setAttributeNS(null, "x", ""+vertex.x); 30: newusetag.setAttributeNS(null, "y", ""+vertex.y); 31: newusetag.setAttributeNS(null, "id", "v"+i); 32: newusetag.setAttributeNS(null, "onmousedown", "startDragging(evt,"+i+")"); 33: points_group.appendChild(newusetag); 34: i=i+1; 35: } 36: } 37: 38: function startDragging(evt,i){ 39: dragging=true; 40: drag_i=i; 41: drag_element = document.getElementById("v"+i); 42: 43: // Get the mouse location: 44: var position = document.rootElement.createSVGPoint(); 45: position.x = evt.clientX; 46: position.y = evt.clientY; 47: var matrix = drag_element.getScreenCTM(); 48: var correctPosition=position.matrixTransform(matrix.inverse()); 49: lastx = correctPosition.x; 50: lasty = correctPosition.y; 51: } 52: 53: function drag(evt) { 54: if (dragging) { 55: // Get the mouse location: 56: var position = document.rootElement.createSVGPoint(); 57: position.x = evt.clientX; 58: position.y = evt.clientY; 59: var matrix = drag_element.getScreenCTM(); 60: var correctPosition = position.matrixTransform(matrix.inverse()); 61: newx = correctPosition.x; 62: newy = correctPosition.y; 63: 64: if ( (newx != lastx) || (newy != lasty) ) { 65: var curx=parseFloat(drag_element.getAttributeNS(null, "x")); 66: var x=curx+newx-lastx; 67: var cury=parseFloat(drag_element.getAttributeNS(null, "y")); 68: var y =cury+newy-lasty; 69: 70: drag_element.setAttributeNS(null,"x",""+x); 71: drag_element.setAttributeNS(null,"y",""+y); 72: var new_vertex = document.rootElement.createSVGPoint(); 73: new_vertex.x=x; 74: new_vertex.y=y; 75: polygon.points.replaceItem(new_vertex,drag_i); 76: 77: lastx=newx; 78: lasty=newy; 79: 80: } 81: } 82: } 83: 84: function release(evt) { 85: dragging=false; 86: } 87: 88: // ]]> 89: </script> 90: <defs> 91: <circle id="vertex" r="5" cx="0" cy="0" fill="yellow" stroke="black" stroke-width="1px" cursor="pointer"/> 92: </defs> 93: <polygon id="polygon" points="200,0 0,200 -200,0 0,-200 0,0" stroke="lightblue" stroke-width="3px" fill="blue"/> 94: <g id="points"> 95: </g> 96: </svg>
The dragging here is done just as you would for dragging a point (or pumpkin) as described before. The only major difference here is that we also update the associated vertex of the polygon. This is done on line 81.
A second more minor difference is that when we process a mousedown event, we pass an integer in addition to the mouse event. This integer records the the mouse is pressing.