1 /* 2 Copyright 2008-2016 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', 'base/line', 'base/transformation' 50 ], function (JXG, Const, Coords, Statistics, Geometry, Type, GeometryElement, Line, Transform) { 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, vertex, l, len, j, 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 vertex = this.board.select(vertices[i]); 83 this.vertices[i] = vertex; 84 } 85 86 // Close the polygon 87 if (this.vertices.length > 0 && this.vertices[this.vertices.length - 1].id !== this.vertices[0].id) { 88 this.vertices.push(this.vertices[0]); 89 } 90 91 /** 92 * References to the border lines of the polygon. 93 * @type Array 94 */ 95 this.borders = []; 96 97 if (this.withLines) { 98 len = this.vertices.length - 1; 99 for (j = 0; j < len; j++) { 100 // This sets the "correct" labels for the first triangle of a construction. 101 i = (j + 1) % len; 102 attr_line.id = attr_line.ids && attr_line.ids[i]; 103 attr_line.name = attr_line.names && attr_line.names[i]; 104 attr_line.strokecolor = (Type.isArray(attr_line.colors) && attr_line.colors[i % attr_line.colors.length]) || attr_line.strokecolor; 105 attr_line.visible = Type.exists(attributes.borders.visible) ? attributes.borders.visible : attributes.visible; 106 107 if (attr_line.strokecolor === false) { 108 attr_line.strokecolor = 'none'; 109 } 110 111 l = board.create('segment', [this.vertices[i], this.vertices[i + 1]], attr_line); 112 l.dump = false; 113 this.borders[i] = l; 114 l.parentPolygon = this; 115 } 116 } 117 118 // Register polygon at board 119 // This needs to be done BEFORE the points get this polygon added in their descendants list 120 this.id = this.board.setId(this, 'Py'); 121 122 // Add polygon as child to defining points 123 for (i = 0; i < this.vertices.length - 1; i++) { 124 vertex = this.board.select(this.vertices[i]); 125 vertex.addChild(this); 126 } 127 128 this.board.renderer.drawPolygon(this); 129 this.board.finalizeAdding(this); 130 131 this.createGradient(); 132 this.elType = 'polygon'; 133 134 // create label 135 this.createLabel(); 136 137 this.methodMap = JXG.deepCopy(this.methodMap, { 138 borders: 'borders', 139 vertices: 'vertices', 140 A: 'Area', 141 Area: 'Area', 142 boundingBox: 'boundingBox', 143 bounds: 'bounds', 144 addPoints: 'addPoints', 145 insertPoints: 'insertPoints', 146 removePoints: 'removePoints' 147 }); 148 }; 149 150 JXG.Polygon.prototype = new GeometryElement(); 151 152 JXG.extend(JXG.Polygon.prototype, /** @lends JXG.Polygon.prototype */ { 153 /** 154 * Checks whether (x,y) is near the polygon. 155 * @param {Number} x Coordinate in x direction, screen coordinates. 156 * @param {Number} y Coordinate in y direction, screen coordinates. 157 * @returns {Boolean} Returns true, if (x,y) is inside or at the boundary the polygon, otherwise false. 158 */ 159 hasPoint: function (x, y) { 160 161 var i, j, len, c = false; 162 163 if (this.visProp.hasinnerpoints) { 164 // All points of the polygon trigger hasPoint: inner and boundary points 165 len = this.vertices.length; 166 // See http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html for a reference 167 for (i = 0, j = len - 2; i < len - 1; j = i++) { 168 if (((this.vertices[i].coords.scrCoords[2] > y) !== (this.vertices[j].coords.scrCoords[2] > y)) && 169 (x < (this.vertices[j].coords.scrCoords[1] - this.vertices[i].coords.scrCoords[1]) * (y - this.vertices[i].coords.scrCoords[2]) / 170 (this.vertices[j].coords.scrCoords[2] - this.vertices[i].coords.scrCoords[2]) + this.vertices[i].coords.scrCoords[1])) { 171 c = !c; 172 } 173 } 174 } else { 175 // Only boundary points trigger hasPoint 176 len = this.borders.length; 177 for (i = 0; i < len; i++) { 178 if (this.borders[i].hasPoint(x, y)) { 179 c = true; 180 break; 181 } 182 } 183 } 184 185 return c; 186 }, 187 188 /** 189 * Uses the boards renderer to update the polygon. 190 */ 191 updateRenderer: function () { 192 if (this.needsUpdate && this.visProp.visible) { 193 this.board.renderer.updatePolygon(this); 194 this.needsUpdate = false; 195 } 196 197 if (this.hasLabel && this.label.visProp.visible) { 198 this.label.update(); 199 this.board.renderer.updateText(this.label); 200 } 201 202 return this; 203 }, 204 205 /** 206 * return TextAnchor 207 */ 208 getTextAnchor: function () { 209 var a, b, x, y, i; 210 211 if (this.vertices.length === 0) { 212 return new Coords(Const.COORDS_BY_USER, [1, 0, 0], this.board); 213 } 214 215 a = this.vertices[0].X(); 216 b = this.vertices[0].Y(); 217 x = a; 218 y = b; 219 for (i = 0; i < this.vertices.length; i++) { 220 if (this.vertices[i].X() < a) { 221 a = this.vertices[i].X(); 222 } 223 224 if (this.vertices[i].X() > x) { 225 x = this.vertices[i].X(); 226 } 227 228 if (this.vertices[i].Y() > b) { 229 b = this.vertices[i].Y(); 230 } 231 232 if (this.vertices[i].Y() < y) { 233 y = this.vertices[i].Y(); 234 } 235 } 236 237 return new Coords(Const.COORDS_BY_USER, [(a + x) * 0.5, (b + y) * 0.5], this.board); 238 }, 239 240 getLabelAnchor: JXG.shortcut(JXG.Polygon.prototype, 'getTextAnchor'), 241 242 // documented in geometry element 243 cloneToBackground: function () { 244 var copy = {}, er; 245 246 copy.id = this.id + 'T' + this.numTraces; 247 this.numTraces++; 248 copy.vertices = this.vertices; 249 copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true); 250 copy.visProp.layer = this.board.options.layer.trace; 251 copy.board = this.board; 252 Type.clearVisPropOld(copy); 253 254 er = this.board.renderer.enhancedRendering; 255 this.board.renderer.enhancedRendering = true; 256 this.board.renderer.drawPolygon(copy); 257 this.board.renderer.enhancedRendering = er; 258 this.traces[copy.id] = copy.rendNode; 259 260 return this; 261 }, 262 263 /** 264 * Hide the polygon including its border lines. It will still exist but not visible on the board. 265 * @param {Boolean} [borderless=false] If set to true, the polygon is treated as a polygon without 266 * borders, i.e. the borders will not be hidden. 267 */ 268 hideElement: function (borderless) { 269 var i; 270 271 this.visProp.visible = false; 272 this.board.renderer.hide(this); 273 274 if (!borderless) { 275 for (i = 0; i < this.borders.length; i++) { 276 this.borders[i].hideElement(); 277 } 278 } 279 280 if (this.hasLabel && Type.exists(this.label)) { 281 this.label.hiddenByParent = true; 282 if (this.label.visProp.visible) { 283 this.label.hideElement(); 284 } 285 } 286 }, 287 288 /** 289 * Make the element visible. 290 * @param {Boolean} [borderless=false] If set to true, the polygon is treated as a polygon without 291 * borders, i.e. the borders will not be shown. 292 */ 293 showElement: function (borderless) { 294 var i; 295 296 this.visProp.visible = true; 297 this.board.renderer.show(this); 298 299 if (!borderless) { 300 for (i = 0; i < this.borders.length; i++) { 301 this.borders[i].showElement(); 302 this.borders[i].updateRenderer(); 303 } 304 } 305 306 if (Type.exists(this.label) && this.hasLabel && this.label.hiddenByParent) { 307 this.label.hiddenByParent = false; 308 if (!this.label.visProp.visible) { 309 this.label.showElement().updateRenderer(); 310 } 311 } 312 }, 313 314 /** 315 * Area of (not self-intersecting) polygon 316 * @returns {Number} Area of (not self-intersecting) polygon 317 */ 318 Area: function () { 319 return Math.abs(Geometry.signedPolygon(this.vertices, true)); 320 }, 321 322 /** 323 * Bounding box of a polygon. The bounding box is an array of four numbers: the first two numbers 324 * determine the upper left corner, the last two number determine the lower right corner of the bounding box. 325 * 326 * The width and height of a polygon can then determined like this: 327 * @example 328 * var box = polygon.boundingBox(); 329 * var width = box[2] - box[0]; 330 * var height = box[1] - box[3]; 331 * 332 * @returns {Array} Array containing four numbers: [minX, maxY, maxX, minY] 333 */ 334 boundingBox: function () { 335 var box = [0, 0, 0, 0], i, v, 336 le = this.vertices.length - 1; 337 338 if (le === 0) { 339 return box; 340 } 341 box[0] = this.vertices[0].X(); 342 box[2] = box[0]; 343 box[1] = this.vertices[0].Y(); 344 box[3] = box[1]; 345 346 for (i = 1; i < le; ++i) { 347 v = this.vertices[i].X(); 348 if (v < box[0]) { 349 box[0] = v; 350 } else if (v > box[2]) { 351 box[2] = v; 352 } 353 354 v = this.vertices[i].Y(); 355 if (v > box[1]) { 356 box[1] = v; 357 } else if (v < box[3]) { 358 box[3] = v; 359 } 360 } 361 362 return box; 363 }, 364 365 // already documented in GeometryElement 366 bounds: function () { 367 return this.boundingBox(); 368 }, 369 370 /** 371 * This method removes the SVG or VML nodes of the lines and the filled area from the renderer, to remove 372 * the object completely you should use {@link JXG.Board#removeObject}. 373 */ 374 remove: function () { 375 var i; 376 377 for (i = 0; i < this.borders.length; i++) { 378 this.board.removeObject(this.borders[i]); 379 } 380 381 GeometryElement.prototype.remove.call(this); 382 }, 383 384 /** 385 * Finds the index to a given point reference. 386 * @param {JXG.Point} p Reference to an element of type {@link JXG.Point} 387 */ 388 findPoint: function (p) { 389 var i; 390 391 if (!Type.isPoint(p)) { 392 return -1; 393 } 394 395 for (i = 0; i < this.vertices.length; i++) { 396 if (this.vertices[i].id === p.id) { 397 return i; 398 } 399 } 400 401 return -1; 402 }, 403 404 /** 405 * Add more points to the polygon. The new points will be inserted at the end. 406 * @param {JXG.Point} p Arbitrary number of points 407 * @returns {JXG.Polygon} Reference to the polygon 408 */ 409 addPoints: function (p) { 410 var args = Array.prototype.slice.call(arguments); 411 412 return this.insertPoints.apply(this, [this.vertices.length - 2].concat(args)); 413 }, 414 415 /** 416 * Adds more points to the vertex list of the polygon, starting with index <tt><i</tt> 417 * @param {Number} idx The position where the new vertices are inserted, starting with 0. 418 * @param {JXG.Point} p Arbitrary number of points to insert. 419 * @returns {JXG.Polygon} Reference to the polygon object 420 */ 421 insertPoints: function (idx, p) { 422 var i, npoints = [], tmp; 423 424 if (arguments.length === 0) { 425 return this; 426 } 427 428 429 if (idx < 0 || idx > this.vertices.length - 2) { 430 return this; 431 } 432 433 for (i = 1; i < arguments.length; i++) { 434 if (Type.isPoint(arguments[i])) { 435 npoints.push(arguments[i]); 436 } 437 } 438 439 tmp = this.vertices.slice(0, idx + 1).concat(npoints); 440 this.vertices = tmp.concat(this.vertices.slice(idx + 1)); 441 442 if (this.withLines) { 443 tmp = this.borders.slice(0, idx); 444 this.board.removeObject(this.borders[idx]); 445 446 for (i = 0; i < npoints.length; i++) { 447 tmp.push(this.board.create('segment', [this.vertices[idx + i], this.vertices[idx + i + 1]], this.attr_line)); 448 } 449 450 tmp.push(this.board.create('segment', [this.vertices[idx + npoints.length], this.vertices[idx + npoints.length + 1]], this.attr_line)); 451 this.borders = tmp.concat(this.borders.slice(idx)); 452 } 453 454 this.board.update(); 455 456 return this; 457 }, 458 459 /** 460 * Removes given set of vertices from the polygon 461 * @param {JXG.Point} p Arbitrary number of vertices as {@link JXG.Point} elements or index numbers 462 * @returns {JXG.Polygon} Reference to the polygon 463 */ 464 removePoints: function (p) { 465 var i, j, idx, nvertices = [], nborders = [], 466 nidx = [], partition = []; 467 468 // partition: 469 // in order to keep the borders which could be recycled, we have to partition 470 // the set of removed points. I.e. if the points 1, 2, 5, 6, 7, 10 are removed, 471 // the partitions are 472 // 1-2, 5-7, 10-10 473 // this gives us the borders, that can be removed and the borders we have to create. 474 475 476 // remove the last vertex which is identical to the first 477 this.vertices = this.vertices.slice(0, this.vertices.length - 1); 478 479 // collect all valid parameters as indices in nidx 480 for (i = 0; i < arguments.length; i++) { 481 if (Type.isPoint(arguments[i])) { 482 idx = this.findPoint(arguments[i]); 483 } 484 485 if (Type.isNumber(idx) && idx > -1 && idx < this.vertices.length && Type.indexOf(nidx, idx) === -1) { 486 nidx.push(idx); 487 } 488 } 489 490 // sort the elements to be eliminated 491 nidx = nidx.sort(); 492 nvertices = this.vertices.slice(); 493 nborders = this.borders.slice(); 494 495 // initialize the partition 496 if (this.withLines) { 497 partition.push([nidx[nidx.length - 1]]); 498 } 499 500 // run through all existing vertices and copy all remaining ones to nvertices 501 // compute the partition 502 for (i = nidx.length - 1; i > -1; i--) { 503 nvertices[nidx[i]] = -1; 504 505 if (this.withLines && (nidx[i] - 1 > nidx[i - 1])) { 506 partition[partition.length - 1][1] = nidx[i]; 507 partition.push([nidx[i - 1]]); 508 } 509 } 510 511 // finalize the partition computation 512 if (this.withLines) { 513 partition[partition.length - 1][1] = nidx[0]; 514 } 515 516 // update vertices 517 this.vertices = []; 518 for (i = 0; i < nvertices.length; i++) { 519 if (Type.isPoint(nvertices[i])) { 520 this.vertices.push(nvertices[i]); 521 } 522 } 523 if (this.vertices[this.vertices.length - 1].id !== this.vertices[0].id) { 524 this.vertices.push(this.vertices[0]); 525 } 526 527 // delete obsolete and create missing borders 528 if (this.withLines) { 529 for (i = 0; i < partition.length; i++) { 530 for (j = partition[i][1] - 1; j < partition[i][0] + 1; j++) { 531 // special cases 532 if (j < 0) { 533 // first vertex is removed, so the last border has to be removed, too 534 j = 0; 535 this.board.removeObject(this.borders[nborders.length - 1]); 536 nborders[nborders.length - 1] = -1; 537 } else if (j > nborders.length - 1) { 538 j = nborders.length - 1; 539 } 540 541 this.board.removeObject(this.borders[j]); 542 nborders[j] = -1; 543 } 544 545 // only create the new segment if it's not the closing border. the closing border is getting a special treatment at the end 546 // the if clause is newer than the min/max calls inside createSegment; i'm sure this makes the min/max calls obsolete, but 547 // just to be sure... 548 if (partition[i][1] !== 0 && partition[i][0] !== nvertices.length - 1) { 549 nborders[partition[i][0] - 1] = this.board.create('segment', [nvertices[Math.max(partition[i][1] - 1, 0)], nvertices[Math.min(partition[i][0] + 1, this.vertices.length - 1)]], this.attr_line); 550 } 551 } 552 553 this.borders = []; 554 for (i = 0; i < nborders.length; i++) { 555 if (nborders[i] !== -1) { 556 this.borders.push(nborders[i]); 557 } 558 } 559 560 // if the first and/or the last vertex is removed, the closing border is created at the end. 561 if (partition[0][1] === 5 || partition[partition.length - 1][1] === 0) { 562 this.borders.push(this.board.create('segment', [this.vertices[0], this.vertices[this.vertices.length - 2]], this.attr_line)); 563 } 564 } 565 566 this.board.update(); 567 568 return this; 569 }, 570 571 // documented in element.js 572 getParents: function () { 573 this.setParents(this.vertices); 574 return this.parents; 575 }, 576 577 getAttributes: function () { 578 var attr = GeometryElement.prototype.getAttributes.call(this), i; 579 580 if (this.withLines) { 581 attr.lines = attr.lines || {}; 582 attr.lines.ids = []; 583 attr.lines.colors = []; 584 585 for (i = 0; i < this.borders.length; i++) { 586 attr.lines.ids.push(this.borders[i].id); 587 attr.lines.colors.push(this.borders[i].visProp.strokecolor); 588 } 589 } 590 591 return attr; 592 }, 593 594 snapToGrid: function () { 595 var i; 596 597 for (i = 0; i < this.vertices.length; i++) { 598 this.vertices[i].snapToGrid(); 599 } 600 }, 601 602 /** 603 * Moves the polygon by the difference of two coordinates. 604 * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}. 605 * @param {Array} coords coordinates in screen/user units 606 * @param {Array} oldcoords previous coordinates in screen/user units 607 * @returns {JXG.Polygon} this element 608 */ 609 setPositionDirectly: function (method, coords, oldcoords) { 610 var dc, t, i, len, 611 c = new Coords(method, coords, this.board), 612 oldc = new Coords(method, oldcoords, this.board); 613 614 len = this.vertices.length - 1; 615 for (i = 0; i < len; i++) { 616 if (!this.vertices[i].draggable()) { 617 return this; 618 } 619 } 620 621 dc = Statistics.subtract(c.usrCoords, oldc.usrCoords); 622 t = this.board.create('transform', dc.slice(1), {type: 'translate'}); 623 t.applyOnce(this.vertices.slice(0, -1)); 624 625 return this; 626 }, 627 628 /** 629 * Algorithm by Sutherland and Hodgman to compute the intersection of two convex polygons. 630 * The polygon itself is the clipping polygon, it expects as parameter a polygon to be clipped. 631 * See <a href="https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm">wikipedia entry</a>. 632 * Called by {@link JXG.Polygon#intersect}. 633 * 634 * @private 635 * 636 * @param {JXG.Polygon} polygon Polygon which will be clipped. 637 * 638 * @returns {Array} of (normalized homogeneous user) coordinates (i.e. [z, x, y], where z==1 in most cases, 639 * representing the vertices of the intersection polygon. 640 * 641 */ 642 sutherlandHodgman: function(polygon) { 643 // First the two polygons are sorted counter clockwise 644 var clip = JXG.Math.Geometry.sortVertices(this.vertices), // "this" is the clipping polygon 645 subject = JXG.Math.Geometry.sortVertices(polygon.vertices), // "polygon" is the subject polygon 646 647 lenClip = clip.length - 1, 648 lenSubject = subject.length - 1, 649 lenIn, 650 651 outputList = [], 652 inputList, i, j, S, E, cross, 653 654 655 // Determines if the point c3 is right of the line through c1 and c2. 656 // Since the polygons are sorted counter clockwise, "right of" and therefore >= is needed here 657 isInside = function(c1, c2, c3) { 658 return ((c2[1] - c1[1]) * (c3[2] - c1[2]) - (c2[2] - c1[2]) * (c3[1] - c1[1])) >= 0; 659 }; 660 661 for (i = 0; i < lenSubject; i++) { 662 outputList.push(subject[i]); 663 } 664 665 for (i = 0; i < lenClip; i++) { 666 inputList = outputList.slice(0); 667 lenIn = inputList.length; 668 outputList = []; 669 670 S = inputList[lenIn - 1]; 671 672 for (j = 0; j < lenIn; j++) { 673 E = inputList[j]; 674 if (isInside(clip[i], clip[i + 1], E)) { 675 if (!isInside(clip[i], clip[i + 1], S)) { 676 cross = JXG.Math.Geometry.meetSegmentSegment(S, E, clip[i], clip[i + 1]); 677 cross[0][1] /= cross[0][0]; 678 cross[0][2] /= cross[0][0]; 679 cross[0][0] = 1; 680 outputList.push(cross[0]); 681 } 682 outputList.push(E); 683 } else if (isInside(clip[i], clip[i + 1], S)) { 684 cross = JXG.Math.Geometry.meetSegmentSegment(S, E, clip[i], clip[i + 1]); 685 cross[0][1] /= cross[0][0]; 686 cross[0][2] /= cross[0][0]; 687 cross[0][0] = 1; 688 outputList.push(cross[0]); 689 } 690 S = E; 691 } 692 } 693 694 return outputList; 695 }, 696 697 /** 698 * Generic method for the intersection of this polygon with another polygon. 699 * The parent object is the clipping polygon, it expects as parameter a polygon to be clipped. 700 * Both polygons have to be convex. 701 * Calls {@link JXG.Polygon#sutherlandHodgman}. 702 * 703 * @param {JXG.Polygon} polygon Polygon which will be clipped. 704 * 705 * @returns {Array} of (normalized homogeneous user) coordinates (i.e. [z, x, y], where z==1 in most cases, 706 * representing the vertices of the intersection polygon. 707 * 708 * @example 709 * // Static intersection of two polygons pol1 and pol2 710 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 711 * name:'pol1', withLabel: true, 712 * fillColor: 'yellow' 713 * }); 714 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 715 * name:'pol2', withLabel: true 716 * }); 717 * 718 * // Static version: 719 * // the intersection polygon does not adapt to changes of pol1 or pol2. 720 * var pol3 = board.create('polygon', pol1.intersect(pol2), {fillColor: 'blue'}); 721 * </pre><div class="jxgbox"id="d1fe5ea9-309f-494a-af07-ee3d033acb7c" style="width: 300px; height: 300px;"></div> 722 * <script type="text/javascript"> 723 * (function() { 724 * var board = JXG.JSXGraph.initBoard('d1fe5ea9-309f-494a-af07-ee3d033acb7c', {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 725 * // Intersect two polygons pol1 and pol2 726 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 727 * name:'pol1', withLabel: true, 728 * fillColor: 'yellow' 729 * }); 730 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 731 * name:'pol2', withLabel: true 732 * }); 733 * 734 * // Static version: the intersection polygon does not adapt to changes of pol1 or pol2. 735 * var pol3 = board.create('polygon', pol1.intersect(pol2), {fillColor: 'blue'}); 736 * })(); 737 * </script><pre> 738 * 739 * @example 740 * // Dynamic intersection of two polygons pol1 and pol2 741 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 742 * name:'pol1', withLabel: true, 743 * fillColor: 'yellow' 744 * }); 745 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 746 * name:'pol2', withLabel: true 747 * }); 748 * 749 * // Dynamic version: 750 * // the intersection polygon does adapt to changes of pol1 or pol2. 751 * // For this a curve element is used. 752 * var curve = board.create('curve', [[],[]], {fillColor: 'blue', fillOpacity: 0.4}); 753 * curve.updateDataArray = function() { 754 * var mat = JXG.Math.transpose(pol1.intersect(pol2)); 755 * 756 * if (mat.length == 3) { 757 * this.dataX = mat[1]; 758 * this.dataY = mat[2]; 759 * } else { 760 * this.dataX = []; 761 * this.dataY = []; 762 * } 763 * }; 764 * board.update(); 765 * </pre><div class="jxgbox"id="f870d516-ca1a-4140-8fe3-5d64fb42e5f2" style="width: 300px; height: 300px;"></div> 766 * <script type="text/javascript"> 767 * (function() { 768 * var board = JXG.JSXGraph.initBoard('f870d516-ca1a-4140-8fe3-5d64fb42e5f2', {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 769 * // Intersect two polygons pol1 and pol2 770 * var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], { 771 * name:'pol1', withLabel: true, 772 * fillColor: 'yellow' 773 * }); 774 * var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], { 775 * name:'pol2', withLabel: true 776 * }); 777 * 778 * // Dynamic version: 779 * // the intersection polygon does adapt to changes of pol1 or pol2. 780 * // For this a curve element is used. 781 * var curve = board.create('curve', [[],[]], {fillColor: 'blue', fillOpacity: 0.4}); 782 * curve.updateDataArray = function() { 783 * var mat = JXG.Math.transpose(pol1.intersect(pol2)); 784 * 785 * if (mat.length == 3) { 786 * this.dataX = mat[1]; 787 * this.dataY = mat[2]; 788 * } else { 789 * this.dataX = []; 790 * this.dataY = []; 791 * } 792 * }; 793 * board.update(); 794 * })(); 795 * </script><pre> 796 * 797 */ 798 intersect: function(polygon) { 799 return this.sutherlandHodgman(polygon); 800 } 801 802 803 }); 804 805 806 /** 807 * @class A polygon is an area enclosed by a set of border lines which are determined by 808 * <ul> 809 * <li> a list of points or 810 * <li> a list of coordinate arrays or 811 * <li> a function returning a list of coordinate arrays. 812 * </ul> 813 * Each two consecutive points of the list define a line. 814 * @pseudo 815 * @constructor 816 * @name Polygon 817 * @type Polygon 818 * @augments JXG.Polygon 819 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 820 * @param {Array} vertices The polygon's vertices. If the first and the last vertex don't match the first one will be 821 * added to the array by the creator. 822 * @example 823 * var p1 = board.create('point', [0.0, 2.0]); 824 * var p2 = board.create('point', [2.0, 1.0]); 825 * var p3 = board.create('point', [4.0, 6.0]); 826 * var p4 = board.create('point', [1.0, 4.0]); 827 * 828 * var pol = board.create('polygon', [p1, p2, p3, p4]); 829 * </pre><div class="jxgbox"id="682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div> 830 * <script type="text/javascript"> 831 * (function () { 832 * var board = JXG.JSXGraph.initBoard('682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 833 * p1 = board.create('point', [0.0, 2.0]), 834 * p2 = board.create('point', [2.0, 1.0]), 835 * p3 = board.create('point', [4.0, 6.0]), 836 * p4 = board.create('point', [1.0, 4.0]), 837 * cc1 = board.create('polygon', [p1, p2, p3, p4]); 838 * })(); 839 * </script><pre> 840 * 841 * @example 842 * var p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [4.0, 6.0], [4.0, 6.0], [1.0, 3.0]]; 843 * 844 * var pol = board.create('polygon', p, {hasInnerPoints: true}); 845 * </pre><div class="jxgbox"id="9f9a5946-112a-4768-99ca-f30792bcdefb" style="width: 400px; height: 400px;"></div> 846 * <script type="text/javascript"> 847 * (function () { 848 * var board = JXG.JSXGraph.initBoard('9f9a5946-112a-4768-99ca-f30792bcdefb', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 849 * p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [4.0, 6.0], [4.0, 6.0], [1.0, 4.0]], 850 * cc1 = board.create('polygon', p, {hasInnerPoints: true}); 851 * })(); 852 * </script><pre> 853 * 854 * @example 855 * var f1 = function() { return [0.0, 2.0]; }, 856 * f2 = function() { return [2.0, 1.0]; }, 857 * f3 = function() { return [4.0, 6.0]; }, 858 * f4 = function() { return [1.0, 4.0]; }, 859 * cc1 = board.create('polygon', [f1, f2, f3, f4]); 860 * 861 * </pre><div class="jxgbox"id="ceb09915-b783-44db-adff-7877ae3534c8" style="width: 400px; height: 400px;"></div> 862 * <script type="text/javascript"> 863 * (function () { 864 * var board = JXG.JSXGraph.initBoard('ceb09915-b783-44db-adff-7877ae3534c8', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 865 * f1 = function() { return [0.0, 2.0]; }, 866 * f2 = function() { return [2.0, 1.0]; }, 867 * f3 = function() { return [4.0, 6.0]; }, 868 * f4 = function() { return [1.0, 4.0]; }, 869 * cc1 = board.create('polygon', [f1, f2, f3, f4]); 870 * })(); 871 * </script><pre> 872 */ 873 JXG.createPolygon = function (board, parents, attributes) { 874 var el, i, points = [], 875 attr, p; 876 877 points = Type.providePoints(board, parents, attributes, 'polygon', ['vertices']); 878 if (points === false) { 879 throw new Error("JSXGraph: Can't create polygon with parent types other than 'point' and 'coordinate arrays' or a function returning an array of coordinates"); 880 } 881 882 attr = Type.copyAttributes(attributes, board.options, 'polygon'); 883 el = new JXG.Polygon(board, points, attr); 884 el.isDraggable = true; 885 886 return el; 887 }; 888 889 890 /** 891 * @class Constructs a regular polygon. It needs two points which define the base line and the number of vertices. 892 * @pseudo 893 * @description Constructs a regular polygon. It needs two points which define the base line and the number of vertices, or a set of points. 894 * @constructor 895 * @name RegularPolygon 896 * @type Polygon 897 * @augments Polygon 898 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 899 * @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. 900 * @example 901 * var p1 = board.create('point', [0.0, 2.0]); 902 * var p2 = board.create('point', [2.0, 1.0]); 903 * 904 * var pol = board.create('regularpolygon', [p1, p2, 5]); 905 * </pre><div class="jxgbox"id="682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div> 906 * <script type="text/javascript"> 907 * (function () { 908 * var board = JXG.JSXGraph.initBoard('682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 909 * p1 = board.create('point', [0.0, 2.0]), 910 * p2 = board.create('point', [2.0, 1.0]), 911 * cc1 = board.create('regularpolygon', [p1, p2, 5]); 912 * })(); 913 * </script><pre> 914 * @example 915 * var p1 = board.create('point', [0.0, 2.0]); 916 * var p2 = board.create('point', [4.0,4.0]); 917 * var p3 = board.create('point', [2.0,0.0]); 918 * 919 * var pol = board.create('regularpolygon', [p1, p2, p3]); 920 * </pre><div class="jxgbox"id="096a78b3-bd50-4bac-b958-3be5e7df17ed" style="width: 400px; height: 400px;"></div> 921 * <script type="text/javascript"> 922 * (function () { 923 * var board = JXG.JSXGraph.initBoard('096a78b3-bd50-4bac-b958-3be5e7df17ed', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}), 924 * p1 = board.create('point', [0.0, 2.0]), 925 * p2 = board.create('point', [4.0, 4.0]), 926 * p3 = board.create('point', [2.0,0.0]), 927 * cc1 = board.create('regularpolygon', [p1, p2, p3]); 928 * })(); 929 * </script><pre> 930 */ 931 JXG.createRegularPolygon = function (board, parents, attributes) { 932 var el, i, n, 933 p = [], rot, c, len, pointsExist, attr; 934 935 len = parents.length; 936 n = parents[len - 1]; 937 938 if (Type.isNumber(n) && (parents.length !== 3 || n < 3)) { 939 throw new Error("JSXGraph: A regular polygon needs two point types and a number > 2 as input."); 940 } 941 942 if (Type.isNumber(board.select(n))) { // Regular polygon given by 2 points and a number 943 len--; 944 pointsExist = false; 945 } else { // Regular polygon given by n points 946 n = len; 947 pointsExist = true; 948 } 949 950 p = Type.providePoints(board, parents.slice(0, len), attributes, 'regularpolygon', ['vertices']); 951 if (p === false) { 952 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"); 953 } 954 955 attr = Type.copyAttributes(attributes, board.options, 'regularpolygon', 'vertices'); 956 for (i = 2; i < n; i++) { 957 rot = board.create('transform', [Math.PI * (2 - (n - 2) / n), p[i - 1]], {type: 'rotate'}); 958 if (pointsExist) { 959 p[i].addTransform(p[i - 2], rot); 960 p[i].prepareUpdate().update().updateRenderer(); 961 } else { 962 if (Type.isArray(attr.ids) && attr.ids.length >= n - 2) { 963 attr.id = attr.ids[i - 2]; 964 } 965 p[i] = board.create('point', [p[i - 2], rot], attr); 966 p[i].type = Const.OBJECT_TYPE_CAS; 967 968 // The next two lines of code are needed to make regular polgonmes draggable 969 // The new helper points are set to be draggable. 970 p[i].isDraggable = true; 971 p[i].visProp.fixed = false; 972 } 973 } 974 975 attr = Type.copyAttributes(attributes, board.options, 'polygon'); 976 el = board.create('polygon', p, attr); 977 el.elType = 'regularpolygon'; 978 979 return el; 980 }; 981 982 JXG.registerElement('polygon', JXG.createPolygon); 983 JXG.registerElement('regularpolygon', JXG.createRegularPolygon); 984 985 return { 986 Polygon: JXG.Polygon, 987 createPolygon: JXG.createPolygon, 988 createRegularPolygon: JXG.createRegularPolygon 989 }; 990 }); 991