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