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