      1 /*
      2     Copyright 2008-2018
      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  utils/type
     40  */
     41
     42 /**
     43  * @fileoverview In this file the class Group is defined, a class for
     44  * managing grouping of points.
     45  */
     46
     47 define([
     48     'jxg', 'base/constants', 'math/math', 'math/geometry', 'utils/type'
     49 ], function (JXG, Const, Mat, Geometry, Type) {
     50
     51     "use strict";
     52
     53     /**
     54      * Creates a new instance of Group.
     55      * @class In this class all group management is done.
     56      * @param {JXG.Board} board
     57      * @param {String} id Unique identifier for this object.  If null or an empty string is given,
     58      * an unique id will be generated by Board
     59      * @param {String} name Not necessarily unique name, displayed on the board.  If null or an
     60      * empty string is given, an unique name will be generated.
     61      * @param {Array} objects Array of points to add to this group.
     62      * @param {Object} attributes Defines the visual appearance of the group.
     63      * @constructor
     64      */
     65     JXG.Group = function (board, id, name, objects, attributes) {
     66         var number, objArray, i, obj;
     67
     68         this.board = board;
     69         this.objects = {};
     70         number = this.board.numObjects;
     71         this.board.numObjects += 1;
     72
     73         if ((id === '') || !Type.exists(id)) {
     74             this.id = this.board.id + 'Group' + number;
     75         } else {
     76             this.id = id;
     77         }
     78         this.board.groups[this.id] = this;
     79
     80         this.type = Const.OBJECT_TYPE_POINT;
     81         this.elementClass = Const.OBJECT_CLASS_POINT;
     82
     83         if ((name === '') || !Type.exists(name)) {
     84             this.name = 'group_' + this.board.generateName(this);
     85         } else {
     86             this.name = name;
     87         }
     88         delete this.type;
     89
     90         this.coords = {};
     91         this.needsRegularUpdate = attributes.needsregularupdate;
     92
     93         this.rotationCenter = 'centroid';
     94         this.scaleCenter = null;
     95         this.rotationPoints = [];
     96         this.translationPoints = [];
     97         this.scalePoints = [];
     98         this.scaleDirections = {};
     99
    100         this.parents = [];
    101
    102         if (Type.isArray(objects)) {
    103             objArray = objects;
    104         } else {
    105             objArray = Array.prototype.slice.call(arguments, 3);
    106         }
    107
    108         for (i = 0; i < objArray.length; i++) {
    109             obj = this.board.select(objArray[i]);
    110
    111             if ((!Type.evaluate(obj.visProp.fixed)) && Type.exists(obj.coords)) {
    112                 this.addPoint(obj);
    113             }
    114         }
    115
    116         this.methodMap = {
    117             ungroup: 'ungroup',
    118             add: 'addPoint',
    119             addPoint: 'addPoint',
    120             addPoints: 'addPoints',
    121             addGroup: 'addGroup',
    122             remove: 'removePoint',
    123             removePoint: 'removePoint',
    124             setAttribute: 'setAttribute',
    125             setProperty: 'setAttribute'
    126         };
    127     };
    128
    129     JXG.extend(JXG.Group.prototype, /** @lends JXG.Group.prototype */ {
    130         /**
    131          * Releases all elements of this group.
    132          * @returns {JXG.Group} returns this (empty) group
    133          */
    134         ungroup: function () {
    135             var el, p, i;
    136             for (el in this.objects) {
    137                 if (this.objects.hasOwnProperty(el)) {
    138                     p = this.objects[el].point;
    139                     if (Type.isArray(p.groups)) {
    140                         i = Type.indexOf(p.groups, this.id);
    141                         if (i >= 0) {
    142                             delete p.groups[i];
    143                         }
    144                     }
    145                 }
    146             }
    147
    148             this.objects = {};
    149             return this;
    150         },
    151
    152         /**
    153          * Adds ids of elements to the array this.parents. This is a copy
    154          * of {@link Element.addParents}.
    155          * @param {Array} parents Array of elements or ids of elements.
    156          * Alternatively, one can give a list of objects as parameters.
    157          * @returns {JXG.Object} reference to the object itself.
    158          **/
    159         addParents: function (parents) {
    160             var i, len, par;
    161
    162             if (Type.isArray(parents)) {
    163                 par = parents;
    164             } else {
    165                 par = arguments;
    166             }
    167
    168             len = par.length;
    169             for (i = 0; i < len; ++i) {
    170                 if (Type.isId(this.board, par[i])) {
    171                     this.parents.push(par[i]);
    172                 } else if (Type.exists(par[i].id)) {
    173                     this.parents.push(par[i].id);
    174                 }
    175             }
    176
    177             this.parents = Type.uniqueArray(this.parents);
    178         },
    179
    180         /**
    181          * Sets ids of elements to the array this.parents. This is a copy
    182          * of {@link Element.setParents}
    183          * First, this.parents is cleared. See {@link Group#addParents}.
    184          * @param {Array} parents Array of elements or ids of elements.
    185          * Alternatively, one can give a list of objects as parameters.
    186          * @returns {JXG.Object} reference to the object itself.
    187          **/
    188         setParents: function(parents) {
    189             this.parents = [];
    190             this.addParents(parents);
    191         },
    192
    193         /**
    194          * List of the element ids resp. values used as parents in {@link JXG.Board#create}.
    195          * @returns {Array}
    196          */
    197         getParents: function () {
    198             return Type.isArray(this.parents) ? this.parents : [];
    199         },
    200
    201         /**
    202          * Sends an update to all group members. This method is called from the points' coords object event listeners
    203          * and not by the board.
    204          * @param{JXG.GeometryElement} drag Element that caused the update.
    205          * @returns {JXG.Group} returns this group
    206          */
    207         update: function (drag) {
    208             var el, actionCenter, desc, s, sx, sy, alpha, t, center, obj = null;
    209
    210             if (!this.needsUpdate) {
    211                 return this;
    212             }
    213
    214             drag = this._update_find_drag_type();
    215
    216             if (drag.action === 'nothing') {
    217                 return this;
    218             }
    219
    220             obj = this.objects[drag.id].point;
    221
    222             // Prepare translation, scaling or rotation
    223             if (drag.action === 'translation') {
    224                 t = [
    225                     obj.coords.usrCoords[1] - this.coords[drag.id].usrCoords[1],
    226                     obj.coords.usrCoords[2] - this.coords[drag.id].usrCoords[2]
    227                 ];
    228
    229             } else if (drag.action === 'rotation' || drag.action === 'scaling') {
    230                 if (drag.action === 'rotation') {
    231                     actionCenter = 'rotationCenter';
    232                 } else {
    233                     actionCenter = 'scaleCenter';
    234                 }
    235
    236                 if (Type.isPoint(this[actionCenter])) {
    237                     center = this[actionCenter].coords.usrCoords.slice(1);
    238                 } else if (this[actionCenter] === 'centroid') {
    239                     center = this._update_centroid_center();
    240                 } else if (Type.isArray(this[actionCenter])) {
    241                     center = this[actionCenter];
    242                 } else if (Type.isFunction(this[actionCenter])) {
    243                     center = this[actionCenter]();
    244                 } else {
    245                     return this;
    246                 }
    247
    248                 if (drag.action === 'rotation') {
    249                     alpha = Geometry.rad(this.coords[drag.id].usrCoords.slice(1), center, this.objects[drag.id].point);
    250                     t = this.board.create('transform', [alpha, center[0], center[1]], {type: 'rotate'});
    251                     t.update();  // This initializes t.matrix, which is needed if the action element is the first group element.
    252                 } else if (drag.action === 'scaling') {
    253                     s = Geometry.distance(this.coords[drag.id].usrCoords.slice(1), center);
    254                     if (Math.abs(s) < Mat.eps) {
    255                         return this;
    256                     }
    257                     s = Geometry.distance(obj.coords.usrCoords.slice(1), center) / s;
    258                     sx = (this.scaleDirections[drag.id].indexOf('x') >= 0) ? s : 1.0;
    259                     sy = (this.scaleDirections[drag.id].indexOf('y') >= 0) ? s : 1.0;
    260
    261                     // Shift scale center to origin, scale and shift the scale center back.
    262                     t = this.board.create('transform',
    263                             [1, 0, 0,
    264                              center[0] * (1 -  sx), sx, 0,
    265                              center[1] * (1 -  sy), 0, sy], {type: 'generic'});
    266                     t.update();  // This initializes t.matrix, which is needed if the action element is the first group element.
    267                 } else {
    268                     return this;
    269                 }
    270             }
    271
    272             this._update_apply_transformation(drag, t);
    273
    274             this.needsUpdate = false;  // This is needed here to prevent infinite recursion because
    275                                        // of the board.updateElements call below,
    276
    277             // Prepare dependent objects for update
    278             for (el in this.objects) {
    279                 if (this.objects.hasOwnProperty(el)) {
    280                     for (desc in this.objects[el].descendants) {
    281                         if (this.objects[el].descendants.hasOwnProperty(desc)) {
    282                             this.objects[el].descendants.needsUpdate = this.objects[el].descendants.needsRegularUpdate || this.board.needsFullUpdate;
    283                         }
    284                     }
    285                 }
    286             }
    287             this.board.updateElements(drag);
    288
    289             // Now, all group elements have their new position and
    290             // we can update the bookkeeping of the coordinates of the group elements.
    291             for (el in this.objects) {
    292                 if (this.objects.hasOwnProperty(el)) {
    293                     obj = this.objects[el].point;
    294                     this.coords[obj.id] = {usrCoords: obj.coords.usrCoords.slice(0)};
    295                 }
    296             }
    297
    298             return this;
    299         },
    300
    301         /**
    302          * @private
    303          * Determine what the dragging of a group element should do:
    304          * rotation, translation, scaling or nothing.
    305          */
    306         _update_find_drag_type: function () {
    307             var el, obj,
    308                 action = 'nothing',
    309                 changed = [],
    310                 dragObjId;
    311
    312             // Determine how many elements have changed their position
    313             // If more than one element changed its position, it is a translation.
    314             // If exactly one element changed its position we have to find the type of the point.
    315             for (el in this.objects) {
    316                 if (this.objects.hasOwnProperty(el)) {
    317                     obj = this.objects[el].point;
    318
    319                     if (obj.coords.distance(Const.COORDS_BY_USER, this.coords[el]) > Mat.eps) {
    320                         changed.push(obj.id);
    321                     }
    322                 }
    323             }
    324
    325             // Determine type of action: translation, scaling or rotation
    326             if (changed.length === 0) {
    327                 return {
    328                     'action': action,
    329                     'id': '',
    330                     'changed': changed
    331                 };
    332             }
    333
    334             dragObjId = changed[0];
    335             obj = this.objects[dragObjId].point;
    336
    337             if (changed.length > 1) { // More than one point moved => translation
    338                 action = 'translation';
    339             } else {                        // One point moved => we have to determine the type
    340                 if (Type.isInArray(this.rotationPoints, obj) && Type.exists(this.rotationCenter)) {
    341                     action = 'rotation';
    342                 } else if (Type.isInArray(this.scalePoints, obj) && Type.exists(this.scaleCenter)) {
    343                     action = 'scaling';
    344                 } else if (Type.isInArray(this.translationPoints, obj)) {
    345                     action = 'translation';
    346                 }
    347             }
    348
    349             return {
    350                 'action': action,
    351                 'id': dragObjId,
    352                 'changed': changed
    353             };
    354         },
    355
    356         /**
    357          * @private
    358          * Determine the Euclidean coordinates of the centroid of the group.
    359          * @returns {Array} array of length two,
    360          */
    361         _update_centroid_center: function () {
    362             var center, len, el;
    363
    364             center = [0, 0];
    365             len = 0;
    366             for (el in this.coords) {
    367                 if (this.coords.hasOwnProperty(el)) {
    368                     center[0] += this.coords[el].usrCoords[1];
    369                     center[1] += this.coords[el].usrCoords[2];
    370                     ++len;
    371                 }
    372             }
    373             if (len > 0) {
    374                 center[0] /= len;
    375                 center[1] /= len;
    376             }
    377
    378             return center;
    379         },
    380
    381         /**
    382          * @private
    383          * Apply the transformation to all elements of the group
    384          */
    385         _update_apply_transformation: function (drag, t) {
    386             var el, obj;
    387
    388             for (el in this.objects) {
    389                 if (this.objects.hasOwnProperty(el)) {
    390                     if (Type.exists(this.board.objects[el])) {
    391                         obj = this.objects[el].point;
    392
    393                         // Here, it is important that we change the position
    394                         // of elements by using setCoordinates.
    395                         // Thus, we avoid the call of snapToGrid().
    396                         // This is done in the subsequent call of board.updateElements()
    397                         // in Group.update() above.
    398                         if (obj.id !== drag.id) {
    399                             if (drag.action === 'translation') {
    400                                 if (!Type.isInArray(drag.changed, obj.id)) {
    401                                     obj.coords.setCoordinates(Const.COORDS_BY_USER,
    402                                         [this.coords[el].usrCoords[1] + t[0],
    403                                          this.coords[el].usrCoords[2] + t[1]]);
    404                                 }
    405                             } else if (drag.action === 'rotation' || drag.action === 'scaling') {
    406                                 t.applyOnce([obj]);
    407                             }
    408                         } else {
    409                             if (drag.action === 'rotation' || drag.action === 'scaling') {
    410                                 obj.coords.setCoordinates(Const.COORDS_BY_USER,
    411                                     Mat.matVecMult(t.matrix, this.coords[obj.id].usrCoords));
    412                             }
    413                         }
    414                     } else {
    415                         delete this.objects[el];
    416                     }
    417                 }
    418             }
    419         },
    420
    421         /**
    422          * Adds an Point to this group.
    423          * @param {JXG.Point} object The point added to the group.
    424          * @returns {JXG.Group} returns this group
    425          */
    426         addPoint: function (object) {
    427             this.objects[object.id] = {point: this.board.select(object)};
    428             this.coords[object.id] = {usrCoords: object.coords.usrCoords.slice(0) };
    429             this.translationPoints.push(object);
    430
    431             object.groups.push(this.id);
    432             object.groups = Type.uniqueArray(object.groups);
    433
    434             return this;
    435         },
    436
    437         /**
    438          * Adds multiple points to this group.
    439          * @param {Array} objects An array of points to add to the group.
    440          * @returns {JXG.Group} returns this group
    441          */
    442         addPoints: function (objects) {
    443             var p;
    444
    445             for (p = 0; p < objects.length; p++) {
    446                 this.addPoint(objects[p]);
    447             }
    448
    449             return this;
    450         },
    451
    452         /**
    453          * Adds all points in a group to this group.
    454          * @param {JXG.Group} group The group added to this group.
    455          * @returns {JXG.Group} returns this group
    456          */
    457         addGroup: function (group) {
    458             var el;
    459
    460             for (el in group.objects) {
    461                 if (group.objects.hasOwnProperty(el)) {
    462                     this.addPoint(group.objects[el].point);
    463                 }
    464             }
    465
    466             return this;
    467         },
    468
    469         /**
    470          * Removes a point from the group.
    471          * @param {JXG.Point} point
    472          * @returns {JXG.Group} returns this group
    473          */
    474         removePoint: function (point) {
    475             delete this.objects[point.id];
    476
    477             return this;
    478         },
    479
    480         /**
    481          * Sets the center of rotation for the group. This is either a point or the centroid of the group.
    482          * @param {JXG.Point|String} object A point which will be the center of rotation, the string "centroid", or
    483          * an array of length two, or a function returning an array of length two.
    484          * @default 'centroid'
    485          * @returns {JXG.Group} returns this group
    486          */
    487         setRotationCenter: function (object) {
    488             this.rotationCenter = object;
    489
    490             return this;
    491         },
    492
    493         /**
    494          * Sets the rotation points of the group. Dragging at one of these points results into a rotation of the whole group around
    495          * the rotation center of the group {@see JXG.Group#setRotationCenter}.
    496          * @param {Array|JXG.Point} objects Array of {@link JXG.Point} or arbitrary number of {@link JXG.Point} elements.
    497          * @returns {JXG.Group} returns this group
    498          */
    499         setRotationPoints: function (objects) {
    500             return this._setActionPoints('rotation', objects);
    501         },
    502
    503         /**
    504          * Adds a point to the set of rotation points of the group. Dragging at one of these points results into a rotation of the whole group around
    505          * the rotation center of the group {@see JXG.Group#setRotationCenter}.
    506          * @param {JXG.Point} point {@link JXG.Point} element.
    507          * @returns {JXG.Group} returns this group
    508          */
    509         addRotationPoint: function (point) {
    510             return this._addActionPoint('rotation', point);
    511         },
    512
    513         /**
    514          * Removes the rotation property from a point of the group.
    515          * @param {JXG.Point} point {@link JXG.Point} element.
    516          * @returns {JXG.Group} returns this group
    517          */
    518         removeRotationPoint: function (point) {
    519             return this._removeActionPoint('rotation', point);
    520         },
    521
    522         /**
    523          * Sets the translation points of the group. Dragging at one of these points results into a translation of the whole group.
    524          * @param {Array|JXG.Point} objects Array of {@link JXG.Point} or arbitrary number of {@link JXG.Point} elements.
    525          *
    526          * By default, all points of the group are translation points.
    527          * @returns {JXG.Group} returns this group
    528          */
    529         setTranslationPoints: function (objects) {
    530             return this._setActionPoints('translation', objects);
    531         },
    532
    533         /**
    534          * Adds a point to the set of the translation points of the group. Dragging at one of these points results into a translation of the whole group.
    535          * @param {JXG.Point} point {@link JXG.Point} element.
    536          * @returns {JXG.Group} returns this group
    537          */
    538         addTranslationPoint: function (point) {
    539             return this._addActionPoint('translation', point);
    540         },
    541
    542         /**
    543          * Removes the translation property from a point of the group.
    544          * @param {JXG.Point} point {@link JXG.Point} element.
    545          * @returns {JXG.Group} returns this group
    546          */
    547         removeTranslationPoint: function (point) {
    548             return this._removeActionPoint('translation', point);
    549         },
    550
    551         /**
    552          * Sets the center of scaling for the group. This is either a point or the centroid of the group.
    553          * @param {JXG.Point|String} object A point which will be the center of scaling, the string "centroid", or
    554          * an array of length two, or a function returning an array of length two.
    555          * @returns {JXG.Group} returns this group
    556          */
    557         setScaleCenter: function (object) {
    558             this.scaleCenter = object;
    559
    560             return this;
    561         },
    562
    563         /**
    564          * Sets the scale points of the group. Dragging at one of these points results into a scaling of the whole group.
    565          * @param {Array|JXG.Point} objects Array of {@link JXG.Point} or arbitrary number of {@link JXG.Point} elements.
    566          * @param {String} direction Restricts the directions to be scaled. Possible values are 'x', 'y', 'xy'. Default value is 'xy'.
    567          *
    568          * By default, all points of the group are translation points.
    569          * @returns {JXG.Group} returns this group
    570          */
    571         setScalePoints: function (objects, direction) {
    572             var objs, i, len;
    573             if (Type.isArray(objects)) {
    574                 objs = objects;
    575             } else {
    576                 objs = arguments;
    577             }
    578
    579             len = objs.length;
    580             for (i = 0; i < len; ++i) {
    581                 this.scaleDirections[this.board.select(objs[i]).id] = direction || 'xy';
    582             }
    583
    584             return this._setActionPoints('scale', objects);
    585         },
    586
    587         /**
    588          * Adds a point to the set of the scale points of the group. Dragging at one of these points results into a scaling of the whole group.
    589          * @param {JXG.Point} point {@link JXG.Point} element.
    590          * @param {String} direction Restricts the directions to be scaled. Possible values are 'x', 'y', 'xy'. Default value is 'xy'.
    591          * @returns {JXG.Group} returns this group
    592          */
    593         addScalePoint: function (point, direction) {
    594             this._addActionPoint('scale', point);
    595             this.scaleDirections[this.board.select(point).id] = direction || 'xy';
    596
    597             return this;
    598         },
    599
    600         /**
    601          * Removes the scaling property from a point of the group.
    602          * @param {JXG.Point} point {@link JXG.Point} element.
    603          * @returns {JXG.Group} returns this group
    604          */
    605         removeScalePoint: function (point) {
    606             return this._removeActionPoint('scale', point);
    607         },
    608
    609         /**
    610          * Generic method for {@link JXG.Group@setTranslationPoints} and {@link JXG.Group@setRotationPoints}
    611          * @private
    612          */
    613         _setActionPoints: function (action, objects) {
    614             var objs, i, len;
    615             if (Type.isArray(objects)) {
    616                 objs = objects;
    617             } else {
    618                 objs = arguments;
    619             }
    620
    621             len = objs.length;
    622             this[action + 'Points'] = [];
    623             for (i = 0; i < len; ++i) {
    624                 this._addActionPoint(action, objs[i]);
    625             }
    626
    627             return this;
    628         },
    629
    630         /**
    631          * Generic method for {@link JXG.Group@addTranslationPoint} and {@link JXG.Group@addRotationPoint}
    632          * @private
    633          */
    634         _addActionPoint: function (action, point) {
    635             this[action + 'Points'].push(this.board.select(point));
    636
    637             return this;
    638         },
    639
    640         /**
    641          * Generic method for {@link JXG.Group@removeTranslationPoint} and {@link JXG.Group@removeRotationPoint}
    642          * @private
    643          */
    644         _removeActionPoint: function (action, point) {
    645             var idx = this[action + 'Points'].indexOf(this.board.select(point));
    646             if (idx > -1) {
    647                 this[action + 'Points'].splice(idx, 1);
    648             }
    649
    650             return this;
    651         },
    652
    653         /**
    654          * @deprecated
    655          * Use setAttribute
    656          */
    657         setProperty: function () {
    658             JXG.deprecated('Group.setProperty', 'Group.setAttribute()');
    659             this.setAttribute.apply(this, arguments);
    660         },
    661
    662         setAttribute: function () {
    663             var el;
    664
    665             for (el in this.objects) {
    666                 if (this.objects.hasOwnProperty(el)) {
    667                     this.objects[el].point.setAttribute.apply(this.objects[el].point, arguments);
    668                 }
    669             }
    670
    671             return this;
    672         }
    673     });
    674
    675     /**
    676      * @class This element combines a given set of {@link JXG.Point} elements to a
    677      *  group. The elements of the group and dependent elements can be translated, rotated and scaled by
    678      *  dragging one of the group elements.
    679      *
    680      *
    681      * @pseudo
    682      * @description
    683      * @name Group
    684      * @augments JXG.Group
    685      * @constructor
    686      * @type JXG.Group
    687      * @param {JXG.Board} board The board the points are on.
    688      * @param {Array} parents Array of points to group.
    689      * @param {Object} attributes Visual properties (unused).
    690      * @returns {JXG.Group}
    691      *
    692      * @example
    693      *
    694      *  // Create some free points. e.g. A, B, C, D
    695      *  // Create a group
    696      *
    697      *  var p, col, g;
    698      *  col = 'blue';
    699      *  p = [];
    700      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    701      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    702      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:col, fillColor:col}));
    703      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    704      *  g = board.create('group', p);
    705      *
    706      * </pre><div class="jxgbox" id="a2204533-db91-4af9-b720-70394de4d367" style="width: 400px; height: 300px;"></div>
    707      * <script type="text/javascript">
    708      *  (function () {
    709      *  var board, p, col, g;
    710      *  board = JXG.JSXGraph.initBoard('a2204533-db91-4af9-b720-70394de4d367', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
    711      *  col = 'blue';
    712      *  p = [];
    713      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    714      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    715      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:col, fillColor:col}));
    716      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    717      *  g = board.create('group', p);
    718      *  })();
    719      * </script><pre>
    720      *
    721      *
    722      * @example
    723      *
    724      *  // Create some free points. e.g. A, B, C, D
    725      *  // Create a group
    726      *  // If the points define a polygon and the polygon has the attribute hasInnerPoints:true,
    727      *  // the polygon can be dragged around.
    728      *
    729      *  var p, col, pol, g;
    730      *  col = 'blue';
    731      *  p = [];
    732      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    733      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    734      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:col, fillColor:col}));
    735      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    736      *
    737      *  pol = board.create('polygon', p, {hasInnerPoints: true});
    738      *  g = board.create('group', p);
    739      *
    740      * </pre><div class="jxgbox" id="781b5564-a671-4327-81c6-de915c8f924e" style="width: 400px; height: 300px;"></div>
    741      * <script type="text/javascript">
    742      *  (function () {
    743      *  var board, p, col, pol, g;
    744      *  board = JXG.JSXGraph.initBoard('781b5564-a671-4327-81c6-de915c8f924e', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
    745      *  col = 'blue';
    746      *  p = [];
    747      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    748      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    749      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:col, fillColor:col}));
    750      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    751      *  pol = board.create('polygon', p, {hasInnerPoints: true});
    752      *  g = board.create('group', p);
    753      *  })();
    754      * </script><pre>
    755      *
    756      *  @example
    757      *
    758      *  // Allow rotations:
    759      *  // Define a center of rotation and declare points of the group as "rotation points".
    760      *
    761      *  var p, col, pol, g;
    762      *  col = 'blue';
    763      *  p = [];
    764      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    765      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    766      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    767      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    768      *
    769      *  pol = board.create('polygon', p, {hasInnerPoints: true});
    770      *  g = board.create('group', p);
    771      *  g.setRotationCenter(p[0]);
    772      *  g.setRotationPoints([p[1], p[2]]);
    773      *
    774      * </pre><div class="jxgbox" id="f0491b62-b377-42cb-b55c-4ef5374b39fc" style="width: 400px; height: 300px;"></div>
    775      * <script type="text/javascript">
    776      *  (function () {
    777      *  var board, p, col, pol, g;
    778      *  board = JXG.JSXGraph.initBoard('f0491b62-b377-42cb-b55c-4ef5374b39fc', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
    779      *  col = 'blue';
    780      *  p = [];
    781      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    782      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    783      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    784      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    785      *  pol = board.create('polygon', p, {hasInnerPoints: true});
    786      *  g = board.create('group', p);
    787      *  g.setRotationCenter(p[0]);
    788      *  g.setRotationPoints([p[1], p[2]]);
    789      *  })();
    790      * </script><pre>
    791      *
    792      *  @example
    793      *
    794      *  // Allow rotations:
    795      *  // As rotation center, arbitrary points, coordinate arrays,
    796      *  // or functions returning coordinate arrays can be given.
    797      *  // Another possibility is to use the predefined string 'centroid'.
    798      *
    799      *  // The methods to define the rotation points can be chained.
    800      *
    801      *  var p, col, pol, g;
    802      *  col = 'blue';
    803      *  p = [];
    804      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    805      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    806      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    807      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    808      *
    809      *  pol = board.create('polygon', p, {hasInnerPoints: true});
    810      *  g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[1], p[2]]);
    811      *
    812      * </pre><div class="jxgbox" id="8785b099-a75e-4769-bfd8-47dd4376fe27" style="width: 400px; height: 300px;"></div>
    813      * <script type="text/javascript">
    814      *  (function () {
    815      *  var board, p, col, pol, g;
    816      *  board = JXG.JSXGraph.initBoard('8785b099-a75e-4769-bfd8-47dd4376fe27', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
    817      *  col = 'blue';
    818      *  p = [];
    819      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    820      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    821      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    822      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    823      *  pol = board.create('polygon', p, {hasInnerPoints: true});
    824      *  g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[1], p[2]]);
    825      *  })();
    826      * </script><pre>
    827      *
    828      *  @example
    829      *
    830      *  // Allow scaling:
    831      *  // As for rotation one can declare points of the group to trigger a scaling operation.
    832      *  // For this, one has to define a scaleCenter, in analogy to rotations.
    833      *
    834      *  // Here, the yellow  point enables scaling, the red point a rotation.
    835      *
    836      *  var p, col, pol, g;
    837      *  col = 'blue';
    838      *  p = [];
    839      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    840      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'}));
    841      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    842      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    843      *
    844      *  pol = board.create('polygon', p, {hasInnerPoints: true});
    845      *  g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[2]]);
    846      *  g.setScaleCenter(p[0]).setScalePoints(p[1]);
    847      *
    848      * </pre><div class="jxgbox" id="c3ca436b-e4fc-4de5-bab4-09790140c675" style="width: 400px; height: 300px;"></div>
    849      * <script type="text/javascript">
    850      *  (function () {
    851      *  var board, p, col, pol, g;
    852      *  board = JXG.JSXGraph.initBoard('c3ca436b-e4fc-4de5-bab4-09790140c675', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
    853      *  col = 'blue';
    854      *  p = [];
    855      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    856      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'}));
    857      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    858      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    859      *  pol = board.create('polygon', p, {hasInnerPoints: true});
    860      *  g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[2]]);
    861      *  g.setScaleCenter(p[0]).setScalePoints(p[1]);
    862      *  })();
    863      * </script><pre>
    864      *
    865      *  @example
    866      *
    867      *  // Allow Translations:
    868      *  // By default, every point of a group triggers a translation.
    869      *  // There may be situations, when this is not wanted.
    870      *
    871      *  // In this example, E triggers nothing, but itself is rotation center
    872      *  // and is translated, if other points are moved around.
    873      *
    874      *  var p, q, col, pol, g;
    875      *  col = 'blue';
    876      *  p = [];
    877      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    878      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'}));
    879      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    880      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    881      *  q = board.create('point',[0, 0], {size: 5, strokeColor:col, fillColor:col});
    882      *
    883      *  pol = board.create('polygon', p, {hasInnerPoints: true});
    884      *  g = board.create('group', p.concat(q)).setRotationCenter('centroid').setRotationPoints([p[2]]);
    885      *  g.setScaleCenter(p[0]).setScalePoints(p[1]);
    886      *  g.removeTranslationPoint(q);
    887      *
    888      * </pre><div class="jxgbox" id="d19b800a-57a9-4303-b49a-8f5b7a5488f0" style="width: 400px; height: 300px;"></div>
    889      * <script type="text/javascript">
    890      *  (function () {
    891      *  var board, p, q, col, pol, g;
    892      *  board = JXG.JSXGraph.initBoard('d19b800a-57a9-4303-b49a-8f5b7a5488f0', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
    893      *  col = 'blue';
    894      *  p = [];
    895      *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
    896      *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'}));
    897      *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
    898      *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
    899      *  q = board.create('point',[0, 0], {size: 5, strokeColor:col, fillColor:col});
    900      *
    901      *  pol = board.create('polygon', p, {hasInnerPoints: true});
    902      *  g = board.create('group', p.concat(q)).setRotationCenter('centroid').setRotationPoints([p[2]]);
    903      *  g.setScaleCenter(p[0]).setScalePoints(p[1]);
    904      *  g.removeTranslationPoint(q);
    905      *  })();
    906      * </script><pre>
    907      *
    908      *
    909      */
    910     JXG.createGroup = function (board, parents, attributes) {
    911         var attr = Type.copyAttributes(attributes, board.options, 'group'),
    912             g = new JXG.Group(board, attr.id, attr.name, parents, attr);
    913
    914         g.elType = 'group';
    915         g.setParents(parents);
    916
    917         return g;
    918     };
    919
    920     JXG.registerElement('group', JXG.createGroup);
    921
    922     return {
    923         Group: JXG.Group,
    924         createGroup: JXG.createGroup
    925     };
    926 });
    927