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