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