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