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