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 // create label 86 this.createLabel(); 87 88 /* Register polygon at board */ 89 this.id = this.board.setId(this, 'Py'); 90 this.board.renderer.drawPolygon(this); 91 this.board.finalizeAdding(this); 92 93 this.methodMap.borders = 'borders'; 94 this.methodMap.vertices = 'vertices'; 95 96 this.elType = 'polygon'; 97 }; 98 JXG.Polygon.prototype = new JXG.GeometryElement; 99 100 101 JXG.extend(JXG.Polygon.prototype, /** @lends JXG.Polygon.prototype */ { 102 /** 103 * Checks whether (x,y) is near the polygon. 104 * @param {Number} x Coordinate in x direction, screen coordinates. 105 * @param {Number} y Coordinate in y direction, screen coordinates. 106 * @return {Boolean} Returns true, if (x,y) is inside or at the boundary the polygon, otherwise false. 107 */ 108 hasPoint: function (x,y) { 109 110 var i, j, len, c = false; 111 112 if (this.visProp.hasinnerpoints) { 113 // All points of the polygon trigger hasPoint: inner and boundary points 114 len = this.vertices.length; 115 // See http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html for a reference 116 for (i=0, j=len-2; i<len-1; j=i++) { // last vertex is first vertex 117 if (((this.vertices[i].coords.scrCoords[2] > y) != (this.vertices[j].coords.scrCoords[2] > y)) 118 && (x < (this.vertices[j].coords.scrCoords[1] - this.vertices[i].coords.scrCoords[1]) * (y - this.vertices[i].coords.scrCoords[2]) 119 / (this.vertices[j].coords.scrCoords[2] - this.vertices[i].coords.scrCoords[2]) + this.vertices[i].coords.scrCoords[1])) { 120 c = !c; 121 } 122 } 123 } else { 124 // Only boundary points trigger hasPoint 125 len = this.borders.length; 126 for (i=0; i<len; i++) { 127 if (this.borders[i].hasPoint(x,y)) { 128 c = true; 129 break; 130 } 131 } 132 } 133 134 return c; 135 }, 136 137 /** 138 * Uses the boards renderer to update the polygon. 139 */ 140 updateRenderer: function () { 141 if (this.needsUpdate) { 142 this.board.renderer.updatePolygon(this); 143 this.needsUpdate = false; 144 } 145 if(this.hasLabel && this.label.content.visProp.visible) { 146 //this.label.setCoordinates(this.coords); 147 this.label.content.update(); 148 //this.board.renderer.updateLabel(this.label); 149 this.board.renderer.updateText(this.label.content); 150 } 151 }, 152 153 /** 154 * return TextAnchor 155 */ 156 getTextAnchor: function() { 157 var a = this.vertices[0].X(), 158 b = this.vertices[0].Y(), 159 x = a, 160 y = b, 161 i; 162 163 for (i = 0; i < this.vertices.length; i++) { 164 if (this.vertices[i].X() < a) 165 a = this.vertices[i].X(); 166 if (this.vertices[i].X() > x) 167 x = this.vertices[i].X(); 168 if (this.vertices[i].Y() > b) 169 b = this.vertices[i].Y(); 170 if (this.vertices[i].Y() < y) 171 y = this.vertices[i].Y(); 172 } 173 174 return new JXG.Coords(JXG.COORDS_BY_USER, [(a + x)*0.5, (b + y)*0.5], this.board); 175 }, 176 177 getLabelAnchor: JXG.shortcut(JXG.Polygon.prototype, 'getTextAnchor'), 178 179 // documented in geometry element 180 cloneToBackground: function() { 181 var copy = {}, er; 182 183 copy.id = this.id + 'T' + this.numTraces; 184 this.numTraces++; 185 copy.vertices = this.vertices; 186 copy.visProp = JXG.deepCopy(this.visProp, this.visProp.traceattributes, true); 187 copy.visProp.layer = this.board.options.layer.trace; 188 copy.board = this.board; 189 JXG.clearVisPropOld(copy); 190 191 er = this.board.renderer.enhancedRendering; 192 this.board.renderer.enhancedRendering = true; 193 this.board.renderer.drawPolygon(copy); 194 this.board.renderer.enhancedRendering = er; 195 this.traces[copy.id] = copy.rendNode; 196 197 return this; 198 }, 199 200 /** 201 * Hide the polygon including its border lines. It will still exist but not visible on the board. 202 */ 203 hideElement: function() { 204 var i; 205 206 this.visProp.visible = false; 207 this.board.renderer.hide(this); 208 209 for(i = 0; i < this.borders.length; i++) { 210 this.borders[i].hideElement(); 211 } 212 213 if (this.hasLabel && JXG.exists(this.label)) { 214 this.label.hiddenByParent = true; 215 if(this.label.content.visProp.visible) { 216 this.board.renderer.hide(this.label.content); 217 } 218 } 219 }, 220 221 /** 222 * Make the element visible. 223 */ 224 showElement: function() { 225 var i; 226 227 this.visProp.visible = true; 228 this.board.renderer.show(this); 229 230 for(i = 0; i < this.borders.length; i++) { 231 this.borders[i].showElement(); 232 } 233 234 if (this.hasLabel && JXG.exists(this.label)) { 235 if(this.label.content.visProp.visible) { 236 this.board.renderer.show(this.label.content); 237 } 238 } 239 }, 240 241 /** 242 * returns the area of the polygon 243 */ 244 Area: function() { 245 //Surveyor's Formula 246 var area = 0, i; 247 248 for (i = 0; i < this.vertices.length - 1; i++) { 249 area += (this.vertices[i].X()*this.vertices[i+1].Y()-this.vertices[i+1].X()*this.vertices[i].Y()); // last vertex is first vertex 250 } 251 area /= 2.0; 252 return Math.abs(area); 253 }, 254 255 /** 256 * This method removes the SVG or VML nodes of the lines and the filled area from the renderer, to remove 257 * the object completely you should use {@link JXG.Board#removeObject}. 258 */ 259 remove: function () { 260 var i; 261 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 }, 267 268 /** 269 * Finds the index to a given point reference. 270 * @param {JXG.Point} p Reference to an element of type {@link JXG.Point} 271 */ 272 findPoint: function (p) { 273 var i; 274 275 if (!JXG.isPoint(p)) { 276 return -1; 277 } 278 279 for (i = 0; i < this.vertices.length; i++) { 280 if (this.vertices[i].id === p.id) { 281 return i; 282 } 283 } 284 285 return -1; 286 }, 287 288 /** 289 * Add more points to the polygon. The new points will be inserted at the end. 290 * @param {%} % Arbitrary number of points 291 * @returns {JXG.Polygon} Reference to the polygon 292 */ 293 addPoints: function () { 294 var args = Array.prototype.slice.call(arguments); 295 296 return this.insertPoints.apply(this, [this.vertices.length-2].concat(args)); 297 }, 298 299 /** 300 * Adds more points to the vertex list of the polygon, starting with index <tt><i</tt> 301 * @param {Number} i The position where the new vertices are inserted, starting with 0. 302 * @param {%} % Arbitrary number of points to insert. 303 * @returns {JXG.Polygon} Reference to the polygon object 304 */ 305 insertPoints: function () { 306 var idx, i, npoints = [], tmp; 307 308 if (arguments.length === 0) { 309 return this; 310 } 311 312 idx = arguments[0]; 313 314 if (idx < 0 || idx > this.vertices.length-2) { 315 return this; 316 } 317 318 for (i = 1; i < arguments.length; i++) { 319 if (JXG.isPoint(arguments[i])) { 320 npoints.push(arguments[i]); 321 } 322 } 323 324 tmp = this.vertices.slice(0, idx+1).concat(npoints); 325 this.vertices = tmp.concat(this.vertices.slice(idx+1)); 326 327 if (this.withLines) { 328 tmp = this.borders.slice(0, idx); 329 this.board.removeObject(this.borders[idx]); 330 331 for (i = 0; i < npoints.length; i++) { 332 tmp.push(JXG.createSegment(this.board, [this.vertices[idx+i], this.vertices[idx+i+1]], this.attr_line)); 333 } 334 tmp.push(JXG.createSegment(this.board, [this.vertices[idx+npoints.length], this.vertices[idx+npoints.length+1]], this.attr_line)); 335 336 this.borders = tmp.concat(this.borders.slice(idx)); 337 } 338 339 this.board.update(); 340 341 return this; 342 }, 343 344 /** 345 * Removes given set of vertices from the polygon 346 * @param {%} % Arbitrary number of vertices as {@link JXG.Point} elements or index numbers 347 * @returns {JXG.Polygon} Reference to the polygon 348 */ 349 removePoints: function () { 350 var i, j, idx, nvertices = [], nborders = [], 351 nidx = [], partition = []; 352 353 // partition: 354 // in order to keep the borders which could be recycled, we have to partition 355 // the set of removed points. I.e. if the points 1, 2, 5, 6, 7, 10 are removed, 356 // the partition is 357 // 1-2, 5-7, 10-10 358 // this gives us the borders, that can be removed and the borders we have to create. 359 360 361 // remove the last vertex which is identical to the first 362 this.vertices = this.vertices.slice(0, this.vertices.length-1); 363 364 // collect all valid parameters as indices in nidx 365 for (i = 0; i < arguments.length; i++) { 366 if (JXG.isPoint(arguments[i])) { 367 idx = this.findPoint(arguments[i]); 368 } 369 370 if (JXG.isNumber(idx) && idx > -1 && idx < this.vertices.length && JXG.indexOf(nidx, idx) === -1) { 371 nidx.push(idx); 372 } 373 } 374 375 // sort the elements to be eliminated 376 nidx = nidx.sort(); 377 nvertices = this.vertices.slice(); 378 nborders = this.borders.slice(); 379 380 // initialize the partition 381 if (this.withLines) { 382 partition.push([nidx[nidx.length-1]]); 383 } 384 385 // run through all existing vertices and copy all remaining ones to nvertices 386 // compute the partition 387 for (i = nidx.length-1; i > -1; i--) { 388 nvertices[nidx[i]] = -1; 389 390 if (this.withLines && (nidx[i] - 1 > nidx[i-1])) { 391 partition[partition.length-1][1] = nidx[i]; 392 partition.push([nidx[i-1]]); 393 } 394 } 395 396 // finalize the partition computation 397 if (this.withLines) { 398 partition[partition.length-1][1] = nidx[0]; 399 } 400 401 // update vertices 402 this.vertices = []; 403 for (i = 0; i < nvertices.length; i++) { 404 if (JXG.isPoint(nvertices[i])) { 405 this.vertices.push(nvertices[i]); 406 } 407 } 408 if (this.vertices[this.vertices.length-1].id !== this.vertices[0].id) { 409 this.vertices.push(this.vertices[0]); 410 } 411 412 // delete obsolete and create missing borders 413 if (this.withLines) { 414 for (i = 0; i < partition.length; i++) { 415 for (j = partition[i][1] - 1; j < partition[i][0] + 1; j++) { 416 // special cases 417 if (j < 0) { 418 // first vertex is removed, so the last border has to be removed, too 419 j = 0; 420 this.board.removeObject(this.borders[nborders.length-1]); 421 nborders[nborders.length-1] = -1; 422 } else if (j > nborders.length-1) { 423 j = nborders.length-1; 424 } 425 426 this.board.removeObject(this.borders[j]); 427 nborders[j] = -1; 428 } 429 430 // only create the new segment if it's not the closing border. the closing border is getting a special treatment at the end 431 // the if clause is newer than the min/max calls inside createSegment; i'm sure this makes the min/max calls obsolete, but 432 // just to be sure... 433 if (partition[i][1] !== 0 && partition[i][0] !== nvertices.length-1) { 434 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); 435 } 436 } 437 438 this.borders = []; 439 for (i = 0; i < nborders.length; i++) { 440 if (nborders[i] !== -1) { 441 this.borders.push(nborders[i]); 442 } 443 } 444 445 // if the first and/or the last vertex is removed, the closing border is created at the end. 446 if (partition[0][1] === 5 || partition[partition.length-1][1] === 0) { 447 this.borders.push(JXG.createSegment(this.board, [this.vertices[0], this.vertices[this.vertices.length-2]], this.attr_line)); 448 } 449 } 450 451 this.board.update(); 452 453 return this; 454 }, 455 456 getParents: function () { 457 var p = [], i; 458 459 for (i = 0; i < this.vertices.length; i++) { 460 p.push(this.vertices[i].id); 461 } 462 return p; 463 }, 464 465 getAttributes: function () { 466 var attr = JXG.GeometryElement.prototype.getAttributes.call(this), i; 467 468 if (this.withLines) { 469 attr.lines = attr.lines || {}; 470 attr.lines.ids = []; 471 attr.lines.colors = []; 472 473 for (i = 0; i < this.borders.length; i++) { 474 attr.lines.ids.push(this.borders[i].id); 475 attr.lines.colors.push(this.borders[i].visProp.strokecolor); 476 } 477 } 478 479 return attr; 480 } 481 }); 482 483 484 /** 485 * @class A polygon is an area enclosed by a set of border lines which are determined by a list of points. Each two 486 * consecutive points of the list define a line. 487 * @pseudo 488 * @constructor 489 * @name Polygon 490 * @type Polygon 491 * @augments JXG.Polygon 492 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 493 * @param {Array} vertices The polygon's vertices. If the first and the last vertex don't match the first one will be 494 * added to the array by the creator. 495 * @example 496 * var p1 = board.create('point', [0.0, 2.0]); 497 * var p2 = board.create('point', [2.0, 1.0]); 498 * var p3 = board.create('point', [4.0, 6.0]); 499 * var p4 = board.create('point', [1.0, 3.0]); 500 * 501 * var pol = board.create('polygon', [p1, p2, p3, p4]); 502 * </pre><div id="682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div> 503 * <script type="text/javascript"> 504 * (function () { 505 * var board = JXG.JSXGraph.initBoard('682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 506 * p1 = board.create('point', [0.0, 2.0]), 507 * p2 = board.create('point', [2.0, 1.0]), 508 * p3 = board.create('point', [4.0, 6.0]), 509 * p4 = board.create('point', [1.0, 3.0]), 510 * cc1 = board.create('polygon', [p1, p2, p3, p4]); 511 * })(); 512 * </script><pre> 513 */ 514 JXG.createPolygon = function(board, parents, attributes) { 515 var el, i, attr = JXG.copyAttributes(attributes, board.options, 'polygon'); 516 517 // Sind alles Punkte? 518 for(i = 0; i < parents.length; i++) { 519 parents[i] = JXG.getReference(board, parents[i]); 520 if(!JXG.isPoint(parents[i])) 521 throw new Error("JSXGraph: Can't create polygon with parent types other than 'point'."); 522 } 523 524 el = new JXG.Polygon(board, parents, attr); 525 526 return el; 527 }; 528 529 530 /** 531 * @class Constructs a regular polygon. It needs two points which define the base line and the number of vertices. 532 * @pseudo 533 * @description Constructs a regular polygon. It needs two points which define the base line and the number of vertices, or a set of points. 534 * @constructor 535 * @name RegularPolygon 536 * @type Polygon 537 * @augments Polygon 538 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 539 * @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. 540 * @example 541 * var p1 = board.create('point', [0.0, 2.0]); 542 * var p2 = board.create('point', [2.0, 1.0]); 543 * 544 * var pol = board.create('regularpolygon', [p1, p2, 5]); 545 * </pre><div id="682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div> 546 * <script type="text/javascript"> 547 * (function () { 548 * var board = JXG.JSXGraph.initBoard('682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 549 * p1 = board.create('point', [0.0, 2.0]), 550 * p2 = board.create('point', [2.0, 1.0]), 551 * cc1 = board.create('regularpolygon', [p1, p2, 5]); 552 * })(); 553 * </script><pre> 554 * @example 555 * var p1 = board.create('point', [0.0, 2.0]); 556 * var p2 = board.create('point', [4.0,4.0]); 557 * var p3 = board.create('point', [2.0,0.0]); 558 * 559 * var pol = board.create('regularpolygon', [p1, p2, p3]); 560 * </pre><div id="096a78b3-bd50-4bac-b958-3be5e7df17ed" style="width: 400px; height: 400px;"></div> 561 * <script type="text/javascript"> 562 * (function () { 563 * var board = JXG.JSXGraph.initBoard('096a78b3-bd50-4bac-b958-3be5e7df17ed', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 564 * p1 = board.create('point', [0.0, 2.0]), 565 * p2 = board.create('point', [4.0, 4.0]), 566 * p3 = board.create('point', [2.0,0.0]), 567 * cc1 = board.create('regularpolygon', [p1, p2, p3]); 568 * })(); 569 * </script><pre> 570 */ 571 JXG.createRegularPolygon = function(board, parents, attributes) { 572 var el, i, n, p = [], rot, c, len, pointsExist, attr; 573 574 if (JXG.isNumber(parents[parents.length-1]) && parents.length!=3) { 575 throw new Error("JSXGraph: A regular polygon needs two points and a number as input."); 576 } 577 578 len = parents.length; 579 n = parents[len-1]; 580 if ((!JXG.isNumber(n) && !JXG.isPoint(JXG.getReference(board, n))) || n<3) { 581 throw new Error("JSXGraph: The third parameter has to be number greater than 2 or a point."); 582 } 583 584 if (JXG.isPoint(JXG.getReference(board, n))) { // Regular polygon given by n points 585 n = len; 586 pointsExist = true; 587 } else { 588 len--; 589 pointsExist = false; 590 } 591 592 // The first two parent elements have to be points 593 for(i=0; i<len; i++) { 594 parents[i] = JXG.getReference(board, parents[i]); 595 if(!JXG.isPoint(parents[i])) 596 throw new Error("JSXGraph: Can't create regular polygon if the first two parameters aren't points."); 597 } 598 599 p[0] = parents[0]; 600 p[1] = parents[1]; 601 attr = JXG.copyAttributes(attributes, board.options, 'polygon', 'vertices'); 602 for (i=2;i<n;i++) { 603 rot = board.create('transform', [Math.PI*(2.0-(n-2)/n),p[i-1]], {type:'rotate'}); 604 if (pointsExist) { 605 p[i] = parents[i]; 606 p[i].addTransform(parents[i-2],rot); 607 } else { 608 if (JXG.isArray(attr.ids) && attr.ids.length >= n-2) { 609 attr.id = attr.ids[i-2]; 610 } 611 p[i] = board.create('point',[p[i-2],rot], attr); 612 p[i].type = JXG.OBJECT_TYPE_CAS; 613 } 614 } 615 attr = JXG.copyAttributes(attributes, board.options, 'polygon'); 616 el = board.create('polygon', p, attr); 617 618 el.elType = 'regularpolygon'; 619 620 return el; 621 }; 622 623 JXG.JSXGraph.registerElement('polygon', JXG.createPolygon); 624 JXG.JSXGraph.registerElement('regularpolygon', JXG.createRegularPolygon); 625