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