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