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