1 /* 2 Copyright 2008,2009 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software: you can redistribute it and/or modify 13 it under the terms of the GNU Lesser General Public License as published by 14 the Free Software Foundation, either version 3 of the License, or 15 (at your option) any later version. 16 17 JSXGraph is distributed in the hope that it will be useful, 18 but WITHOUT ANY WARRANTY; without even the implied warranty of 19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 20 GNU Lesser General Public License for more details. 21 22 You should have received a copy of the GNU Lesser General Public License 23 along with JSXGraph. If not, see <http://www.gnu.org/licenses/>. 24 */ 25 26 /** 27 * Creates a new instance of JXG.Polygon. 28 * @class Polygon stores all style and functional properties that are required 29 * to draw and to interactact with a polygon. 30 * @param {JXG.Board} board Reference to the board the polygon is to be drawn on. 31 * @param {Array} vertices Unique identifiers for the points defining the polygon. 32 * Last point must be first point. Otherwise, the first point will be added at the list. 33 * @param {Object} attributes An object which contains properties as given in {@link JXG.Options.elements} 34 * and {@link JXG.Options.polygon}. 35 * @constructor 36 * @extends JXG.GeometryElement 37 */ 38 39 JXG.Polygon = function (board, vertices, attributes) { 40 this.constructor(board, attributes, JXG.OBJECT_TYPE_POLYGON, JXG.OBJECT_CLASS_AREA); 41 42 var i, vertex, l, 43 attr_line = JXG.copyAttributes(attributes, board.options, 'polygon', 'borders'); 44 45 this.withLines = attributes.withlines; 46 this.attr_line = attr_line; 47 48 /** 49 * References to the points defining the polygon. The last vertex is the same as the first vertex. 50 * @type Array 51 */ 52 this.vertices = []; 53 for(i=0; i<vertices.length; i++) { 54 vertex = JXG.getRef(this.board, vertices[i]); 55 this.vertices[i] = vertex; 56 } 57 58 if(this.vertices[this.vertices.length-1] != this.vertices[0]) { 59 this.vertices.push(this.vertices[0]); 60 } 61 62 /** 63 * References to the border lines of the polygon. 64 * @type Array 65 */ 66 this.borders = []; 67 if (this.withLines) { 68 for(i = 0; i < this.vertices.length - 1; i++) { 69 attr_line.id = attr_line.ids && attr_line.ids[i]; 70 attr_line.strokecolor = JXG.isArray(attr_line.colors) && attr_line.colors[i % attr_line.colors.length] || attr_line.strokecolor; 71 if (attr_line.strokecolor===false) attr_line.strokecolor = 'none'; 72 l = JXG.createSegment(board, [this.vertices[i], this.vertices[i+1]], attr_line); 73 l.dump = false; 74 this.borders[i] = l; 75 l.parentPolygon = this; 76 } 77 } 78 79 // Add polygon as child to defining points 80 for(i=0; i<this.vertices.length-1; i++) { // last vertex is first vertex 81 vertex = JXG.getReference(this.board, this.vertices[i]); 82 vertex.addChild(this); 83 } 84 85 86 /* Register polygon at board */ 87 this.id = this.board.setId(this, 'Py'); 88 this.board.renderer.drawPolygon(this); 89 this.board.finalizeAdding(this); 90 this.elType = 'polygon'; 91 92 // create label 93 this.createLabel(); 94 95 this.methodMap.borders = 'borders'; 96 this.methodMap.vertices = 'vertices'; 97 98 }; 99 JXG.Polygon.prototype = new JXG.GeometryElement; 100 101 102 JXG.extend(JXG.Polygon.prototype, /** @lends JXG.Polygon.prototype */ { 103 /** 104 * Checks whether (x,y) is near the polygon. 105 * @param {Number} x Coordinate in x direction, screen coordinates. 106 * @param {Number} y Coordinate in y direction, screen coordinates. 107 * @return {Boolean} Returns true, if (x,y) is inside or at the boundary the polygon, otherwise false. 108 */ 109 hasPoint: function (x,y) { 110 111 var i, j, len, c = false; 112 113 if (this.visProp.hasinnerpoints) { 114 // All points of the polygon trigger hasPoint: inner and boundary points 115 len = this.vertices.length; 116 // See http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html for a reference 117 for (i=0, j=len-2; i<len-1; j=i++) { // last vertex is first vertex 118 if (((this.vertices[i].coords.scrCoords[2] > y) != (this.vertices[j].coords.scrCoords[2] > y)) 119 && (x < (this.vertices[j].coords.scrCoords[1] - this.vertices[i].coords.scrCoords[1]) * (y - this.vertices[i].coords.scrCoords[2]) 120 / (this.vertices[j].coords.scrCoords[2] - this.vertices[i].coords.scrCoords[2]) + this.vertices[i].coords.scrCoords[1])) { 121 c = !c; 122 } 123 } 124 } else { 125 // Only boundary points trigger hasPoint 126 len = this.borders.length; 127 for (i=0; i<len; i++) { 128 if (this.borders[i].hasPoint(x,y)) { 129 c = true; 130 break; 131 } 132 } 133 } 134 135 return c; 136 }, 137 138 /** 139 * Uses the boards renderer to update the polygon. 140 */ 141 updateRenderer: function () { 142 if (this.needsUpdate) { 143 this.board.renderer.updatePolygon(this); 144 this.needsUpdate = false; 145 } 146 if(this.hasLabel && this.label.content.visProp.visible) { 147 //this.label.setCoordinates(this.coords); 148 this.label.content.update(); 149 //this.board.renderer.updateLabel(this.label); 150 this.board.renderer.updateText(this.label.content); 151 } 152 }, 153 154 /** 155 * return TextAnchor 156 */ 157 getTextAnchor: function() { 158 var a = this.vertices[0].X(), 159 b = this.vertices[0].Y(), 160 x = a, 161 y = b, 162 i; 163 164 for (i = 0; i < this.vertices.length; i++) { 165 if (this.vertices[i].X() < a) 166 a = this.vertices[i].X(); 167 if (this.vertices[i].X() > x) 168 x = this.vertices[i].X(); 169 if (this.vertices[i].Y() > b) 170 b = this.vertices[i].Y(); 171 if (this.vertices[i].Y() < y) 172 y = this.vertices[i].Y(); 173 } 174 175 return new JXG.Coords(JXG.COORDS_BY_USER, [(a + x)*0.5, (b + y)*0.5], this.board); 176 }, 177 178 getLabelAnchor: JXG.shortcut(JXG.Polygon.prototype, 'getTextAnchor'), 179 180 // documented in geometry element 181 cloneToBackground: function() { 182 var copy = {}, er; 183 184 copy.id = this.id + 'T' + this.numTraces; 185 this.numTraces++; 186 copy.vertices = this.vertices; 187 copy.visProp = JXG.deepCopy(this.visProp, this.visProp.traceattributes, true); 188 copy.visProp.layer = this.board.options.layer.trace; 189 copy.board = this.board; 190 JXG.clearVisPropOld(copy); 191 192 er = this.board.renderer.enhancedRendering; 193 this.board.renderer.enhancedRendering = true; 194 this.board.renderer.drawPolygon(copy); 195 this.board.renderer.enhancedRendering = er; 196 this.traces[copy.id] = copy.rendNode; 197 198 return this; 199 }, 200 201 /** 202 * Hide the polygon including its border lines. It will still exist but not visible on the board. 203 */ 204 hideElement: function() { 205 var i; 206 207 this.visProp.visible = false; 208 this.board.renderer.hide(this); 209 210 for(i = 0; i < this.borders.length; i++) { 211 this.borders[i].hideElement(); 212 } 213 214 if (this.hasLabel && JXG.exists(this.label)) { 215 this.label.hiddenByParent = true; 216 if(this.label.content.visProp.visible) { 217 this.board.renderer.hide(this.label.content); 218 } 219 } 220 }, 221 222 /** 223 * Make the element visible. 224 */ 225 showElement: function() { 226 var i; 227 228 this.visProp.visible = true; 229 this.board.renderer.show(this); 230 231 for(i = 0; i < this.borders.length; i++) { 232 this.borders[i].showElement(); 233 } 234 235 if (this.hasLabel && JXG.exists(this.label)) { 236 if(this.label.content.visProp.visible) { 237 this.board.renderer.show(this.label.content); 238 } 239 } 240 }, 241 242 /** 243 * returns the area of the polygon 244 */ 245 Area: function() { 246 //Surveyor's Formula 247 var area = 0, i; 248 249 for (i = 0; i < this.vertices.length - 1; i++) { 250 area += (this.vertices[i].X()*this.vertices[i+1].Y()-this.vertices[i+1].X()*this.vertices[i].Y()); // last vertex is first vertex 251 } 252 area /= 2.0; 253 return Math.abs(area); 254 }, 255 256 /** 257 * This method removes the SVG or VML nodes of the lines and the filled area from the renderer, to remove 258 * the object completely you should use {@link JXG.Board#removeObject}. 259 */ 260 remove: function () { 261 var i; 262 for (i = 0; i < this.borders.length; i++) { 263 this.board.removeObject(this.borders[i]); 264 } 265 //this.board.renderer.remove(this.rendNode); 266 JXG.GeometryElement.prototype.remove.call(this); 267 }, 268 269 /** 270 * Finds the index to a given point reference. 271 * @param {JXG.Point} p Reference to an element of type {@link JXG.Point} 272 */ 273 findPoint: function (p) { 274 var i; 275 276 if (!JXG.isPoint(p)) { 277 return -1; 278 } 279 280 for (i = 0; i < this.vertices.length; i++) { 281 if (this.vertices[i].id === p.id) { 282 return i; 283 } 284 } 285 286 return -1; 287 }, 288 289 /** 290 * Add more points to the polygon. The new points will be inserted at the end. 291 * @param {%} % Arbitrary number of points 292 * @returns {JXG.Polygon} Reference to the polygon 293 */ 294 addPoints: function () { 295 var args = Array.prototype.slice.call(arguments); 296 297 return this.insertPoints.apply(this, [this.vertices.length-2].concat(args)); 298 }, 299 300 /** 301 * Adds more points to the vertex list of the polygon, starting with index <tt><i</tt> 302 * @param {Number} i The position where the new vertices are inserted, starting with 0. 303 * @param {%} % Arbitrary number of points to insert. 304 * @returns {JXG.Polygon} Reference to the polygon object 305 */ 306 insertPoints: function () { 307 var idx, i, npoints = [], tmp; 308 309 if (arguments.length === 0) { 310 return this; 311 } 312 313 idx = arguments[0]; 314 315 if (idx < 0 || idx > this.vertices.length-2) { 316 return this; 317 } 318 319 for (i = 1; i < arguments.length; i++) { 320 if (JXG.isPoint(arguments[i])) { 321 npoints.push(arguments[i]); 322 } 323 } 324 325 tmp = this.vertices.slice(0, idx+1).concat(npoints); 326 this.vertices = tmp.concat(this.vertices.slice(idx+1)); 327 328 if (this.withLines) { 329 tmp = this.borders.slice(0, idx); 330 this.board.removeObject(this.borders[idx]); 331 332 for (i = 0; i < npoints.length; i++) { 333 tmp.push(JXG.createSegment(this.board, [this.vertices[idx+i], this.vertices[idx+i+1]], this.attr_line)); 334 } 335 tmp.push(JXG.createSegment(this.board, [this.vertices[idx+npoints.length], this.vertices[idx+npoints.length+1]], this.attr_line)); 336 337 this.borders = tmp.concat(this.borders.slice(idx)); 338 } 339 340 this.board.update(); 341 342 return this; 343 }, 344 345 /** 346 * Removes given set of vertices from the polygon 347 * @param {%} % Arbitrary number of vertices as {@link JXG.Point} elements or index numbers 348 * @returns {JXG.Polygon} Reference to the polygon 349 */ 350 removePoints: function () { 351 var i, j, idx, nvertices = [], nborders = [], 352 nidx = [], partition = []; 353 354 // partition: 355 // in order to keep the borders which could be recycled, we have to partition 356 // the set of removed points. I.e. if the points 1, 2, 5, 6, 7, 10 are removed, 357 // the partition is 358 // 1-2, 5-7, 10-10 359 // this gives us the borders, that can be removed and the borders we have to create. 360 361 362 // remove the last vertex which is identical to the first 363 this.vertices = this.vertices.slice(0, this.vertices.length-1); 364 365 // collect all valid parameters as indices in nidx 366 for (i = 0; i < arguments.length; i++) { 367 if (JXG.isPoint(arguments[i])) { 368 idx = this.findPoint(arguments[i]); 369 } 370 371 if (JXG.isNumber(idx) && idx > -1 && idx < this.vertices.length && JXG.indexOf(nidx, idx) === -1) { 372 nidx.push(idx); 373 } 374 } 375 376 // sort the elements to be eliminated 377 nidx = nidx.sort(); 378 nvertices = this.vertices.slice(); 379 nborders = this.borders.slice(); 380 381 // initialize the partition 382 if (this.withLines) { 383 partition.push([nidx[nidx.length-1]]); 384 } 385 386 // run through all existing vertices and copy all remaining ones to nvertices 387 // compute the partition 388 for (i = nidx.length-1; i > -1; i--) { 389 nvertices[nidx[i]] = -1; 390 391 if (this.withLines && (nidx[i] - 1 > nidx[i-1])) { 392 partition[partition.length-1][1] = nidx[i]; 393 partition.push([nidx[i-1]]); 394 } 395 } 396 397 // finalize the partition computation 398 if (this.withLines) { 399 partition[partition.length-1][1] = nidx[0]; 400 } 401 402 // update vertices 403 this.vertices = []; 404 for (i = 0; i < nvertices.length; i++) { 405 if (JXG.isPoint(nvertices[i])) { 406 this.vertices.push(nvertices[i]); 407 } 408 } 409 if (this.vertices[this.vertices.length-1].id !== this.vertices[0].id) { 410 this.vertices.push(this.vertices[0]); 411 } 412 413 // delete obsolete and create missing borders 414 if (this.withLines) { 415 for (i = 0; i < partition.length; i++) { 416 for (j = partition[i][1] - 1; j < partition[i][0] + 1; j++) { 417 // special cases 418 if (j < 0) { 419 // first vertex is removed, so the last border has to be removed, too 420 j = 0; 421 this.board.removeObject(this.borders[nborders.length-1]); 422 nborders[nborders.length-1] = -1; 423 } else if (j > nborders.length-1) { 424 j = nborders.length-1; 425 } 426 427 this.board.removeObject(this.borders[j]); 428 nborders[j] = -1; 429 } 430 431 // only create the new segment if it's not the closing border. the closing border is getting a special treatment at the end 432 // the if clause is newer than the min/max calls inside createSegment; i'm sure this makes the min/max calls obsolete, but 433 // just to be sure... 434 if (partition[i][1] !== 0 && partition[i][0] !== nvertices.length-1) { 435 nborders[partition[i][0] - 1] = JXG.createSegment(this.board, [nvertices[Math.max(partition[i][1]-1, 0)], nvertices[Math.min(partition[i][0]+1, this.vertices.length-1)]], this.attr_line); 436 } 437 } 438 439 this.borders = []; 440 for (i = 0; i < nborders.length; i++) { 441 if (nborders[i] !== -1) { 442 this.borders.push(nborders[i]); 443 } 444 } 445 446 // if the first and/or the last vertex is removed, the closing border is created at the end. 447 if (partition[0][1] === 5 || partition[partition.length-1][1] === 0) { 448 this.borders.push(JXG.createSegment(this.board, [this.vertices[0], this.vertices[this.vertices.length-2]], this.attr_line)); 449 } 450 } 451 452 this.board.update(); 453 454 return this; 455 }, 456 457 getParents: function () { 458 var p = [], i; 459 460 for (i = 0; i < this.vertices.length; i++) { 461 p.push(this.vertices[i].id); 462 } 463 return p; 464 }, 465 466 getAttributes: function () { 467 var attr = JXG.GeometryElement.prototype.getAttributes.call(this), i; 468 469 if (this.withLines) { 470 attr.lines = attr.lines || {}; 471 attr.lines.ids = []; 472 attr.lines.colors = []; 473 474 for (i = 0; i < this.borders.length; i++) { 475 attr.lines.ids.push(this.borders[i].id); 476 attr.lines.colors.push(this.borders[i].visProp.strokecolor); 477 } 478 } 479 480 return attr; 481 }, 482 483 /** 484 * Moves the line by the difference of two coordinates. 485 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 486 * @param {Array} coords coordinates in screen/user units 487 * @param {Array} oldcoords previous coordinates in screen/user units 488 * @returns {JXG.Line} 489 */ 490 setPositionDirectly: function (method, coords, oldcoords) { 491 var dc, t, i, len, 492 c = new JXG.Coords(method, coords, this.board), 493 oldc = new JXG.Coords(method, oldcoords, this.board); 494 495 len = this.vertices.length-1; 496 for (i=0; i<len; i++) { 497 if (!this.vertices[i].draggable()) { 498 return this; 499 } 500 } 501 502 dc = JXG.Math.Statistics.subtract(c.usrCoords, oldc.usrCoords); 503 t = this.board.create('transform', dc.slice(1), {type:'translate'}); 504 t.applyOnce(this.vertices.slice(0,-1)); 505 506 return this; 507 } 508 509 }); 510 511 512 /** 513 * @class A polygon is an area enclosed by a set of border lines which are determined by a list of points. Each two 514 * consecutive points of the list define a line. 515 * @pseudo 516 * @constructor 517 * @name Polygon 518 * @type Polygon 519 * @augments JXG.Polygon 520 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 521 * @param {Array} vertices The polygon's vertices. If the first and the last vertex don't match the first one will be 522 * added to the array by the creator. 523 * @example 524 * var p1 = board.create('point', [0.0, 2.0]); 525 * var p2 = board.create('point', [2.0, 1.0]); 526 * var p3 = board.create('point', [4.0, 6.0]); 527 * var p4 = board.create('point', [1.0, 3.0]); 528 * 529 * var pol = board.create('polygon', [p1, p2, p3, p4]); 530 * </pre><div id="682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div> 531 * <script type="text/javascript"> 532 * (function () { 533 * var board = JXG.JSXGraph.initBoard('682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 534 * p1 = board.create('point', [0.0, 2.0]), 535 * p2 = board.create('point', [2.0, 1.0]), 536 * p3 = board.create('point', [4.0, 6.0]), 537 * p4 = board.create('point', [1.0, 3.0]), 538 * cc1 = board.create('polygon', [p1, p2, p3, p4]); 539 * })(); 540 * </script><pre> 541 */ 542 JXG.createPolygon = function(board, parents, attributes) { 543 var el, i, attr = JXG.copyAttributes(attributes, board.options, 'polygon'); 544 545 // Sind alles Punkte? 546 for(i = 0; i < parents.length; i++) { 547 parents[i] = JXG.getReference(board, parents[i]); 548 if(!JXG.isPoint(parents[i])) 549 throw new Error("JSXGraph: Can't create polygon with parent types other than 'point'."); 550 } 551 552 el = new JXG.Polygon(board, parents, attr); 553 el.isDraggable = true; 554 555 return el; 556 }; 557 558 559 /** 560 * @class Constructs a regular polygon. It needs two points which define the base line and the number of vertices. 561 * @pseudo 562 * @description Constructs a regular polygon. It needs two points which define the base line and the number of vertices, or a set of points. 563 * @constructor 564 * @name RegularPolygon 565 * @type Polygon 566 * @augments Polygon 567 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 568 * @param {JXG.Point_JXG.Point_Number} p1,p2,n The constructed regular polygon has n vertices and the base line defined by p1 and p2. 569 * @example 570 * var p1 = board.create('point', [0.0, 2.0]); 571 * var p2 = board.create('point', [2.0, 1.0]); 572 * 573 * var pol = board.create('regularpolygon', [p1, p2, 5]); 574 * </pre><div id="682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div> 575 * <script type="text/javascript"> 576 * (function () { 577 * var board = JXG.JSXGraph.initBoard('682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 578 * p1 = board.create('point', [0.0, 2.0]), 579 * p2 = board.create('point', [2.0, 1.0]), 580 * cc1 = board.create('regularpolygon', [p1, p2, 5]); 581 * })(); 582 * </script><pre> 583 * @example 584 * var p1 = board.create('point', [0.0, 2.0]); 585 * var p2 = board.create('point', [4.0,4.0]); 586 * var p3 = board.create('point', [2.0,0.0]); 587 * 588 * var pol = board.create('regularpolygon', [p1, p2, p3]); 589 * </pre><div id="096a78b3-bd50-4bac-b958-3be5e7df17ed" style="width: 400px; height: 400px;"></div> 590 * <script type="text/javascript"> 591 * (function () { 592 * var board = JXG.JSXGraph.initBoard('096a78b3-bd50-4bac-b958-3be5e7df17ed', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 593 * p1 = board.create('point', [0.0, 2.0]), 594 * p2 = board.create('point', [4.0, 4.0]), 595 * p3 = board.create('point', [2.0,0.0]), 596 * cc1 = board.create('regularpolygon', [p1, p2, p3]); 597 * })(); 598 * </script><pre> 599 */ 600 JXG.createRegularPolygon = function(board, parents, attributes) { 601 var el, i, n, p = [], rot, c, len, pointsExist, attr; 602 603 if (JXG.isNumber(parents[parents.length-1]) && parents.length!=3) { 604 throw new Error("JSXGraph: A regular polygon needs two points and a number as input."); 605 } 606 607 len = parents.length; 608 n = parents[len-1]; 609 if ((!JXG.isNumber(n) && !JXG.isPoint(JXG.getReference(board, n))) || n<3) { 610 throw new Error("JSXGraph: The third parameter has to be number greater than 2 or a point."); 611 } 612 613 if (JXG.isPoint(JXG.getReference(board, n))) { // Regular polygon given by n points 614 n = len; 615 pointsExist = true; 616 } else { 617 len--; 618 pointsExist = false; 619 } 620 621 // The first two parent elements have to be points 622 for(i=0; i<len; i++) { 623 parents[i] = JXG.getReference(board, parents[i]); 624 if(!JXG.isPoint(parents[i])) 625 throw new Error("JSXGraph: Can't create regular polygon if the first two parameters aren't points."); 626 } 627 628 p[0] = parents[0]; 629 p[1] = parents[1]; 630 attr = JXG.copyAttributes(attributes, board.options, 'polygon', 'vertices'); 631 for (i=2;i<n;i++) { 632 rot = board.create('transform', [Math.PI*(2.0-(n-2)/n),p[i-1]], {type:'rotate'}); 633 if (pointsExist) { 634 p[i] = parents[i]; 635 p[i].addTransform(parents[i-2],rot); 636 } else { 637 if (JXG.isArray(attr.ids) && attr.ids.length >= n-2) { 638 attr.id = attr.ids[i-2]; 639 } 640 p[i] = board.create('point',[p[i-2],rot], attr); 641 p[i].type = JXG.OBJECT_TYPE_CAS; 642 643 // The next two lines of code are need to make regular polgonmes draggable 644 // The new helper points are set to be draggable. 645 p[i].isDraggable = true; 646 p[i].visProp.fixed = false; 647 } 648 } 649 attr = JXG.copyAttributes(attributes, board.options, 'polygon'); 650 el = board.create('polygon', p, attr); 651 652 el.elType = 'regularpolygon'; 653 654 return el; 655 }; 656 657 JXG.JSXGraph.registerElement('polygon', JXG.createPolygon); 658 JXG.JSXGraph.registerElement('regularpolygon', JXG.createRegularPolygon); 659