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