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