1 /* 2 Copyright 2008-2025 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 (!obj.evalVisProp('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 i, drag, el, 228 actionCenter, 229 desc, 230 s, sx, sy, 231 alpha, 232 t, T, 233 center, 234 obj = null; 235 236 if (!this.needsUpdate) { 237 return this; 238 } 239 240 drag = this._update_find_drag_type(); 241 if (drag.action === "nothing") { 242 this._updateCoordsCache(drag.id); 243 return this; 244 } 245 246 obj = this.objects[drag.id].point; 247 248 // Prepare translation, scaling or rotation. 249 // Scaling and rotation is handled by transformations for all elements. 250 // Translation is handled by direct coordinate manipulation for points. 251 // For images and texts, all translation, scaling and rotation is 252 // done by binding a transformation to the element. 253 if (drag.action === "translation") { 254 t = [ 255 obj.coords.usrCoords[1] - this.coords[drag.id].usrCoords[1], 256 obj.coords.usrCoords[2] - this.coords[drag.id].usrCoords[2] 257 ]; 258 259 if (obj.elementClass !== Const.OBJECT_CLASS_POINT) { 260 // For images and texts we have to update the drag direction 261 // by reapplying all transformations. 262 263 t.unshift(0); 264 for (i = 0; i < obj.transformations.length; i++) { 265 t = Mat.matVecMult(obj.transformations[i].matrix, t); 266 } 267 t.shift(); 268 } 269 270 // For images and texts 271 T = this.board.create("transform", t, { type: "translate" }); 272 T.update(); 273 } else if (drag.action === "rotation" || drag.action === "scaling") { 274 if (drag.action === "rotation") { 275 actionCenter = "rotationCenter"; 276 } else { 277 actionCenter = "scaleCenter"; 278 } 279 280 // if (Type.isPoint(this.board, this[actionCenter])) { 281 if (Type.exists(this[actionCenter].coords)) { 282 center = this[actionCenter].coords.usrCoords.slice(1); 283 } else if (this[actionCenter] === "centroid") { 284 center = this._update_centroid_center(); 285 } else if (Type.isArray(this[actionCenter])) { 286 center = this[actionCenter]; 287 } else if (Type.isFunction(this[actionCenter])) { 288 center = this[actionCenter](); 289 } else { 290 // No valid center for this transformation, get out of here. 291 JXG.debug('Group.update: No valid center for this transformation, get out of here.'); 292 return this; 293 } 294 295 if (drag.action === "rotation") { 296 alpha = Geometry.rad( 297 this.coords[drag.id].usrCoords.slice(1), 298 center, 299 this.objects[drag.id].point 300 ); 301 t = this.board.create("transform", [alpha, center[0], center[1]], { 302 type: "rotate" 303 }); 304 t.update(); // t.update initializes t.matrix, which is needed if the action element is the first group element. 305 } else if (drag.action === "scaling") { 306 s = Geometry.distance(this.coords[drag.id].usrCoords.slice(1), center); 307 if (Math.abs(s) < Mat.eps) { 308 return this; 309 } 310 s = Geometry.distance(obj.coords.usrCoords.slice(1), center) / s; 311 sx = this.scaleDirections[drag.id].indexOf("x") >= 0 ? s : 1.0; 312 sy = this.scaleDirections[drag.id].indexOf("y") >= 0 ? s : 1.0; 313 314 // Shift scale center to origin, scale and shift the scale center back. 315 t = this.board.create( 316 "transform", 317 [1, 0, 0, center[0] * (1 - sx), sx, 0, center[1] * (1 - sy), 0, sy], 318 { type: "generic" } 319 ); 320 t.update(); // This initializes t.matrix, which is needed if the action element is the first group element. 321 } else { 322 // This should not be reached 323 return this; 324 } 325 } 326 327 // Bind the transformation to any images and texts 328 for (el in this.objects) { 329 obj = this.objects[el].point; 330 if (obj.elementClass !== Const.OBJECT_CLASS_POINT) { 331 if (Type.exists(t.board)) { 332 // t itself is a transformation 333 t.meltTo(obj); 334 } else { 335 // Drag element is a point, therefore 336 // t is an array and we have to use the transformation T. 337 if (drag.id !== obj.id) { 338 T.meltTo(obj); 339 } 340 } 341 } 342 } 343 344 this._update_apply_transformation(drag, t); 345 346 this.needsUpdate = false; // This is needed here to prevent infinite recursion because 347 // of the board.updateElements call below, 348 349 // Prepare dependent objects for update 350 for (el in this.objects) { 351 if (this.objects.hasOwnProperty(el)) { 352 for (desc in this.objects[el].descendants) { 353 if (this.objects[el].descendants.hasOwnProperty(desc)) { 354 this.objects[el].descendants.needsUpdate = 355 this.objects[el].descendants.needsRegularUpdate || 356 this.board.needsFullUpdate; 357 } 358 } 359 } 360 } 361 this.board.updateElements(drag); 362 363 // Now, all group elements have their new position and 364 // we can update the bookkeeping of the coordinates of the group elements. 365 for (el in this.objects) { 366 if (this.objects.hasOwnProperty(el)) { 367 this._updateCoordsCache(el); 368 } 369 } 370 371 return this; 372 }, 373 374 /** 375 * @private 376 */ 377 // Determine what the dragging of a group element should do: 378 // rotation, translation, scaling or nothing. 379 _update_find_drag_type: function () { 380 var el, 381 obj, 382 action = "nothing", 383 changed = [], 384 dragObjId; 385 386 // Determine how many elements have changed their position 387 // If more than one element changed its position, it is a translation. 388 // If exactly one element changed its position we have to find the type of the point. 389 for (el in this.objects) { 390 if (this.objects.hasOwnProperty(el)) { 391 obj = this.objects[el].point; 392 393 if (obj.coords.distance(Const.COORDS_BY_USER, this.coords[el]) > Mat.eps) { 394 changed.push(obj.id); 395 } 396 } 397 } 398 399 // Determine type of action: translation, scaling or rotation 400 if (changed.length === 0) { 401 return { 402 action: action, 403 id: "", 404 changed: changed 405 }; 406 } 407 408 dragObjId = changed[0]; 409 obj = this.objects[dragObjId].point; 410 411 if (changed.length > 1) { 412 // More than one point moved => translation 413 action = "translation"; 414 } else { 415 // One point moved => we have to determine the type 416 if ( 417 Type.isInArray(this.rotationPoints, obj) && 418 Type.exists(this.rotationCenter) 419 ) { 420 action = "rotation"; 421 } else if ( 422 Type.isInArray(this.scalePoints, obj) && 423 Type.exists(this.scaleCenter) 424 ) { 425 action = "scaling"; 426 } else if (Type.isInArray(this.translationPoints, obj)) { 427 action = "translation"; 428 } 429 } 430 431 return { 432 action: action, 433 id: dragObjId, 434 changed: changed 435 }; 436 }, 437 438 /** 439 * Determine the Euclidean (affine) coordinates of the centroid of the group. 440 * @private 441 * @returns {Array} array of length two, 442 */ 443 _update_centroid_center: function () { 444 var center, len, el; 445 446 center = [0, 0]; 447 len = 0; 448 for (el in this.coords) { 449 if (this.coords.hasOwnProperty(el)) { 450 center[0] += this.coords[el].usrCoords[1]; 451 center[1] += this.coords[el].usrCoords[2]; 452 ++len; 453 } 454 } 455 if (len > 0) { 456 center[0] /= len; 457 center[1] /= len; 458 } 459 460 return center; 461 }, 462 463 /** 464 * @private 465 */ 466 // Apply the transformation to all elements of the group 467 _update_apply_transformation: function (drag, t) { 468 var el, obj; 469 470 for (el in this.objects) { 471 if (this.objects.hasOwnProperty(el)) { 472 if (Type.exists(this.board.objects[el])) { 473 obj = this.objects[el].point; 474 475 // Here, it is important that we change the position 476 // of elements by using setCoordinates. 477 // Thus, we avoid the call of snapToGrid(). 478 // This is done in the subsequent call of board.updateElements() 479 // in Group.update() above. 480 if (obj.id !== drag.id) { 481 if (drag.action === "translation") { 482 if (!Type.isInArray(drag.changed, obj.id)) { 483 if (obj.elementClass === Const.OBJECT_CLASS_POINT) { 484 obj.coords.setCoordinates(Const.COORDS_BY_USER, [ 485 this.coords[el].usrCoords[1] + t[0], 486 this.coords[el].usrCoords[2] + t[1] 487 ]); 488 } 489 } 490 } else if ( 491 drag.action === "rotation" || 492 drag.action === "scaling" 493 ) { 494 if (obj.elementClass === Const.OBJECT_CLASS_POINT) { 495 t.applyOnce([obj]); 496 } 497 } 498 } else { 499 if (drag.action === "rotation" || drag.action === "scaling") { 500 obj.coords.setCoordinates( 501 Const.COORDS_BY_USER, 502 Mat.matVecMult(t.matrix, this.coords[obj.id].usrCoords) 503 ); 504 } 505 } 506 } else { 507 delete this.objects[el]; 508 } 509 } 510 } 511 }, 512 513 /** 514 * Adds an Point to this group. 515 * @param {JXG.Point} object The point added to the group. 516 * @returns {JXG.Group} returns this group 517 */ 518 addPoint: function (object) { 519 this.objects[object.id] = { point: this.board.select(object) }; 520 this._updateCoordsCache(object.id); 521 this.translationPoints.push(object); 522 523 object.groups.push(this.id); 524 object.groups = Type.uniqueArray(object.groups); 525 526 return this; 527 }, 528 529 /** 530 * Adds multiple points to this group. 531 * @param {Array} objects An array of points to add to the group. 532 * @returns {JXG.Group} returns this group 533 */ 534 addPoints: function (objects) { 535 var p; 536 537 for (p = 0; p < objects.length; p++) { 538 this.addPoint(objects[p]); 539 } 540 541 return this; 542 }, 543 544 /** 545 * Adds all points in a group to this group. 546 * @param {JXG.Group} group The group added to this group. 547 * @returns {JXG.Group} returns this group 548 */ 549 addGroup: function (group) { 550 var el; 551 552 for (el in group.objects) { 553 if (group.objects.hasOwnProperty(el)) { 554 this.addPoint(group.objects[el].point); 555 } 556 } 557 558 return this; 559 }, 560 561 /** 562 * Removes a point from the group. 563 * @param {JXG.Point} point 564 * @returns {JXG.Group} returns this group 565 */ 566 removePoint: function (point) { 567 delete this.objects[point.id]; 568 569 return this; 570 }, 571 572 /** 573 * Sets the center of rotation for the group. This is either a point or the centroid of the group. 574 * @param {JXG.Point|String|Array|Function} object A point which will be the center of rotation, the string "centroid", or 575 * an array of length two, or a function returning an array of length two. 576 * @default 'centroid' 577 * @returns {JXG.Group} returns this group 578 */ 579 setRotationCenter: function (object) { 580 this.rotationCenter = object; 581 582 return this; 583 }, 584 585 /** 586 * Sets the rotation points of the group. Dragging at one of these points results into a rotation of the whole group around 587 * the rotation center of the group {@see JXG.Group#setRotationCenter}. 588 * @param {Array|JXG.Point} objects Array of {@link JXG.Point} or arbitrary number of {@link JXG.Point} elements. 589 * @returns {JXG.Group} returns this group 590 */ 591 setRotationPoints: function (objects) { 592 return this._setActionPoints("rotation", objects); 593 }, 594 595 /** 596 * 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 597 * the rotation center of the group {@see JXG.Group#setRotationCenter}. 598 * @param {JXG.Point} point {@link JXG.Point} element. 599 * @returns {JXG.Group} returns this group 600 */ 601 addRotationPoint: function (point) { 602 return this._addActionPoint("rotation", point); 603 }, 604 605 /** 606 * Removes the rotation property from a point of the group. 607 * @param {JXG.Point} point {@link JXG.Point} element. 608 * @returns {JXG.Group} returns this group 609 */ 610 removeRotationPoint: function (point) { 611 return this._removeActionPoint("rotation", point); 612 }, 613 614 /** 615 * Sets the translation points of the group. Dragging at one of these points results into a translation of the whole group. 616 * @param {Array|JXG.Point} objects Array of {@link JXG.Point} or arbitrary number of {@link JXG.Point} elements. 617 * 618 * By default, all points of the group are translation points. 619 * @returns {JXG.Group} returns this group 620 */ 621 setTranslationPoints: function (objects) { 622 return this._setActionPoints("translation", objects); 623 }, 624 625 /** 626 * Adds a point to the set of the translation points of the group. 627 * Dragging one of these points results into a translation of the whole group. 628 * @param {JXG.Point} point {@link JXG.Point} element. 629 * @returns {JXG.Group} returns this group 630 */ 631 addTranslationPoint: function (point) { 632 return this._addActionPoint("translation", point); 633 }, 634 635 /** 636 * Removes the translation property from a point of the group. 637 * @param {JXG.Point} point {@link JXG.Point} element. 638 * @returns {JXG.Group} returns this group 639 */ 640 removeTranslationPoint: function (point) { 641 return this._removeActionPoint("translation", point); 642 }, 643 644 /** 645 * Sets the center of scaling for the group. This is either a point or the centroid of the group. 646 * @param {JXG.Point|String} object A point which will be the center of scaling, the string "centroid", or 647 * an array of length two, or a function returning an array of length two. 648 * @returns {JXG.Group} returns this group 649 */ 650 setScaleCenter: function (object) { 651 this.scaleCenter = object; 652 653 return this; 654 }, 655 656 /** 657 * Sets the scale points of the group. Dragging at one of these points results into a scaling of the whole group. 658 * @param {Array|JXG.Point} objects Array of {@link JXG.Point} or arbitrary number of {@link JXG.Point} elements. 659 * @param {String} direction Restricts the directions to be scaled. Possible values are 'x', 'y', 'xy'. Default value is 'xy'. 660 * 661 * By default, all points of the group are translation points. 662 * @returns {JXG.Group} returns this group 663 */ 664 setScalePoints: function (objects, direction) { 665 var objs, i, len; 666 if (Type.isArray(objects)) { 667 objs = objects; 668 } else { 669 objs = arguments; 670 } 671 672 len = objs.length; 673 for (i = 0; i < len; ++i) { 674 this.scaleDirections[this.board.select(objs[i]).id] = direction || "xy"; 675 } 676 677 return this._setActionPoints("scale", objects); 678 }, 679 680 /** 681 * 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. 682 * @param {JXG.Point} point {@link JXG.Point} element. 683 * @param {String} direction Restricts the directions to be scaled. Possible values are 'x', 'y', 'xy'. Default value is 'xy'. 684 * @returns {JXG.Group} returns this group 685 */ 686 addScalePoint: function (point, direction) { 687 this._addActionPoint("scale", point); 688 this.scaleDirections[this.board.select(point).id] = direction || "xy"; 689 690 return this; 691 }, 692 693 /** 694 * Removes the scaling property from a point of the group. 695 * @param {JXG.Point} point {@link JXG.Point} element. 696 * @returns {JXG.Group} returns this group 697 */ 698 removeScalePoint: function (point) { 699 return this._removeActionPoint("scale", point); 700 }, 701 702 /** 703 * Generic method for {@link JXG.Group@setTranslationPoints} and {@link JXG.Group@setRotationPoints} 704 * @private 705 */ 706 _setActionPoints: function (action, objects) { 707 var objs, i, len; 708 if (Type.isArray(objects)) { 709 objs = objects; 710 } else { 711 objs = arguments; 712 } 713 714 len = objs.length; 715 this[action + "Points"] = []; 716 for (i = 0; i < len; ++i) { 717 this._addActionPoint(action, objs[i]); 718 } 719 720 return this; 721 }, 722 723 /** 724 * Generic method for {@link JXG.Group@addTranslationPoint} and {@link JXG.Group@addRotationPoint} 725 * @private 726 */ 727 _addActionPoint: function (action, point) { 728 this[action + "Points"].push(this.board.select(point)); 729 730 return this; 731 }, 732 733 /** 734 * Generic method for {@link JXG.Group@removeTranslationPoint} and {@link JXG.Group@removeRotationPoint} 735 * @private 736 */ 737 _removeActionPoint: function (action, point) { 738 var idx = this[action + "Points"].indexOf(this.board.select(point)); 739 if (idx > -1) { 740 this[action + "Points"].splice(idx, 1); 741 } 742 743 return this; 744 }, 745 746 /** 747 * @deprecated 748 * Use setAttribute 749 */ 750 setProperty: function () { 751 JXG.deprecated("Group.setProperty", "Group.setAttribute()"); 752 this.setAttribute.apply(this, arguments); 753 }, 754 755 setAttribute: function () { 756 var el; 757 758 for (el in this.objects) { 759 if (this.objects.hasOwnProperty(el)) { 760 this.objects[el].point.setAttribute.apply( 761 this.objects[el].point, 762 arguments 763 ); 764 } 765 } 766 767 return this; 768 } 769 } 770 ); 771 772 /** 773 * @class A container element to control the movement of given set of point, image or text elements simultaneously. 774 * The elements of the group and dependent elements can be translated, rotated and scaled by 775 * dragging one of the group elements. 776 * 777 * @pseudo 778 * @name Group 779 * @augments JXG.Group 780 * @constructor 781 * @type JXG.Group 782 * @param {JXG.Board} board The board the points are on. 783 * @param {Array} parents Array of points to group. 784 * @param {Object} attributes Visual properties (unused). 785 * @returns {JXG.Group} 786 * 787 * @example 788 * 789 * // Create some free points. e.g. A, B, C, D 790 * // Create a group 791 * 792 * var p, col, g; 793 * col = 'blue'; 794 * p = []; 795 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 796 * p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 797 * p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:col, fillColor:col})); 798 * p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col})); 799 * g = board.create('group', p); 800 * 801 * </pre><div class="jxgbox" id="JXGa2204533-db91-4af9-b720-70394de4d367" style="width: 400px; height: 300px;"></div> 802 * <script type="text/javascript"> 803 * (function () { 804 * var board, p, col, g; 805 * board = JXG.JSXGraph.initBoard('JXGa2204533-db91-4af9-b720-70394de4d367', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false}); 806 * col = 'blue'; 807 * p = []; 808 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 809 * p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 810 * p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:col, fillColor:col})); 811 * p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col})); 812 * g = board.create('group', p); 813 * })(); 814 * </script><pre> 815 * 816 * 817 * @example 818 * 819 * // Create some free points. e.g. A, B, C, D 820 * // Create a group 821 * // If the points define a polygon and the polygon has the attribute hasInnerPoints:true, 822 * // the polygon can be dragged around. 823 * 824 * var p, col, pol, g; 825 * col = 'blue'; 826 * p = []; 827 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 828 * p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 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:col, fillColor:col})); 831 * 832 * pol = board.create('polygon', p, {hasInnerPoints: true}); 833 * g = board.create('group', p); 834 * 835 * </pre><div class="jxgbox" id="JXG781b5564-a671-4327-81c6-de915c8f924e" style="width: 400px; height: 300px;"></div> 836 * <script type="text/javascript"> 837 * (function () { 838 * var board, p, col, pol, g; 839 * board = JXG.JSXGraph.initBoard('JXG781b5564-a671-4327-81c6-de915c8f924e', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false}); 840 * col = 'blue'; 841 * p = []; 842 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 843 * p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 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:col, fillColor:col})); 846 * pol = board.create('polygon', p, {hasInnerPoints: true}); 847 * g = board.create('group', p); 848 * })(); 849 * </script><pre> 850 * 851 * @example 852 * 853 * // Allow rotations: 854 * // Define a center of rotation and declare points of the group as "rotation points". 855 * 856 * var p, col, pol, g; 857 * col = 'blue'; 858 * p = []; 859 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 860 * p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'red', fillColor:'red'})); 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:col, fillColor:col})); 863 * 864 * pol = board.create('polygon', p, {hasInnerPoints: true}); 865 * g = board.create('group', p); 866 * g.setRotationCenter(p[0]); 867 * g.setRotationPoints([p[1], p[2]]); 868 * 869 * </pre><div class="jxgbox" id="JXGf0491b62-b377-42cb-b55c-4ef5374b39fc" style="width: 400px; height: 300px;"></div> 870 * <script type="text/javascript"> 871 * (function () { 872 * var board, p, col, pol, g; 873 * board = JXG.JSXGraph.initBoard('JXGf0491b62-b377-42cb-b55c-4ef5374b39fc', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false}); 874 * col = 'blue'; 875 * p = []; 876 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 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:'red', fillColor:'red'})); 879 * p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col})); 880 * pol = board.create('polygon', p, {hasInnerPoints: true}); 881 * g = board.create('group', p); 882 * g.setRotationCenter(p[0]); 883 * g.setRotationPoints([p[1], p[2]]); 884 * })(); 885 * </script><pre> 886 * 887 * @example 888 * 889 * // Allow rotations: 890 * // As rotation center, arbitrary points, coordinate arrays, 891 * // or functions returning coordinate arrays can be given. 892 * // Another possibility is to use the predefined string 'centroid'. 893 * 894 * // The methods to define the rotation points can be chained. 895 * 896 * var p, col, pol, g; 897 * col = 'blue'; 898 * p = []; 899 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 900 * p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'red', fillColor:'red'})); 901 * p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'})); 902 * p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col})); 903 * 904 * pol = board.create('polygon', p, {hasInnerPoints: true}); 905 * g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[1], p[2]]); 906 * 907 * </pre><div class="jxgbox" id="JXG8785b099-a75e-4769-bfd8-47dd4376fe27" style="width: 400px; height: 300px;"></div> 908 * <script type="text/javascript"> 909 * (function () { 910 * var board, p, col, pol, g; 911 * board = JXG.JSXGraph.initBoard('JXG8785b099-a75e-4769-bfd8-47dd4376fe27', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false}); 912 * col = 'blue'; 913 * p = []; 914 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 915 * p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'red', fillColor:'red'})); 916 * p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'})); 917 * p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col})); 918 * pol = board.create('polygon', p, {hasInnerPoints: true}); 919 * g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[1], p[2]]); 920 * })(); 921 * </script><pre> 922 * 923 * @example 924 * 925 * // Allow scaling: 926 * // As for rotation one can declare points of the group to trigger a scaling operation. 927 * // For this, one has to define a scaleCenter, in analogy to rotations. 928 * 929 * // Here, the yellow point enables scaling, the red point a rotation. 930 * 931 * var p, col, pol, g; 932 * col = 'blue'; 933 * p = []; 934 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 935 * p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'})); 936 * p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'})); 937 * p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col})); 938 * 939 * pol = board.create('polygon', p, {hasInnerPoints: true}); 940 * g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[2]]); 941 * g.setScaleCenter(p[0]).setScalePoints(p[1]); 942 * 943 * </pre><div class="jxgbox" id="JXGc3ca436b-e4fc-4de5-bab4-09790140c675" style="width: 400px; height: 300px;"></div> 944 * <script type="text/javascript"> 945 * (function () { 946 * var board, p, col, pol, g; 947 * board = JXG.JSXGraph.initBoard('JXGc3ca436b-e4fc-4de5-bab4-09790140c675', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false}); 948 * col = 'blue'; 949 * p = []; 950 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 951 * p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'})); 952 * p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'})); 953 * p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col})); 954 * pol = board.create('polygon', p, {hasInnerPoints: true}); 955 * g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[2]]); 956 * g.setScaleCenter(p[0]).setScalePoints(p[1]); 957 * })(); 958 * </script><pre> 959 * 960 * @example 961 * 962 * // Allow Translations: 963 * // By default, every point of a group triggers a translation. 964 * // There may be situations, when this is not wanted. 965 * 966 * // In this example, E triggers nothing, but itself is rotation center 967 * // and is translated, if other points are moved around. 968 * 969 * var p, q, col, pol, g; 970 * col = 'blue'; 971 * p = []; 972 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 973 * p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'})); 974 * p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'})); 975 * p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col})); 976 * q = board.create('point',[0, 0], {size: 5, strokeColor:col, fillColor:col}); 977 * 978 * pol = board.create('polygon', p, {hasInnerPoints: true}); 979 * g = board.create('group', p.concat(q)).setRotationCenter('centroid').setRotationPoints([p[2]]); 980 * g.setScaleCenter(p[0]).setScalePoints(p[1]); 981 * g.removeTranslationPoint(q); 982 * 983 * </pre><div class="jxgbox" id="JXGd19b800a-57a9-4303-b49a-8f5b7a5488f0" style="width: 400px; height: 300px;"></div> 984 * <script type="text/javascript"> 985 * (function () { 986 * var board, p, q, col, pol, g; 987 * board = JXG.JSXGraph.initBoard('JXGd19b800a-57a9-4303-b49a-8f5b7a5488f0', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false}); 988 * col = 'blue'; 989 * p = []; 990 * p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col})); 991 * p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'})); 992 * p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'})); 993 * p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col})); 994 * q = board.create('point',[0, 0], {size: 5, strokeColor:col, fillColor:col}); 995 * 996 * pol = board.create('polygon', p, {hasInnerPoints: true}); 997 * g = board.create('group', p.concat(q)).setRotationCenter('centroid').setRotationPoints([p[2]]); 998 * g.setScaleCenter(p[0]).setScalePoints(p[1]); 999 * g.removeTranslationPoint(q); 1000 * })(); 1001 * </script><pre> 1002 * 1003 * @example 1004 * 1005 * // Add an image and use the group tools to manipulate it 1006 * let urlImg = "https://jsxgraph.org/distrib/images/uccellino.jpg"; 1007 * let lowleft = [-2, -1] 1008 * 1009 * let col = 'blue'; 1010 * let p = []; 1011 * p.push(board.create('point', lowleft, { size: 5, strokeColor: col, fillColor: col })); 1012 * p.push(board.create('point', [2, -1], { size: 5, strokeColor: 'yellow', fillColor: 'yellow', name: 'scale' })); 1013 * p.push(board.create('point', [2, 1], { size: 5, strokeColor: 'red', fillColor: 'red', name: 'rotate' })); 1014 * p.push(board.create('point', [-2, 1], { size: 5, strokeColor: col, fillColor: col, name: 'translate' })); 1015 * 1016 * let im = board.create('image', [urlImg, lowleft, [2, 2]]); 1017 * let pol = board.create('polygon', p, { hasInnerPoints: true }); 1018 * 1019 * let g = board.create('group', p.concat(im)) 1020 * // g.addPoint(im) // image, but adds as a point 1021 * 1022 * g.setRotationCenter(lowleft) 1023 * g.setRotationPoints([p[2]]); 1024 * 1025 * g.setScaleCenter(p[0]).setScalePoints(p[1]); 1026 * 1027 * </pre><div class="jxgbox" id="JXGd19b800a-57a9-4303-b49a-8f5b7a5489f1" style="width: 400px; height: 300px;"></div> 1028 * <script type="text/javascript"> 1029 * (function () { 1030 * let board = JXG.JSXGraph.initBoard('JXGd19b800a-57a9-4303-b49a-8f5b7a5489f1') 1031 * 1032 * // Add an image and use the group tools to manipulate it 1033 * let urlImg = "https://jsxgraph.org/distrib/images/uccellino.jpg"; 1034 * let lowleft = [-2, -1] 1035 * 1036 * let col = 'blue'; 1037 * let p = []; 1038 * p.push(board.create('point', lowleft, { size: 5, strokeColor: col, fillColor: col })); 1039 * p.push(board.create('point', [2, -1], { size: 5, strokeColor: 'yellow', fillColor: 'yellow', name: 'scale' })); 1040 * p.push(board.create('point', [2, 1], { size: 5, strokeColor: 'red', fillColor: 'red', name: 'rotate' })); 1041 * p.push(board.create('point', [-2, 1], { size: 5, strokeColor: col, fillColor: col, name: 'translate' })); 1042 * 1043 * let im = board.create('image', [urlImg, lowleft, [2, 2]]); 1044 * let pol = board.create('polygon', p, { hasInnerPoints: true }); 1045 * 1046 * let g = board.create('group', p.concat(im)) 1047 * // g.addPoint(im) // image, but adds as a point 1048 * 1049 * g.setRotationCenter(lowleft) 1050 * g.setRotationPoints([p[2]]); 1051 * 1052 * g.setScaleCenter(p[0]).setScalePoints(p[1]); 1053 * })(); 1054 * </script><pre> 1055 */ 1056 JXG.createGroup = function (board, parents, attributes) { 1057 var attr = Type.copyAttributes(attributes, board.options, "group"), 1058 g = new JXG.Group(board, attr.id, attr.name, parents, attr); 1059 1060 g.elType = "group"; 1061 g.setParents(parents); 1062 1063 return g; 1064 }; 1065 1066 JXG.registerElement("group", JXG.createGroup); 1067 1068 export default JXG.Group; 1069 // export default { 1070 // Group: JXG.Group, 1071 // createGroup: JXG.createGroup 1072 // }; 1073