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