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