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