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