1 /* 2 Copyright 2008-2022 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 dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG:true, define: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 base/constants 39 base/coords 40 math/statistics 41 utils/type 42 base/element 43 elements: 44 segment 45 transform 46 */ 47 48 define([ 49 'jxg', 'base/constants', 'base/coords', 'math/statistics', 'math/geometry', 'utils/type', 'base/element' 50 ], function (JXG, Const, Coords, Statistics, Geometry, Type, GeometryElement) { 51 52 "use strict"; 53 54 /** 55 * Creates a new instance of JXG.Polygon. 56 * @class Polygon stores all style and functional properties that are required 57 * to draw and to interactact with a polygon. 58 * @param {JXG.Board} board Reference to the board the polygon is to be drawn on. 59 * @param {Array} vertices Unique identifiers for the points defining the polygon. 60 * Last point must be first point. Otherwise, the first point will be added at the list. 61 * @param {Object} attributes An object which contains properties as given in {@link JXG.Options.elements} 62 * and {@link JXG.Options.polygon}. 63 * @constructor 64 * @extends JXG.GeometryElement 65 */ 66 67 JXG.Polygon = function (board, vertices, attributes) { 68 this.constructor(board, attributes, Const.OBJECT_TYPE_POLYGON, Const.OBJECT_CLASS_AREA); 69 70 var i, l, len, j, p, 71 attr_line = Type.copyAttributes(attributes, board.options, 'polygon', 'borders'); 72 73 this.withLines = attributes.withlines; 74 this.attr_line = attr_line; 75 76 /** 77 * References to the points defining the polygon. The last vertex is the same as the first vertex. 78 * @type Array 79 */ 80 this.vertices = []; 81 for (i = 0; i < vertices.length; i++) { 82 this.vertices[i] = this.board.select(vertices[i]); 83 } 84 85 // Close the polygon 86 if (this.vertices.length > 0 && this.vertices[this.vertices.length - 1].id !== this.vertices[0].id) { 87 this.vertices.push(this.vertices[0]); 88 } 89 90 /** 91 * References to the border lines of the polygon. 92 * @type Array 93 */ 94 this.borders = []; 95 96 if (this.withLines) { 97 len = this.vertices.length - 1; 98 for (j = 0; j < len; j++) { 99 // This sets the "correct" labels for the first triangle of a construction. 100 i = (j + 1) % len; 101 attr_line.id = attr_line.ids && attr_line.ids[i]; 102 attr_line.name = attr_line.names && attr_line.names[i]; 103 attr_line.strokecolor = (Type.isArray(attr_line.colors) && attr_line.colors[i % attr_line.colors.length]) || 104 attr_line.strokecolor; 105 attr_line.visible = Type.exists(attributes.borders.visible) ? attributes.borders.visible : attributes.visible; 106 107 if (attr_line.strokecolor === false) { 108 attr_line.strokecolor = 'none'; 109 } 110 111 l = board.create('segment', [this.vertices[i], this.vertices[i + 1]], attr_line); 112 l.dump = false; 113 this.borders[i] = l; 114 l.parentPolygon = this; 115 } 116 } 117 118 this.inherits.push(this.vertices, this.borders); 119 120 // Register polygon at board 121 // This needs to be done BEFORE the points get this polygon added in their descendants list 122 this.id = this.board.setId(this, 'Py'); 123 124 // Add dependencies: either 125 // - add polygon as child to an existing point 126 // or 127 // - add points (supplied as coordinate arrays by the user and created by Type.providePoints) as children to the polygon 128 for (i = 0; i < this.vertices.length - 1; i++) { 129 p = this.board.select(this.vertices[i]); 130 if (Type.exists(p._is_new)) { 131 this.addChild(p); 132 delete p._is_new; 133 } else { 134 p.addChild(this); 135 } 136 } 137 138 this.board.renderer.drawPolygon(this); 139 this.board.finalizeAdding(this); 140 141 this.createGradient(); 142 this.elType = 'polygon'; 143 144 // create label 145 this.createLabel(); 146 147 this.methodMap = JXG.deepCopy(this.methodMap, { 148 borders: 'borders', 149 vertices: 'vertices', 150 A: 'Area', 151 Area: 'Area', 152 Perimeter: 'Perimeter', 153 L: 'Perimeter', 154 Length: 'Perimeter', 155 boundingBox: 'boundingBox', 156 bounds: 'bounds', 157 addPoints: 'addPoints', 158 insertPoints: 'insertPoints', 159 removePoints: 'removePoints' 160 }); 161 }; 162 163 JXG.Polygon.prototype = new GeometryElement(); 164 165 JXG.extend(JXG.Polygon.prototype, /** @lends JXG.Polygon.prototype */ { 166 167 /** 168 * Decides if a point (x,y) is inside of the polygon. 169 * Implements W. Randolf Franklin's pnpoly method. 170 * 171 * See <a href="https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html">https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html</a>. 172 * 173 * @param {Number} x_in x-coordinate (screen or user coordinates) 174 * @param {Number} y_in y-coordinate (screen or user coordinates) 175 * @param {Number} coord_type (Optional) the type of coordinates used here. 176 * Possible values are <b>JXG.COORDS_BY_USER</b> and <b>JXG.COORDS_BY_SCREEN</b>. 177 * Default value is JXG.COORDS_BY_SCREEN 178 * 179 * @returns {Boolean} if (x,y) is inside of the polygon. 180 * @example 181 * var pol = board.create('polygon', [[-1,2], [2,2], [-1,4]]); 182 * var p = board.create('point', [4, 3]); 183 * var txt = board.create('text', [-1, 0.5, function() { 184 * return 'Point A is inside of the polygon = ' + 185 * pol.pnpoly(p.X(), p.Y(), JXG.COORDS_BY_USER); 186 * }]); 187 * 188 * </pre><div id="JXG7f96aec7-4e3d-4ffc-a3f5-d3f967b6691c" class="jxgbox" style="width: 300px; height: 300px;"></div> 189 * <script type="text/javascript"> 190 * (function() { 191 * var board = JXG.JSXGraph.initBoard('JXG7f96aec7-4e3d-4ffc-a3f5-d3f967b6691c', 192 * {boundingbox: [-2, 5, 5,-2], axis: true, showcopyright: false, shownavigation: false}); 193 * var pol = board.create('polygon', [[-1,2], [2,2], [-1,4]]); 194 * var p = board.create('point', [4, 3]); 195 * var txt = board.create('text', [-1, 0.5, function() { 196 * return 'Point A is inside of the polygon = ' + pol.pnpoly(p.X(), p.Y(), JXG.COORDS_BY_USER); 197 * }]); 198 * 199 * })(); 200 * 201 * </script><pre> 202 * 203 */ 204 pnpoly: function(x_in, y_in, coord_type) { 205 var i, j, len, 206 x, y, crds, 207 v = this.vertices, 208 isIn = false; 209 210 if (coord_type === Const.COORDS_BY_USER) { 211 crds = new Coords(Const.COORDS_BY_USER, [x_in, y_in], this.board); 212 x = crds.scrCoords[1]; 213 y = crds.scrCoords[2]; 214 } else { 215 x = x_in; 216 y = y_in; 217 } 218 219 len = this.vertices.length; 220 for (i = 0, j = len - 2; i < len - 1; j = i++) { 221 if (((v[i].coords.scrCoords[2] > y) !== (v[j].coords.scrCoords[2] > y)) && 222 (x < (v[j].coords.scrCoords[1] - v[i].coords.scrCoords[1]) * 223 (y - v[i].coords.scrCoords[2]) / (v[j].coords.scrCoords[2] - v[i].coords.scrCoords[2]) + v[i].coords.scrCoords[1]) 224 ) { 225 isIn = !isIn; 226 } 227 } 228 229 return isIn; 230 }, 231 232 /** 233 * Checks whether (x,y) is near the polygon. 234 * @param {Number} x Coordinate in x direction, screen coordinates. 235 * @param {Number} y Coordinate in y direction, screen coordinates. 236 * @returns {Boolean} Returns true, if (x,y) is inside or at the boundary the polygon, otherwise false. 237 */ 238 hasPoint: function (x, y) { 239 var i, len; 240 241 if (Type.evaluate(this.visProp.hasinnerpoints)) { 242 // All points of the polygon trigger hasPoint: inner and boundary points 243 if (this.pnpoly(x, y)) { 244 return true; 245 } 246 } 247 248 // Only boundary points trigger hasPoint 249 // We additionally test the boundary also in case hasInnerPoints. 250 // Since even if the above test has failed, the strokewidth may be large and (x, y) may 251 // be inside of hasPoint() of a vertices. 252 len = this.borders.length; 253 for (i = 0; i < len; i++) { 254 if (this.borders[i].hasPoint(x, y)) { 255 return true; 256 } 257 } 258 259 return false; 260 }, 261 262 /** 263 * Uses the boards renderer to update the polygon. 264 */ 265 updateRenderer: function () { 266 var i, len; // wasReal, 267 268 269 if (!this.needsUpdate) { 270 return this; 271 } 272 273 if (this.visPropCalc.visible) { 274 // wasReal = this.isReal; 275 276 len = this.vertices.length; 277 this.isReal = true; 278 for (i = 0; i < len; ++i) { 279 if (!this.vertices[i].isReal) { 280 this.isReal = false; 281 break; 282 } 283 } 284 285 if (//wasReal && 286 !this.isReal) { 287 this.updateVisibility(false); 288 } 289 } 290 291 if (this.visPropCalc.visible) { 292 this.board.renderer.updatePolygon(this); 293 } 294 295 /* Update the label if visible. */ 296 if (this.hasLabel && this.visPropCalc.visible && this.label && 297 this.label.visPropCalc.visible && this.isReal) { 298 299 this.label.update(); 300 this.board.renderer.updateText(this.label); 301 } 302 303 // Update rendNode display 304 this.setDisplayRendNode(); 305 // if (this.visPropCalc.visible !== this.visPropOld.visible) { 306 // this.board.renderer.display(this, this.visPropCalc.visible); 307 // this.visPropOld.visible = this.visPropCalc.visible; 308 // 309 // if (this.hasLabel) { 310 // this.board.renderer.display(this.label, this.label.visPropCalc.visible); 311 // } 312 // } 313 314 this.needsUpdate = false; 315 return this; 316 }, 317 318 /** 319 * return TextAnchor 320 */ 321 getTextAnchor: function () { 322 var a, b, x, y, i; 323 324 if (this.vertices.length === 0) { 325 return new Coords(Const.COORDS_BY_USER, [1, 0, 0], this.board); 326 } 327 328 a = this.vertices[0].X(); 329 b = this.vertices[0].Y(); 330 x = a; 331 y = b; 332 for (i = 0; i < this.vertices.length; i++) { 333 if (this.vertices[i].X() < a) { 334 a = this.vertices[i].X(); 335 } 336 337 if (this.vertices[i].X() > x) { 338 x = this.vertices[i].X(); 339 } 340 341 if (this.vertices[i].Y() > b) { 342 b = this.vertices[i].Y(); 343 } 344 345 if (this.vertices[i].Y() < y) { 346 y = this.vertices[i].Y(); 347 } 348 } 349 350 return new Coords(Const.COORDS_BY_USER, [(a + x) * 0.5, (b + y) * 0.5], this.board); 351 }, 352 353 getLabelAnchor: JXG.shortcut(JXG.Polygon.prototype, 'getTextAnchor'), 354 355 // documented in geometry element 356 cloneToBackground: function () { 357 var copy = {}, er; 358 359 copy.id = this.id + 'T' + this.numTraces; 360 this.numTraces++; 361 copy.vertices = this.vertices; 362 copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true); 363 copy.visProp.layer = this.board.options.layer.trace; 364 copy.board = this.board; 365 Type.clearVisPropOld(copy); 366 367 copy.visPropCalc = { 368 visible: Type.evaluate(copy.visProp.visible) 369 }; 370 371 er = this.board.renderer.enhancedRendering; 372 this.board.renderer.enhancedRendering = true; 373 this.board.renderer.drawPolygon(copy); 374 this.board.renderer.enhancedRendering = er; 375 this.traces[copy.id] = copy.rendNode; 376 377 return this; 378 }, 379 380 /** 381 * Hide the polygon including its border lines. It will still exist but not visible on the board. 382 * @param {Boolean} [borderless=false] If set to true, the polygon is treated as a polygon without 383 * borders, i.e. the borders will not be hidden. 384 */ 385 hideElement: function (borderless) { 386 var i; 387 388 JXG.deprecated('Element.hideElement()', 'Element.setDisplayRendNode()'); 389 390 this.visPropCalc.visible = false; 391 this.board.renderer.display(this, false); 392 393 if (!borderless) { 394 for (i = 0; i < this.borders.length; i++) { 395 this.borders[i].hideElement(); 396 } 397 } 398 399 if (this.hasLabel && Type.exists(this.label)) { 400 this.label.hiddenByParent = true; 401 if (this.label.visPropCalc.visible) { 402 this.label.hideElement(); 403 } 404 } 405 }, 406 407 /** 408 * Make the element visible. 409 * @param {Boolean} [borderless=false] If set to true, the polygon is treated as a polygon without 410 * borders, i.e. the borders will not be shown. 411 */ 412 showElement: function (borderless) { 413 var i; 414 415 JXG.deprecated('Element.showElement()', 'Element.setDisplayRendNode()'); 416 417 this.visPropCalc.visible = true; 418 this.board.renderer.display(this, true); 419 420 if (!borderless) { 421 for (i = 0; i < this.borders.length; i++) { 422 this.borders[i].showElement().updateRenderer(); 423 } 424 } 425 426 if (Type.exists(this.label) && this.hasLabel && this.label.hiddenByParent) { 427 this.label.hiddenByParent = false; 428 if (!this.label.visPropCalc.visible) { 429 this.label.showElement().updateRenderer(); 430 } 431 } 432 return this; 433 }, 434 435 /** 436 * Area of (not self-intersecting) polygon 437 * @returns {Number} Area of (not self-intersecting) polygon 438 */ 439 Area: function () { 440 return Math.abs(Geometry.signedPolygon(this.vertices, true)); 441 }, 442 443 /** 444 * Perimeter of polygon. 445 * @returns {Number} Perimeter of polygon in user units. 446 * 447 * @example 448 * var p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 3.0]]; 449 * 450 * var pol = board.create('polygon', p, {hasInnerPoints: true}); 451 * var t = board.create('text', [5, 5, function() { return pol.Perimeter(); }]); 452 * </pre><div class="jxgbox" id="JXGb10b734d-89fc-4b9d-b4a7-e3f0c1c6bf77" style="width: 400px; height: 400px;"></div> 453 * <script type="text/javascript"> 454 * (function () { 455 * var board = JXG.JSXGraph.initBoard('JXGb10b734d-89fc-4b9d-b4a7-e3f0c1c6bf77', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 456 * p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 4.0]], 457 * cc1 = board.create('polygon', p, {hasInnerPoints: true}), 458 * t = board.create('text', [5, 5, function() { return cc1.Perimeter(); }]); 459 * })(); 460 * </script><pre> 461 * 462 */ 463 Perimeter: function() { 464 var i, 465 len = this.vertices.length, 466 val = 0.0; 467 468 for (i = 1; i < len; ++i) { 469 val += this.vertices[i].Dist(this.vertices[i - 1]); 470 } 471 472 return val; 473 }, 474 475 /** 476 * Bounding box of a polygon. The bounding box is an array of four numbers: the first two numbers 477 * determine the upper left corner, the last two number determine the lower right corner of the bounding box. 478 * 479 * The width and height of a polygon can then determined like this: 480 * @example 481 * var box = polygon.boundingBox(); 482 * var width = box[2] - box[0]; 483 * var height = box[1] - box[3]; 484 * 485 * @returns {Array} Array containing four numbers: [minX, maxY, maxX, minY] 486 */ 487 boundingBox: function () { 488 var box = [0, 0, 0, 0], i, v, 489 le = this.vertices.length - 1; 490 491 if (le === 0) { 492 return box; 493 } 494 box[0] = this.vertices[0].X(); 495 box[2] = box[0]; 496 box[1] = this.vertices[0].Y(); 497 box[3] = box[1]; 498 499 for (i = 1; i < le; ++i) { 500 v = this.vertices[i].X(); 501 if (v < box[0]) { 502 box[0] = v; 503 } else if (v > box[2]) { 504 box[2] = v; 505 } 506 507 v = this.vertices[i].Y(); 508 if (v > box[1]) { 509 box[1] = v; 510 } else if (v < box[3]) { 511 box[3] = v; 512 } 513 } 514 515 return box; 516 }, 517 518 // Already documented in GeometryElement 519 bounds: function () { 520 return this.boundingBox(); 521 }, 522 523 /** 524 * This method removes the SVG or VML nodes of the lines and the filled area from the renderer, to remove 525 * the object completely you should use {@link JXG.Board#removeObject}. 526 * 527 * @private 528 */ 529 remove: function () { 530 var i; 531 532 for (i = 0; i < this.borders.length; i++) { 533 this.board.removeObject(this.borders[i]); 534 } 535 536 GeometryElement.prototype.remove.call(this); 537 }, 538 539 /** 540 * Finds the index to a given point reference. 541 * @param {JXG.Point} p Reference to an element of type {@link JXG.Point} 542 * @returns {Number} Index of the point or -1. 543 */ 544 findPoint: function (p) { 545 var i; 546 547 if (!Type.isPoint(p)) { 548 return -1; 549 } 550 551 for (i = 0; i < this.vertices.length; i++) { 552 if (this.vertices[i].id === p.id) { 553 return i; 554 } 555 } 556 557 return -1; 558 }, 559 560 /** 561 * Add more points to the polygon. The new points will be inserted at the end. 562 * The attributes of new border segments are set to the same values 563 * as those used when the polygon was created. 564 * If new vertices are supplied by coordinates, the default attributes of polygon 565 * vertices are taken as their attributes. Therefore, the visual attributes of 566 * new vertices and borders may have to be adapted afterwards. 567 * @param {JXG.Point} p Arbitrary number of points or coordinate arrays 568 * @returns {JXG.Polygon} Reference to the polygon 569 * @example 570 * const board = JXG.JSXGraph.initBoard('jxgbox', {axis:true}); 571 * var pg = board.create('polygon', [[1,2], [3,4], [-3,1]], {hasInnerPoints: true}); 572 * var newPoint = board.create('point', [-1, -1]); 573 * var newPoint2 = board.create('point', [-1, -2]); 574 * pg.addPoints(newPoint, newPoint2, [1, -2]); 575 * 576 * </pre><div id="JXG70eb0fd2-d20f-4ba9-9ab6-0eac92aabfa5" class="jxgbox" style="width: 300px; height: 300px;"></div> 577 * <script type="text/javascript"> 578 * (function() { 579 * var board = JXG.JSXGraph.initBoard('JXG70eb0fd2-d20f-4ba9-9ab6-0eac92aabfa5', 580 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 581 * const board = JXG.JSXGraph.initBoard('jxgbox', {axis:true}); 582 * var pg = board.create('polygon', [[1,2], [3,4], [-3,1]], {hasInnerPoints: true}); 583 * var newPoint = board.create('point', [-1, -1]); 584 * var newPoint2 = board.create('point', [-1, -2]); 585 * pg.addPoints(newPoint, newPoint2, [1, -2]); 586 * 587 * })(); 588 * 589 * </script><pre> 590 * 591 */ 592 addPoints: function (p) { 593 var args = Array.prototype.slice.call(arguments); 594 595 return this.insertPoints.apply(this, [this.vertices.length - 2].concat(args)); 596 }, 597 598 /** 599 * Insert points to the vertex list of the polygon after index <tt><idx</tt>. 600 * The attributes of new border segments are set to the same values 601 * as those used when the polygon was created. 602 * If new vertices are supplied by coordinates, the default attributes of polygon 603 * vertices are taken as their attributes. Therefore, the visual attributes of 604 * new vertices and borders may have to be adapted afterwards. 605 * 606 * @param {Number} idx The position after which the new vertices are inserted. 607 * Setting idx to -1 inserts the new points at the front, i.e. at position 0. 608 * @param {JXG.Point} p Arbitrary number of points or coordinate arrays to insert. 609 * @returns {JXG.Polygon} Reference to the polygon object 610 * 611 * @example 612 * const board = JXG.JSXGraph.initBoard('jxgbox', {axis:true}); 613 * var pg = board.create('polygon', [[1,2], [3,4], [-3,1]], {hasInnerPoints: true}); 614 * var newPoint = board.create('point', [-1, -1]); 615 * pg.insertPoints(0, newPoint, newPoint, [1, -2]); 616 * 617 * </pre><div id="JXG17b84b2a-a851-4e3f-824f-7f6a60f166ca" class="jxgbox" style="width: 300px; height: 300px;"></div> 618 * <script type="text/javascript"> 619 * (function() { 620 * var board = JXG.JSXGraph.initBoard('JXG17b84b2a-a851-4e3f-824f-7f6a60f166ca', 621 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 622 * const board = JXG.JSXGraph.initBoard('jxgbox', {axis:true}); 623 * var pg = board.create('polygon', [[1,2], [3,4], [-3,1]], {hasInnerPoints: true}); 624 * var newPoint = board.create('point', [-1, -1]); 625 * pg.insertPoints(0, newPoint, newPoint, [1, -2]); 626 * 627 * })(); 628 * 629 * </script><pre> 630 * 631 */ 632 insertPoints: function (idx, p) { 633 var i, le; 634 635 if (arguments.length === 0) { 636 return this; 637 } 638 639 640 if (idx < -1 || idx > this.vertices.length - 2) { 641 return this; 642 } 643 644 le = arguments.length - 1; 645 for (i = 1; i < le + 1; i++) { 646 this.vertices.splice(idx + i, 0, 647 Type.providePoints(this.board, [arguments[i]], {}, 'polygon', ['vertices'])[0] 648 ); 649 } 650 if (idx === -1) { 651 this.vertices[this.vertices.length - 1] = this.vertices[0]; 652 } 653 if (this.withLines) { 654 if (idx < 0) { 655 this.borders[this.borders.length - 1].point2 = this.vertices[this.vertices.length - 1]; 656 } else { 657 this.borders[idx].point2 = this.vertices[idx + 1]; 658 } 659 for (i = idx + 1; i < idx + 1 + le; i++) { 660 this.borders.splice(i, 0, 661 this.board.create('segment', [this.vertices[i], this.vertices[i + 1]], this.attr_line) 662 ); 663 } 664 } 665 this.board.update(); 666 667 return this; 668 }, 669 670 /** 671 * Removes given set of vertices from the polygon 672 * @param {JXG.Point} p Arbitrary number of vertices as {@link JXG.Point} elements or index numbers 673 * @returns {JXG.Polygon} Reference to the polygon 674 */ 675 removePoints: function (p) { 676 var i, j, idx, nvertices = [], nborders = [], 677 nidx = [], partition = []; 678 679 // Partition: 680 // in order to keep the borders which could be recycled, we have to partition 681 // the set of removed points. I.e. if the points 1, 2, 5, 6, 7, 10 are removed, 682 // the partitions are 683 // 1-2, 5-7, 10-10 684 // this gives us the borders, that can be removed and the borders we have to create. 685 686 687 // Remove the last vertex which is identical to the first 688 this.vertices = this.vertices.slice(0, this.vertices.length - 1); 689 690 // Collect all valid parameters as indices in nidx 691 for (i = 0; i < arguments.length; i++) { 692 idx = arguments[i]; 693 if (Type.isPoint(idx)) { 694 idx = this.findPoint(idx); 695 } 696 697 if (Type.isNumber(idx) && idx > -1 && idx < this.vertices.length && Type.indexOf(nidx, idx) === -1) { 698 nidx.push(idx); 699 } 700 } 701 702 // Remove the polygon from each removed point's children 703 for (i = 0; i < nidx.length; i++) { 704 this.vertices[nidx[i]].removeChild(this); 705 } 706 707 // Sort the elements to be eliminated 708 nidx = nidx.sort(); 709 nvertices = this.vertices.slice(); 710 nborders = this.borders.slice(); 711 712 // Initialize the partition 713 if (this.withLines) { 714 partition.push([nidx[nidx.length - 1]]); 715 } 716 717 // Run through all existing vertices and copy all remaining ones to nvertices, 718 // compute the partition 719 for (i = nidx.length - 1; i > -1; i--) { 720 nvertices[nidx[i]] = -1; 721 722 if (this.withLines && (nidx[i] - 1 > nidx[i - 1])) { 723 partition[partition.length - 1][1] = nidx[i]; 724 partition.push([nidx[i - 1]]); 725 } 726 } 727 728 // Finalize the partition computation 729 if (this.withLines) { 730 partition[partition.length - 1][1] = nidx[0]; 731 } 732 733 // Update vertices 734 this.vertices = []; 735 for (i = 0; i < nvertices.length; i++) { 736 if (Type.isPoint(nvertices[i])) { 737 this.vertices.push(nvertices[i]); 738 } 739 } 740 if (this.vertices[this.vertices.length - 1].id !== this.vertices[0].id) { 741 this.vertices.push(this.vertices[0]); 742 } 743 744 // Delete obsolete and create missing borders 745 if (this.withLines) { 746 for (i = 0; i < partition.length; i++) { 747 for (j = partition[i][1] - 1; j < partition[i][0] + 1; j++) { 748 // special cases 749 if (j < 0) { 750 // first vertex is removed, so the last border has to be removed, too 751 j = 0; 752 this.board.removeObject(this.borders[nborders.length - 1]); 753 nborders[nborders.length - 1] = -1; 754 } else if (j > nborders.length - 1) { 755 j = nborders.length - 1; 756 } 757 758 this.board.removeObject(this.borders[j]); 759 nborders[j] = -1; 760 } 761 762 // only create the new segment if it's not the closing border. the closing border is getting a special treatment at the end 763 // the if clause is newer than the min/max calls inside createSegment; i'm sure this makes the min/max calls obsolete, but 764 // just to be sure... 765 if (partition[i][1] !== 0 && partition[i][0] !== nvertices.length - 1) { 766 nborders[partition[i][0] - 1] = this.board.create('segment', [nvertices[Math.max(partition[i][1] - 1, 0)], nvertices[Math.min(partition[i][0] + 1, this.vertices.length - 1)]], this.attr_line); 767 } 768 } 769 770 this.borders = []; 771 for (i = 0; i < nborders.length; i++) { 772 if (nborders[i] !== -1) { 773 this.borders.push(nborders[i]); 774 } 775 } 776 777 // if the first and/or the last vertex is removed, the closing border is created at the end. 778 if (partition[0][1] === this.vertices.length - 1 || partition[partition.length - 1][1] === 0) { 779 this.borders.push(this.board.create('segment', [this.vertices[0], this.vertices[this.vertices.length - 2]], this.attr_line)); 780 } 781 } 782 783 this.board.update(); 784 785 return this; 786 }, 787 788 // documented in element.js 789 getParents: function () { 790 this.setParents(this.vertices); 791 return this.parents; 792 }, 793 794 getAttributes: function () { 795 var attr = GeometryElement.prototype.getAttributes.call(this), i; 796 797 if (this.withLines) { 798 attr.lines = attr.lines || {}; 799 attr.lines.ids = []; 800 attr.lines.colors = []; 801 802 for (i = 0; i < this.borders.length; i++) { 803 attr.lines.ids.push(this.borders[i].id); 804 attr.lines.colors.push(this.borders[i].visProp.strokecolor); 805 } 806 } 807 808 return attr; 809 }, 810 811 snapToGrid: function () { 812 var i, force; 813 814 if (Type.evaluate(this.visProp.snaptogrid)) { 815 force = true; 816 } else { 817 force = false; 818 } 819 820 for (i = 0; i < this.vertices.length; i++) { 821 this.vertices[i].handleSnapToGrid(force, true); 822 } 823 824 }, 825 826 /** 827 * Moves the polygon by the difference of two coordinates. 828 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 829 * @param {Array} coords coordinates in screen/user units 830 * @param {Array} oldcoords previous coordinates in screen/user units 831 * @returns {JXG.Polygon} this element 832 */ 833 setPositionDirectly: function (method, coords, oldcoords) { 834 var dc, t, i, len, 835 c = new Coords(method, coords, this.board), 836 oldc = new Coords(method, oldcoords, this.board); 837 838 len = this.vertices.length - 1; 839 for (i = 0; i < len; i++) { 840 if (!this.vertices[i].draggable()) { 841 return this; 842 } 843 } 844 845 dc = Statistics.subtract(c.usrCoords, oldc.usrCoords); 846 t = this.board.create('transform', dc.slice(1), {type: 'translate'}); 847 t.applyOnce(this.vertices.slice(0, -1)); 848 849 return this; 850 }, 851 852 /** 853 * Algorithm by Sutherland and Hodgman to compute the intersection of two convex polygons. 854 * The polygon itself is the clipping polygon, it expects as parameter a polygon to be clipped. 855 * See <a href="https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm">wikipedia entry</a>. 856 * Called by {@link JXG.Polygon#intersect}. 857 * 858 * @private 859 * 860 * @param {JXG.Polygon} polygon Polygon which will be clipped. 861 * 862 * @returns {Array} of (normalized homogeneous user) coordinates (i.e. [z, x, y], where z==1 in most cases, 863 * representing the vertices of the intersection polygon. 864 * 865 */ 866 sutherlandHodgman: function(polygon) { 867 // First the two polygons are sorted counter clockwise 868 var clip = JXG.Math.Geometry.sortVertices(this.vertices), // "this" is the clipping polygon 869 subject = JXG.Math.Geometry.sortVertices(polygon.vertices), // "polygon" is the subject polygon 870 871 lenClip = clip.length - 1, 872 lenSubject = subject.length - 1, 873 lenIn, 874 875 outputList = [], 876 inputList, i, j, S, E, cross, 877 878 // Determines if the point c3 is right of the line through c1 and c2. 879 // Since the polygons are sorted counter clockwise, "right of" and therefore >= is needed here 880 isInside = function(c1, c2, c3) { 881 return ((c2[1] - c1[1]) * (c3[2] - c1[2]) - (c2[2] - c1[2]) * (c3[1] - c1[1])) >= 0; 882 }; 883 884 for (i = 0; i < lenSubject; i++) { 885 outputList.push(subject[i]); 886 } 887 888 for (i = 0; i < lenClip; i++) { 889 inputList = outputList.slice(0); 890 lenIn = inputList.length; 891 outputList = []; 892 893 S = inputList[lenIn - 1]; 894 895 for (j = 0; j < lenIn; j++) { 896 E = inputList[j]; 897 if (isInside(clip[i], clip[i + 1], E)) { 898 if (!isInside(clip[i], clip[i + 1], S)) { 899 cross = JXG.Math.Geometry.meetSegmentSegment(S, E, clip[i], clip[i + 1]); 900 cross[0][1] /= cross[0][0]; 901 cross[0][2] /= cross[0][0]; 902 cross[0][0] = 1; 903 outputList.push(cross[0]); 904 } 905 outputList.push(E); 906 } else if (isInside(clip[i], clip[i + 1], S)) { 907 cross = JXG.Math.Geometry.meetSegmentSegment(S, E, clip[i], clip[i + 1]); 908 cross[0][1] /= cross[0][0]; 909 cross[0][2] /= cross[0][0]; 910 cross[0][0] = 1; 911 outputList.push(cross[0]); 912 } 913 S = E; 914 } 915 } 916 917 return outputList; 918 }, 919 920 /** 921 * Generic method for the intersection of this polygon with another polygon. 922 * The parent object is the clipping polygon, it expects as parameter a polygon to be clipped. 923 * Both polygons have to be convex. 924 * Calls the algorithm by Sutherland, Hodgman, {@link JXG.Polygon#sutherlandHodgman}. 925 * <p> 926 * An alternative is to use the methods from {@link JXG.Math.Clip}, where the algorithm by Greiner and Hormann 927 * is used. 928 * 929 * @param {JXG.Polygon} polygon Polygon which will be clipped. 930 * 931 * @returns {Array} of (normalized homogeneous user) coordinates (i.e. [z, x, y], where z==1 in most cases, 932 * representing the vertices of the intersection polygon. 933 * 934 * @example 935 * // Static intersection of two polygons pol1 and pol2 936 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 937 * name:'pol1', withLabel: true, 938 * fillColor: 'yellow' 939 * }); 940 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 941 * name:'pol2', withLabel: true 942 * }); 943 * 944 * // Static version: 945 * // the intersection polygon does not adapt to changes of pol1 or pol2. 946 * var pol3 = board.create('polygon', pol1.intersect(pol2), {fillColor: 'blue'}); 947 * </pre><div class="jxgbox" id="JXGd1fe5ea9-309f-494a-af07-ee3d033acb7c" style="width: 300px; height: 300px;"></div> 948 * <script type="text/javascript"> 949 * (function() { 950 * var board = JXG.JSXGraph.initBoard('JXGd1fe5ea9-309f-494a-af07-ee3d033acb7c', {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 951 * // Intersect two polygons pol1 and pol2 952 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 953 * name:'pol1', withLabel: true, 954 * fillColor: 'yellow' 955 * }); 956 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 957 * name:'pol2', withLabel: true 958 * }); 959 * 960 * // Static version: the intersection polygon does not adapt to changes of pol1 or pol2. 961 * var pol3 = board.create('polygon', pol1.intersect(pol2), {fillColor: 'blue'}); 962 * })(); 963 * </script><pre> 964 * 965 * @example 966 * // Dynamic intersection of two polygons pol1 and pol2 967 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 968 * name:'pol1', withLabel: true, 969 * fillColor: 'yellow' 970 * }); 971 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 972 * name:'pol2', withLabel: true 973 * }); 974 * 975 * // Dynamic version: 976 * // the intersection polygon does adapt to changes of pol1 or pol2. 977 * // For this a curve element is used. 978 * var curve = board.create('curve', [[],[]], {fillColor: 'blue', fillOpacity: 0.4}); 979 * curve.updateDataArray = function() { 980 * var mat = JXG.Math.transpose(pol1.intersect(pol2)); 981 * 982 * if (mat.length == 3) { 983 * this.dataX = mat[1]; 984 * this.dataY = mat[2]; 985 * } else { 986 * this.dataX = []; 987 * this.dataY = []; 988 * } 989 * }; 990 * board.update(); 991 * </pre><div class="jxgbox" id="JXGf870d516-ca1a-4140-8fe3-5d64fb42e5f2" style="width: 300px; height: 300px;"></div> 992 * <script type="text/javascript"> 993 * (function() { 994 * var board = JXG.JSXGraph.initBoard('JXGf870d516-ca1a-4140-8fe3-5d64fb42e5f2', {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 995 * // Intersect two polygons pol1 and pol2 996 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 997 * name:'pol1', withLabel: true, 998 * fillColor: 'yellow' 999 * }); 1000 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 1001 * name:'pol2', withLabel: true 1002 * }); 1003 * 1004 * // Dynamic version: 1005 * // the intersection polygon does adapt to changes of pol1 or pol2. 1006 * // For this a curve element is used. 1007 * var curve = board.create('curve', [[],[]], {fillColor: 'blue', fillOpacity: 0.4}); 1008 * curve.updateDataArray = function() { 1009 * var mat = JXG.Math.transpose(pol1.intersect(pol2)); 1010 * 1011 * if (mat.length == 3) { 1012 * this.dataX = mat[1]; 1013 * this.dataY = mat[2]; 1014 * } else { 1015 * this.dataX = []; 1016 * this.dataY = []; 1017 * } 1018 * }; 1019 * board.update(); 1020 * })(); 1021 * </script><pre> 1022 * 1023 */ 1024 intersect: function(polygon) { 1025 return this.sutherlandHodgman(polygon); 1026 } 1027 1028 1029 }); 1030 1031 1032 /** 1033 * @class A polygon is an area enclosed by a set of border lines which are determined by 1034 * <ul> 1035 * <li> a list of points or 1036 * <li> a list of coordinate arrays or 1037 * <li> a function returning a list of coordinate arrays. 1038 * </ul> 1039 * Each two consecutive points of the list define a line. 1040 * @pseudo 1041 * @constructor 1042 * @name Polygon 1043 * @type Polygon 1044 * @augments JXG.Polygon 1045 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1046 * @param {Array} vertices The polygon's vertices. If the first and the last vertex don't match the first one will be 1047 * added to the array by the creator. Here, two points match if they have the same 'id' attribute. 1048 * 1049 * Additionally, a polygon can be created by providing a polygon and a transformation (or an array of transformations). 1050 * The result is a polygon which is the transformation of the supplied polygon. 1051 * 1052 * @example 1053 * var p1 = board.create('point', [0.0, 2.0]); 1054 * var p2 = board.create('point', [2.0, 1.0]); 1055 * var p3 = board.create('point', [4.0, 6.0]); 1056 * var p4 = board.create('point', [1.0, 4.0]); 1057 * 1058 * var pol = board.create('polygon', [p1, p2, p3, p4]); 1059 * </pre><div class="jxgbox" id="JXG682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div> 1060 * <script type="text/javascript"> 1061 * (function () { 1062 * var board = JXG.JSXGraph.initBoard('JXG682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 1063 * p1 = board.create('point', [0.0, 2.0]), 1064 * p2 = board.create('point', [2.0, 1.0]), 1065 * p3 = board.create('point', [4.0, 6.0]), 1066 * p4 = board.create('point', [1.0, 4.0]), 1067 * cc1 = board.create('polygon', [p1, p2, p3, p4]); 1068 * })(); 1069 * </script><pre> 1070 * 1071 * @example 1072 * var p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 3.0]]; 1073 * 1074 * var pol = board.create('polygon', p, {hasInnerPoints: true}); 1075 * </pre><div class="jxgbox" id="JXG9f9a5946-112a-4768-99ca-f30792bcdefb" style="width: 400px; height: 400px;"></div> 1076 * <script type="text/javascript"> 1077 * (function () { 1078 * var board = JXG.JSXGraph.initBoard('JXG9f9a5946-112a-4768-99ca-f30792bcdefb', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 1079 * p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 4.0]], 1080 * cc1 = board.create('polygon', p, {hasInnerPoints: true}); 1081 * })(); 1082 * </script><pre> 1083 * 1084 * @example 1085 * var f1 = function() { return [0.0, 2.0]; }, 1086 * f2 = function() { return [2.0, 1.0]; }, 1087 * f3 = function() { return [4.0, 6.0]; }, 1088 * f4 = function() { return [1.0, 4.0]; }, 1089 * cc1 = board.create('polygon', [f1, f2, f3, f4]); 1090 * board.update(); 1091 * 1092 * </pre><div class="jxgbox" id="JXGceb09915-b783-44db-adff-7877ae3534c8" style="width: 400px; height: 400px;"></div> 1093 * <script type="text/javascript"> 1094 * (function () { 1095 * var board = JXG.JSXGraph.initBoard('JXGceb09915-b783-44db-adff-7877ae3534c8', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 1096 * f1 = function() { return [0.0, 2.0]; }, 1097 * f2 = function() { return [2.0, 1.0]; }, 1098 * f3 = function() { return [4.0, 6.0]; }, 1099 * f4 = function() { return [1.0, 4.0]; }, 1100 * cc1 = board.create('polygon', [f1, f2, f3, f4]); 1101 * board.update(); 1102 * })(); 1103 * </script><pre> 1104 * 1105 * @example 1106 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 1107 * var a = board.create('point', [-3,-2], {name: 'a'}); 1108 * var b = board.create('point', [-1,-4], {name: 'b'}); 1109 * var c = board.create('point', [-2,-0.5], {name: 'c'}); 1110 * var pol1 = board.create('polygon', [a,b,c], {vertices: {withLabel: false}}); 1111 * var pol2 = board.create('polygon', [pol1, t], {vertices: {withLabel: true}}); 1112 * 1113 * </pre><div id="JXG6530a69c-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1114 * <script type="text/javascript"> 1115 * (function() { 1116 * var board = JXG.JSXGraph.initBoard('JXG6530a69c-6339-11e8-9fb9-901b0e1b8723', 1117 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1118 * var t = board.create('transform', [2, 1.5], {type: 'scale'}); 1119 * var a = board.create('point', [-3,-2], {name: 'a'}); 1120 * var b = board.create('point', [-1,-4], {name: 'b'}); 1121 * var c = board.create('point', [-2,-0.5], {name: 'c'}); 1122 * var pol1 = board.create('polygon', [a,b,c], {vertices: {withLabel: false}}); 1123 * var pol2 = board.create('polygon', [pol1, t], {vertices: {withLabel: true}}); 1124 * 1125 * })(); 1126 * 1127 * </script><pre> 1128 * 1129 */ 1130 JXG.createPolygon = function (board, parents, attributes) { 1131 var el, i, le, obj, 1132 points = [], 1133 attr, attr_points, 1134 is_transform = false; 1135 1136 attr = Type.copyAttributes(attributes, board.options, 'polygon'); 1137 obj = board.select(parents[0]); 1138 if (obj === null) { 1139 // This is necessary if the original polygon is defined in another board. 1140 obj = parents[0]; 1141 } 1142 if (Type.isObject(obj) && obj.type === Const.OBJECT_TYPE_POLYGON && 1143 Type.isTransformationOrArray(parents[1])) { 1144 1145 is_transform = true; 1146 le = obj.vertices.length - 1; 1147 attr_points = Type.copyAttributes(attributes, board.options, 'polygon', 'vertices'); 1148 for (i = 0; i < le; i++) { 1149 if (attr_points.withlabel) { 1150 attr_points.name = (obj.vertices[i].name === '') ? '' : (obj.vertices[i].name + "'"); 1151 } 1152 points.push(board.create('point', [obj.vertices[i], parents[1]], attr_points)); 1153 } 1154 } else { 1155 points = Type.providePoints(board, parents, attributes, 'polygon', ['vertices']); 1156 if (points === false) { 1157 throw new Error("JSXGraph: Can't create polygon / polygonalchain with parent types other than 'point' and 'coordinate arrays' or a function returning an array of coordinates. Alternatively, a polygon and a transformation can be supplied"); 1158 } 1159 } 1160 1161 attr = Type.copyAttributes(attributes, board.options, 'polygon'); 1162 el = new JXG.Polygon(board, points, attr); 1163 el.isDraggable = true; 1164 1165 // Put the points to their position 1166 if (is_transform) { 1167 el.prepareUpdate().update().updateVisibility().updateRenderer(); 1168 le = obj.vertices.length - 1; 1169 for (i = 0; i < le; i++) { 1170 points[i].prepareUpdate().update().updateVisibility().updateRenderer(); 1171 } 1172 } 1173 1174 return el; 1175 }; 1176 1177 /** 1178 * @class Constructs a regular polygon. It needs two points which define the base line and the number of vertices. 1179 * @pseudo 1180 * @description Constructs a regular polygon. It needs two points which define the base line and the number of vertices, or a set of points. 1181 * @constructor 1182 * @name RegularPolygon 1183 * @type Polygon 1184 * @augments Polygon 1185 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1186 * @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. 1187 * @example 1188 * var p1 = board.create('point', [0.0, 2.0]); 1189 * var p2 = board.create('point', [2.0, 1.0]); 1190 * 1191 * var pol = board.create('regularpolygon', [p1, p2, 5]); 1192 * </pre><div class="jxgbox" id="JXG682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div> 1193 * <script type="text/javascript"> 1194 * (function () { 1195 * var board = JXG.JSXGraph.initBoard('JXG682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 1196 * p1 = board.create('point', [0.0, 2.0]), 1197 * p2 = board.create('point', [2.0, 1.0]), 1198 * cc1 = board.create('regularpolygon', [p1, p2, 5]); 1199 * })(); 1200 * </script><pre> 1201 * @example 1202 * var p1 = board.create('point', [0.0, 2.0]); 1203 * var p2 = board.create('point', [4.0,4.0]); 1204 * var p3 = board.create('point', [2.0,0.0]); 1205 * 1206 * var pol = board.create('regularpolygon', [p1, p2, p3]); 1207 * </pre><div class="jxgbox" id="JXG096a78b3-bd50-4bac-b958-3be5e7df17ed" style="width: 400px; height: 400px;"></div> 1208 * <script type="text/javascript"> 1209 * (function () { 1210 * var board = JXG.JSXGraph.initBoard('JXG096a78b3-bd50-4bac-b958-3be5e7df17ed', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 1211 * p1 = board.create('point', [0.0, 2.0]), 1212 * p2 = board.create('point', [4.0, 4.0]), 1213 * p3 = board.create('point', [2.0,0.0]), 1214 * cc1 = board.create('regularpolygon', [p1, p2, p3]); 1215 * })(); 1216 * </script><pre> 1217 * 1218 * @example 1219 * // Line of reflection 1220 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1221 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1222 * var pol1 = board.create('polygon', [[-3,-2], [-1,-4], [-2,-0.5]]); 1223 * var pol2 = board.create('polygon', [pol1, reflect]); 1224 * 1225 * </pre><div id="JXG58fc3078-d8d1-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div> 1226 * <script type="text/javascript"> 1227 * (function() { 1228 * var board = JXG.JSXGraph.initBoard('JXG58fc3078-d8d1-11e7-93b3-901b0e1b8723', 1229 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1230 * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'}); 1231 * var reflect = board.create('transform', [li], {type: 'reflect'}); 1232 * var pol1 = board.create('polygon', [[-3,-2], [-1,-4], [-2,-0.5]]); 1233 * var pol2 = board.create('polygon', [pol1, reflect]); 1234 * 1235 * })(); 1236 * 1237 * </script><pre> 1238 * 1239 */ 1240 JXG.createRegularPolygon = function (board, parents, attributes) { 1241 var el, i, n, 1242 p = [], rot, len, pointsExist, attr; 1243 1244 len = parents.length; 1245 n = parents[len - 1]; 1246 1247 if (Type.isNumber(n) && (parents.length !== 3 || n < 3)) { 1248 throw new Error("JSXGraph: A regular polygon needs two point types and a number > 2 as input."); 1249 } 1250 1251 if (Type.isNumber(board.select(n))) { // Regular polygon given by 2 points and a number 1252 len--; 1253 pointsExist = false; 1254 } else { // Regular polygon given by n points 1255 n = len; 1256 pointsExist = true; 1257 } 1258 1259 p = Type.providePoints(board, parents.slice(0, len), attributes, 'regularpolygon', ['vertices']); 1260 if (p === false) { 1261 throw new Error("JSXGraph: Can't create regular polygon with parent types other than 'point' and 'coordinate arrays' or a function returning an array of coordinates"); 1262 } 1263 1264 attr = Type.copyAttributes(attributes, board.options, 'regularpolygon', 'vertices'); 1265 for (i = 2; i < n; i++) { 1266 rot = board.create('transform', [Math.PI * (2 - (n - 2) / n), p[i - 1]], {type: 'rotate'}); 1267 if (pointsExist) { 1268 p[i].addTransform(p[i - 2], rot); 1269 p[i].fullUpdate(); 1270 } else { 1271 if (Type.isArray(attr.ids) && attr.ids.length >= n - 2) { 1272 attr.id = attr.ids[i - 2]; 1273 } 1274 p[i] = board.create('point', [p[i - 2], rot], attr); 1275 p[i].type = Const.OBJECT_TYPE_CAS; 1276 1277 // The next two lines of code are needed to make regular polygones draggable 1278 // The new helper points are set to be draggable. 1279 p[i].isDraggable = true; 1280 p[i].visProp.fixed = false; 1281 } 1282 } 1283 1284 attr = Type.copyAttributes(attributes, board.options, 'regularpolygon'); 1285 el = board.create('polygon', p, attr); 1286 el.elType = 'regularpolygon'; 1287 1288 return el; 1289 }; 1290 1291 /** 1292 * @class A polygonal chain is a connected series of line segments determined by 1293 * <ul> 1294 * <li> a list of points or 1295 * <li> a list of coordinate arrays or 1296 * <li> a function returning a list of coordinate arrays. 1297 * </ul> 1298 * Each two consecutive points of the list define a line. 1299 * In JSXGraph, a polygonal chain is simply realized as polygon without the last - closing - point. 1300 * This may lead to unexpected results. Polygonal chains can be distinguished from polygons by the attribute 'elType' which 1301 * is 'polygonalchain' for the first and 'polygon' for the latter. 1302 * @pseudo 1303 * @constructor 1304 * @name PolygonalChain 1305 * @type Polygon 1306 * @augments JXG.Polygon 1307 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 1308 * @param {Array} vertices The polygon's vertices. 1309 * 1310 * Additionally, a polygonal chain can be created by providing a polygonal chain and a transformation (or an array of transformations). 1311 * The result is a polygonal chain which is the transformation of the supplied polygona chain. 1312 * 1313 * @example 1314 * var attr = { 1315 * snapToGrid: true 1316 * }, 1317 * p = []; 1318 * 1319 * p.push(board.create('point', [-4, 0], attr)); 1320 * p.push(board.create('point', [-1, -3], attr)); 1321 * p.push(board.create('point', [0, 2], attr)); 1322 * p.push(board.create('point', [2, 1], attr)); 1323 * p.push(board.create('point', [4, -2], attr)); 1324 * 1325 * var chain = board.create('polygonalchain', p, {borders: {strokeWidth: 3}}); 1326 * 1327 * </pre><div id="JXG878f93d8-3e49-46cf-aca2-d3bb7d60c5ae" class="jxgbox" style="width: 300px; height: 300px;"></div> 1328 * <script type="text/javascript"> 1329 * (function() { 1330 * var board = JXG.JSXGraph.initBoard('JXG878f93d8-3e49-46cf-aca2-d3bb7d60c5ae', 1331 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 1332 * var attr = { 1333 * snapToGrid: true 1334 * }, 1335 * p = []; 1336 * 1337 * p.push(board.create('point', [-4, 0], attr)); 1338 * p.push(board.create('point', [-1, -3], attr)); 1339 * p.push(board.create('point', [0, 2], attr)); 1340 * p.push(board.create('point', [2, 1], attr)); 1341 * p.push(board.create('point', [4, -2], attr)); 1342 * 1343 * var chain = board.create('polygonalchain', p, {borders: {strokeWidth: 3}}); 1344 * 1345 * })(); 1346 * 1347 * </script><pre> 1348 * 1349 */ 1350 JXG.createPolygonalChain = function (board, parents, attributes) { 1351 var attr, el; 1352 1353 attr = Type.copyAttributes(attributes, board.options, 'polygonalchain'); 1354 el = board.create('polygon', parents, attr); 1355 el.elType = 'polygonalchain'; 1356 1357 // A polygonal chain is not necessarily closed. 1358 el.vertices.pop(); 1359 board.removeObject(el.borders[el.borders.length - 1]); 1360 el.borders.pop(); 1361 1362 return el; 1363 }; 1364 1365 JXG.registerElement('polygon', JXG.createPolygon); 1366 JXG.registerElement('regularpolygon', JXG.createRegularPolygon); 1367 JXG.registerElement('polygonalchain', JXG.createPolygonalChain); 1368 1369 return { 1370 Polygon: JXG.Polygon, 1371 createPolygon: JXG.createPolygon, 1372 createRegularPolygon: JXG.createRegularPolygon 1373 }; 1374 }); 1375