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