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 This file contains code for transformations of geometrical objects. 37 */ 38 39 import JXG from "../jxg.js"; 40 import Const from "./constants.js"; 41 import Mat from "../math/math.js"; 42 import Type from "../utils/type.js"; 43 44 /** 45 * A transformation consists of a 3x3 matrix, i.e. it is a projective transformation. 46 * @class Creates a new transformation object. Do not use this constructor to create a transformation. 47 * Use {@link JXG.Board#create} with 48 * type {@link Transformation} instead. 49 * @constructor 50 * @param {JXG.Board} board The board the new circle is drawn on. 51 * @param {String} type Can be 52 * <ul><li> 'translate' 53 * <li> 'scale' 54 * <li> 'reflect' 55 * <li> 'rotate' 56 * <li> 'shear' 57 * <li> 'generic' 58 * </ul> 59 * @param {Object} params The parameters depend on the transformation type 60 * 61 * <p> 62 * Translation matrix: 63 * <pre> 64 * ( 1 0 0) ( z ) 65 * ( a 1 0) * ( x ) 66 * ( b 0 1) ( y ) 67 * </pre> 68 * 69 * <p> 70 * Scale matrix: 71 * <pre> 72 * ( 1 0 0) ( z ) 73 * ( 0 a 0) * ( x ) 74 * ( 0 0 b) ( y ) 75 * </pre> 76 * 77 * <p> 78 * A rotation matrix with angle a (in Radians) 79 * <pre> 80 * ( 1 0 0 ) ( z ) 81 * ( 0 cos(a) -sin(a)) * ( x ) 82 * ( 0 sin(a) cos(a) ) ( y ) 83 * </pre> 84 * 85 * <p> 86 * Shear matrix: 87 * <pre> 88 * ( 1 0 0) ( z ) 89 * ( 0 1 a) * ( x ) 90 * ( 0 b 1) ( y ) 91 * </pre> 92 * 93 * <p>Generic transformation: 94 * <pre> 95 * ( a b c ) ( z ) 96 * ( d e f ) * ( x ) 97 * ( g h i ) ( y ) 98 * </pre> 99 * 100 */ 101 JXG.Transformation = function (board, type, params) { 102 this.elementClass = Const.OBJECT_CLASS_OTHER; 103 this.type = Const.OBJECT_TYPE_TRANSFORMATION; 104 this.matrix = [ 105 [1, 0, 0], 106 [0, 1, 0], 107 [0, 0, 1] 108 ]; 109 this.board = board; 110 this.isNumericMatrix = false; 111 this.setMatrix(board, type, params); 112 113 this.methodMap = { 114 apply: "apply", 115 applyOnce: "applyOnce", 116 bindTo: "bindTo", 117 bind: "bindTo", 118 melt: "melt", 119 meltTo: "meltTo" 120 }; 121 }; 122 123 JXG.Transformation.prototype = {}; 124 125 JXG.extend( 126 JXG.Transformation.prototype, 127 /** @lends JXG.Transformation.prototype */ { 128 /** 129 * Updates the numerical data for the transformation, i.e. the entry of the subobject matrix. 130 * @returns {JXG.Transform} returns pointer to itself 131 */ 132 update: function () { 133 return this; 134 }, 135 136 /** 137 * Set the transformation matrix for different types of standard transforms. 138 * @param {JXG.Board} board 139 * @param {String} type Transformation type, possible values are 140 * 'translate', 'scale', 'reflect', 'rotate', 141 * 'shear', 'generic'. 142 * @param {Array} params Parameters for the various transformation types. 143 * 144 * <p>These are 145 * @param {Array} x,y Shift vector (number or function) in case of 'translate'. 146 * @param {Array} scale_x,scale_y Scale vector (number or function) in case of 'scale'. 147 * @param {Array} line|point_pair|"four coordinates" In case of 'reflect' the parameters could 148 * be a line, a pair of points or four number (or functions) p_x, p_y, q_x, q_y, 149 * determining a line through points (p_x, p_y) and (q_x, q_y). 150 * @param {Array} angle,x,y|angle,[x,y] In case of 'rotate' the parameters are an angle or angle function, 151 * returning the angle in Radians and - optionally - a coordinate pair or a point defining the 152 * rotation center. If the rotation center is not given, the transformation rotates around (0,0). 153 * @param {Array} shear_x,shear_y Shear vector (number or function) in case of 'shear'. 154 * @param {Array} a,b,c,d,e,f,g,h,i Nine matrix entries (numbers or functions) for a generic 155 * projective transformation in case of 'generic'. 156 * 157 * <p>A transformation with a generic matrix looks like: 158 * <pre> 159 * ( a b c ) ( z ) 160 * ( d e f ) * ( x ) 161 * ( g h i ) ( y ) 162 * </pre> 163 * 164 */ 165 setMatrix: function (board, type, params) { 166 var i; 167 // e, obj; // Handle dependencies 168 169 this.isNumericMatrix = true; 170 171 for (i = 0; i < params.length; i++) { 172 if (typeof params[i] !== "number") { 173 this.isNumericMatrix = false; 174 break; 175 } 176 } 177 178 if (type === "translate") { 179 if (params.length !== 2) { 180 throw new Error("JSXGraph: translate transformation needs 2 parameters."); 181 } 182 this.evalParam = Type.createEvalFunction(board, params, 2); 183 this.update = function () { 184 this.matrix[1][0] = this.evalParam(0); 185 this.matrix[2][0] = this.evalParam(1); 186 }; 187 } else if (type === "scale") { 188 if (params.length !== 2) { 189 throw new Error("JSXGraph: scale transformation needs 2 parameters."); 190 } 191 this.evalParam = Type.createEvalFunction(board, params, 2); 192 this.update = function () { 193 this.matrix[1][1] = this.evalParam(0); // x 194 this.matrix[2][2] = this.evalParam(1); // y 195 }; 196 // Input: line or two points 197 } else if (type === "reflect") { 198 // line or two points 199 if (params.length < 4) { 200 params[0] = board.select(params[0]); 201 } 202 203 // two points 204 if (params.length === 2) { 205 params[1] = board.select(params[1]); 206 } 207 208 // 4 coordinates [px,py,qx,qy] 209 if (params.length === 4) { 210 this.evalParam = Type.createEvalFunction(board, params, 4); 211 } 212 213 this.update = function () { 214 var x, y, z, xoff, yoff, d, v, p; 215 // Determine homogeneous coordinates of reflections axis 216 // line 217 if (params.length === 1) { 218 v = params[0].stdform; 219 } else if (params.length === 2) { 220 // two points 221 v = Mat.crossProduct( 222 params[1].coords.usrCoords, 223 params[0].coords.usrCoords 224 ); 225 } else if (params.length === 4) { 226 // two points coordinates [px,py,qx,qy] 227 v = Mat.crossProduct( 228 [1, this.evalParam(2), this.evalParam(3)], 229 [1, this.evalParam(0), this.evalParam(1)] 230 ); 231 } 232 233 // Project origin to the line. This gives a finite point p 234 x = v[1]; 235 y = v[2]; 236 z = v[0]; 237 p = [-z * x, -z * y, x * x + y * y]; 238 d = p[2]; 239 240 // Normalize p 241 xoff = p[0] / p[2]; 242 yoff = p[1] / p[2]; 243 244 // x, y is the direction of the line 245 x = -v[2]; 246 y = v[1]; 247 248 this.matrix[1][1] = (x * x - y * y) / d; 249 this.matrix[1][2] = (2 * x * y) / d; 250 this.matrix[2][1] = this.matrix[1][2]; 251 this.matrix[2][2] = -this.matrix[1][1]; 252 this.matrix[1][0] = 253 xoff * (1 - this.matrix[1][1]) - yoff * this.matrix[1][2]; 254 this.matrix[2][0] = 255 yoff * (1 - this.matrix[2][2]) - xoff * this.matrix[2][1]; 256 }; 257 } else if (type === "rotate") { 258 // angle, x, y 259 if (params.length === 3) { 260 this.evalParam = Type.createEvalFunction(board, params, 3); 261 // angle, p or angle 262 } else if (params.length > 0 && params.length <= 2) { 263 this.evalParam = Type.createEvalFunction(board, params, 1); 264 265 if (params.length === 2 && !Type.isArray(params[1])) { 266 params[1] = board.select(params[1]); 267 } 268 } 269 270 this.update = function () { 271 var x, 272 y, 273 beta = this.evalParam(0), 274 co = Math.cos(beta), 275 si = Math.sin(beta); 276 277 this.matrix[1][1] = co; 278 this.matrix[1][2] = -si; 279 this.matrix[2][1] = si; 280 this.matrix[2][2] = co; 281 282 // rotate around [x,y] otherwise rotate around [0,0] 283 if (params.length > 1) { 284 if (params.length === 3) { 285 x = this.evalParam(1); 286 y = this.evalParam(2); 287 } else { 288 if (Type.isArray(params[1])) { 289 x = params[1][0]; 290 y = params[1][1]; 291 } else { 292 x = params[1].X(); 293 y = params[1].Y(); 294 } 295 } 296 this.matrix[1][0] = x * (1 - co) + y * si; 297 this.matrix[2][0] = y * (1 - co) - x * si; 298 } 299 }; 300 } else if (type === "shear") { 301 if (params.length !== 2) { 302 throw new Error("JSXGraph: shear transformation needs 2 parameters."); 303 } 304 305 this.evalParam = Type.createEvalFunction(board, params, 2); 306 this.update = function () { 307 this.matrix[1][2] = this.evalParam(0); 308 this.matrix[2][1] = this.evalParam(1); 309 }; 310 } else if (type === "generic") { 311 if (params.length !== 9) { 312 throw new Error("JSXGraph: generic transformation needs 9 parameters."); 313 } 314 315 this.evalParam = Type.createEvalFunction(board, params, 9); 316 317 this.update = function () { 318 this.matrix[0][0] = this.evalParam(0); 319 this.matrix[0][1] = this.evalParam(1); 320 this.matrix[0][2] = this.evalParam(2); 321 this.matrix[1][0] = this.evalParam(3); 322 this.matrix[1][1] = this.evalParam(4); 323 this.matrix[1][2] = this.evalParam(5); 324 this.matrix[2][0] = this.evalParam(6); 325 this.matrix[2][1] = this.evalParam(7); 326 this.matrix[2][2] = this.evalParam(8); 327 }; 328 } 329 330 // Handle dependencies 331 // NO: transformations do not have method addParents 332 // if (Type.exists(this.evalParam)) { 333 // for (e in this.evalParam.deps) { 334 // obj = this.evalParam.deps[e]; 335 // this.addParents(obj); 336 // obj.addChild(this); 337 // } 338 // } 339 }, 340 341 /** 342 * Transform a GeometryElement: 343 * First, the transformation matrix is updated, then do the matrix-vector-multiplication. 344 * @private 345 * @param {JXG.GeometryElement} p element which is transformed 346 * @param {String} 'self' Apply the transformation to the initialCoords instead of the coords if this is set. 347 * @returns {Array} 348 */ 349 apply: function (p, self) { 350 this.update(); 351 352 if (Type.exists(self)) { 353 return Mat.matVecMult(this.matrix, p.initialCoords.usrCoords); 354 } 355 return Mat.matVecMult(this.matrix, p.coords.usrCoords); 356 }, 357 358 /** 359 * Applies a transformation once to a GeometryElement or an array of elements. 360 * If it is a free point, then it can be dragged around later 361 * and will overwrite the transformed coordinates. 362 * @param {JXG.Point|Array} p 363 */ 364 applyOnce: function (p) { 365 var c, len, i; 366 367 if (!Type.isArray(p)) { 368 p = [p]; 369 } 370 371 len = p.length; 372 373 for (i = 0; i < len; i++) { 374 this.update(); 375 c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords); 376 p[i].coords.setCoordinates(Const.COORDS_BY_USER, c); 377 } 378 }, 379 380 /** 381 * Binds a transformation to a GeometryElement or an array of elements. In every update of the 382 * GeometryElement(s), the transformation is executed. That means, in order to immediately 383 * apply the transformation after calling bindTo, a call of board.update() has to follow. 384 * <p> 385 * The transformation is simply appended to the existing list of transformations of the object. 386 * It is not fused (melt) with an existing transformation. 387 * 388 * @param {Array|JXG.Object} el JXG.Object or array of JXG.Object to 389 * which the transformation is bound to. 390 * @see JXG.Transformation.meltTo 391 */ 392 bindTo: function (el) { 393 var i, len; 394 if (Type.isArray(el)) { 395 len = el.length; 396 397 for (i = 0; i < len; i++) { 398 el[i].transformations.push(this); 399 } 400 } else { 401 el.transformations.push(this); 402 } 403 }, 404 405 /** 406 * Binds a transformation to a GeometryElement or an array of elements. In every update of the 407 * GeometryElement(s), the transformation is executed. That means, in order to immediately 408 * apply the transformation after calling meltTo, a call of board.update() has to follow. 409 * <p> 410 * In case the last transformation of the element and this transformation are static, 411 * i.e. the transformation matrices do not depend on other elements, 412 * the transformation will be fused into (multiplied with) the last transformation of 413 * the element. Thus, the list of transformations is kept small. 414 * If the transformation will be the first transformation ot the element, it will be cloned 415 * to prevent side effects. 416 * 417 * @param {Array|JXG.Object} el JXG.Object or array of JXG.Object to 418 * which the transformation is bound to. 419 * 420 * @see JXG.Transformation#bindTo 421 */ 422 meltTo: function (el) { 423 var i, elt, t; 424 425 if (Type.isArray(el)) { 426 for (i = 0; i < el.length; i++) { 427 this.meltTo(el[i]); 428 } 429 } else { 430 elt = el.transformations; 431 if (elt.length > 0 && 432 elt[elt.length - 1].isNumericMatrix && 433 this.isNumericMatrix 434 ) { 435 elt[elt.length - 1].melt(this); 436 } else { 437 // Use a clone of the transformation. 438 // Otherwise, if the transformation is meltTo twice 439 // the transformation will be changed. 440 t = this.clone(); 441 elt.push(t); 442 } 443 } 444 }, 445 446 /** 447 * Create a copy of the transformation in case it is static, i.e. 448 * if the transformation matrix does not depend on other elements. 449 * <p> 450 * If the transformation matrix is not static, null will be returned. 451 * 452 * @returns {JXG.Transformation} 453 */ 454 clone: function() { 455 var t = null; 456 457 if (this.isNumericMatrix) { 458 t = new JXG.Transformation(this.board, 'none', []); 459 t.matrix = this.matrix.slice(); 460 } 461 462 return t; 463 }, 464 465 /** 466 * Unused 467 * @deprecated Use setAttribute 468 * @param term 469 */ 470 setProperty: function (term) { 471 JXG.deprecated("Transformation.setProperty()", "Transformation.setAttribute()"); 472 }, 473 474 /** 475 * Empty method. Unused. 476 * @param {Object} term Key-value pairs of the attributes. 477 */ 478 setAttribute: function (term) {}, 479 480 /** 481 * Combine two transformations to one transformation. This only works if 482 * both of transformation matrices consist of numbers solely, and do not 483 * contain functions. 484 * 485 * Multiplies the transformation with a transformation t from the left. 486 * i.e. (this) = (t) join (this) 487 * @param {JXG.Transform} t Transformation which is the left multiplicand 488 * @returns {JXG.Transform} the transformation object. 489 */ 490 melt: function (t) { 491 var res = []; 492 493 this.update(); 494 t.update(); 495 496 res = Mat.matMatMult(t.matrix, this.matrix); 497 498 this.update = function () { 499 this.matrix = res; 500 }; 501 502 return this; 503 }, 504 505 // Documented in element.js 506 // Not yet, since transformations are not listed in board.objects. 507 getParents: function () { 508 var p = [[].concat.apply([], this.matrix)]; 509 510 if (this.parents.length !== 0) { 511 p = this.parents; 512 } 513 514 return p; 515 } 516 } 517 ); 518 519 /** 520 * @class This element is used to provide projective transformations. 521 * @pseudo 522 * @description A transformation consists of a 3x3 matrix, i.e. it is a projective transformation. 523 * <p> 524 * Internally, a transformation is applied to an element by multiplying the 3x3 matrix from the left to 525 * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order 526 * (z, x, y). The matrix has the form 527 * <pre> 528 * ( a b c ) ( z ) 529 * ( d e f ) * ( x ) 530 * ( g h i ) ( y ) 531 * </pre> 532 * where in general a=1. If b = c = 0, the transformation is called <i>affine</i>. 533 * In this case, finite points will stay finite. This is not the case for general projective coordinates. 534 * <p> 535 * Transformations acting on texts and images are considered to be affine, i.e. b and c are ignored. 536 * 537 * @name Transformation 538 * @augments JXG.Transformation 539 * @constructor 540 * @type JXG.Transformation 541 * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown. 542 * @param {numbers|functions} parameters The parameters depend on the transformation type, supplied as attribute 'type'. 543 * Possible transformation types are 544 * <ul><li> 'translate' 545 * <li> 'scale' 546 * <li> 'reflect' 547 * <li> 'rotate' 548 * <li> 'shear' 549 * <li> 'generic' 550 * </ul> 551 * The transformation matrix then looks like: 552 * <p> 553 * Translation matrix: 554 * <pre> 555 * ( 1 0 0) ( z ) 556 * ( a 1 0) * ( x ) 557 * ( b 0 1) ( y ) 558 * </pre> 559 * 560 * <p> 561 * Scale matrix: 562 * <pre> 563 * ( 1 0 0) ( z ) 564 * ( 0 a 0) * ( x ) 565 * ( 0 0 b) ( y ) 566 * </pre> 567 * 568 * <p> 569 * A rotation matrix with angle a (in Radians) 570 * <pre> 571 * ( 1 0 0 ) ( z ) 572 * ( 0 cos(a) -sin(a)) * ( x ) 573 * ( 0 sin(a) cos(a) ) ( y ) 574 * </pre> 575 * 576 * <p> 577 * Shear matrix: 578 * <pre> 579 * ( 1 0 0) ( z ) 580 * ( 0 1 a) * ( x ) 581 * ( 0 b 1) ( y ) 582 * </pre> 583 * 584 * <p>Generic transformation: 585 * <pre> 586 * ( a b c ) ( z ) 587 * ( d e f ) * ( x ) 588 * ( g h i ) ( y ) 589 * </pre> 590 * 591 * @see JXG.Transformation#setMatrix 592 * 593 * @example 594 * // The point B is determined by taking twice the vector A from the origin 595 * 596 * var p0 = board.create('point', [0, 3], {name: 'A'}), 597 * t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type: 'translate'}), 598 * p1 = board.create('point', [p0, t], {color: 'blue'}); 599 * 600 * </pre><div class="jxgbox" id="JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 601 * <script type="text/javascript"> 602 * (function() { 603 * var board = JXG.JSXGraph.initBoard('JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723', 604 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 605 * var p0 = board.create('point', [0, 3], {name: 'A'}), 606 * t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type:'translate'}), 607 * p1 = board.create('point', [p0, t], {color: 'blue'}); 608 * 609 * })(); 610 * 611 * </script><pre> 612 * 613 * @example 614 * // The point B is the result of scaling the point A with factor 2 in horizontal direction 615 * // and with factor 0.5 in vertical direction. 616 * 617 * var p1 = board.create('point', [1, 1]), 618 * t = board.create('transform', [2, 0.5], {type: 'scale'}), 619 * p2 = board.create('point', [p1, t], {color: 'blue'}); 620 * 621 * </pre><div class="jxgbox" id="JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 622 * <script type="text/javascript"> 623 * (function() { 624 * var board = JXG.JSXGraph.initBoard('JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723', 625 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 626 * var p1 = board.create('point', [1, 1]), 627 * t = board.create('transform', [2, 0.5], {type: 'scale'}), 628 * p2 = board.create('point', [p1, t], {color: 'blue'}); 629 * 630 * })(); 631 * 632 * </script><pre> 633 * 634 * @example 635 * // The point B is rotated around C which gives point D. The angle is determined 636 * // by the vertical height of point A. 637 * 638 * var p0 = board.create('point', [0, 3], {name: 'A'}), 639 * p1 = board.create('point', [1, 1]), 640 * p2 = board.create('point', [2, 1], {name:'C', fixed: true}), 641 * 642 * // angle, rotation center: 643 * t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}), 644 * p3 = board.create('point', [p1, t], {color: 'blue'}); 645 * 646 * </pre><div class="jxgbox" id="JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 647 * <script type="text/javascript"> 648 * (function() { 649 * var board = JXG.JSXGraph.initBoard('JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723', 650 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 651 * var p0 = board.create('point', [0, 3], {name: 'A'}), 652 * p1 = board.create('point', [1, 1]), 653 * p2 = board.create('point', [2, 1], {name:'C', fixed: true}), 654 * 655 * // angle, rotation center: 656 * t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}), 657 * p3 = board.create('point', [p1, t], {color: 'blue'}); 658 * 659 * })(); 660 * 661 * </script><pre> 662 * 663 * @example 664 * // A concatenation of several transformations. 665 * var p1 = board.create('point', [1, 1]), 666 * t1 = board.create('transform', [-2, -1], {type: 'translate'}), 667 * t2 = board.create('transform', [Math.PI/4], {type: 'rotate'}), 668 * t3 = board.create('transform', [2, 1], {type: 'translate'}), 669 * p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'}); 670 * 671 * </pre><div class="jxgbox" id="JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 672 * <script type="text/javascript"> 673 * (function() { 674 * var board = JXG.JSXGraph.initBoard('JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723', 675 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 676 * var p1 = board.create('point', [1, 1]), 677 * t1 = board.create('transform', [-2, -1], {type:'translate'}), 678 * t2 = board.create('transform', [Math.PI/4], {type:'rotate'}), 679 * t3 = board.create('transform', [2, 1], {type:'translate'}), 680 * p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'}); 681 * 682 * })(); 683 * 684 * </script><pre> 685 * 686 * @example 687 * // Reflection of point A 688 * var p1 = board.create('point', [1, 1]), 689 * p2 = board.create('point', [1, 3]), 690 * p3 = board.create('point', [-2, 0]), 691 * l = board.create('line', [p2, p3]), 692 * t = board.create('transform', [l], {type: 'reflect'}), // Possible are l, l.id, l.name 693 * p4 = board.create('point', [p1, t], {color: 'blue'}); 694 * 695 * </pre><div class="jxgbox" id="JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 696 * <script type="text/javascript"> 697 * (function() { 698 * var board = JXG.JSXGraph.initBoard('JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723', 699 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 700 * var p1 = board.create('point', [1, 1]), 701 * p2 = board.create('point', [1, 3]), 702 * p3 = board.create('point', [-2, 0]), 703 * l = board.create('line', [p2, p3]), 704 * t = board.create('transform', [l], {type:'reflect'}), // Possible are l, l.id, l.name 705 * p4 = board.create('point', [p1, t], {color: 'blue'}); 706 * 707 * })(); 708 * 709 * </script><pre> 710 * 711 * @example 712 * // One time application of a transform to points A, B 713 * var p1 = board.create('point', [1, 1]), 714 * p2 = board.create('point', [-1, -2]), 715 * t = board.create('transform', [3, 2], {type: 'shear'}); 716 * t.applyOnce([p1, p2]); 717 * 718 * </pre><div class="jxgbox" id="JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 719 * <script type="text/javascript"> 720 * (function() { 721 * var board = JXG.JSXGraph.initBoard('JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723', 722 * {boundingbox: [-8, 8, 8, -8], axis: true, showcopyright: false, shownavigation: false}); 723 * var p1 = board.create('point', [1, 1]), 724 * p2 = board.create('point', [-1, -2]), 725 * t = board.create('transform', [3, 2], {type: 'shear'}); 726 * t.applyOnce([p1, p2]); 727 * 728 * })(); 729 * 730 * </script><pre> 731 * 732 * @example 733 * // Construct a square of side length 2 with the 734 * // help of transformations 735 * var sq = [], 736 * right = board.create('transform', [2, 0], {type: 'translate'}), 737 * up = board.create('transform', [0, 2], {type: 'translate'}), 738 * pol, rot, p0; 739 * 740 * // The first point is free 741 * sq[0] = board.create('point', [0, 0], {name: 'Drag me'}), 742 * 743 * // Construct the other free points by transformations 744 * sq[1] = board.create('point', [sq[0], right]), 745 * sq[2] = board.create('point', [sq[0], [right, up]]), 746 * sq[3] = board.create('point', [sq[0], up]), 747 * 748 * // Polygon through these four points 749 * pol = board.create('polygon', sq, { 750 * fillColor:'blue', 751 * gradient:'radial', 752 * gradientsecondcolor:'white', 753 * gradientSecondOpacity:'0' 754 * }), 755 * 756 * p0 = board.create('point', [0, 3], {name: 'angle'}), 757 * // Rotate the square around point sq[0] by dragging A vertically. 758 * rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'}); 759 * 760 * // Apply the rotation to all but the first point of the square 761 * rot.bindTo(sq.slice(1)); 762 * 763 * </pre><div class="jxgbox" id="JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div> 764 * <script type="text/javascript"> 765 * (function() { 766 * var board = JXG.JSXGraph.initBoard('JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723', 767 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 768 * // Construct a square of side length 2 with the 769 * // help of transformations 770 * var sq = [], 771 * right = board.create('transform', [2, 0], {type: 'translate'}), 772 * up = board.create('transform', [0, 2], {type: 'translate'}), 773 * pol, rot, p0; 774 * 775 * // The first point is free 776 * sq[0] = board.create('point', [0, 0], {name: 'Drag me'}), 777 * 778 * // Construct the other free points by transformations 779 * sq[1] = board.create('point', [sq[0], right]), 780 * sq[2] = board.create('point', [sq[0], [right, up]]), 781 * sq[3] = board.create('point', [sq[0], up]), 782 * 783 * // Polygon through these four points 784 * pol = board.create('polygon', sq, { 785 * fillColor:'blue', 786 * gradient:'radial', 787 * gradientsecondcolor:'white', 788 * gradientSecondOpacity:'0' 789 * }), 790 * 791 * p0 = board.create('point', [0, 3], {name: 'angle'}), 792 * // Rotate the square around point sq[0] by dragging A vertically. 793 * rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'}); 794 * 795 * // Apply the rotation to all but the first point of the square 796 * rot.bindTo(sq.slice(1)); 797 * 798 * })(); 799 * 800 * </script><pre> 801 * 802 * @example 803 * // Text transformation 804 * var p0 = board.create('point', [0, 0], {name: 'p_0'}); 805 * var p1 = board.create('point', [3, 0], {name: 'p_1'}); 806 * var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'}); 807 * 808 * // If p_0 is dragged, translate p_1 and text accordingly 809 * var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'}); 810 * tOff.bindTo(txt); 811 * tOff.bindTo(p1); 812 * 813 * // Rotate text around p_0 by dragging point p_1 814 * var tRot = board.create('transform', [ 815 * () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'}); 816 * tRot.bindTo(txt); 817 * 818 * // Scale text by dragging point "p_1" 819 * // We do this by 820 * // - moving text by -p_0 (inverse of transformation tOff), 821 * // - scale the text (because scaling is relative to (0,0)) 822 * // - move the text back by +p_0 823 * var tOffInv = board.create('transform', [ 824 * () => -p0.X(), 825 * () => -p0.Y() 826 * ], {type:'translate'}); 827 * var tScale = board.create('transform', [ 828 * // Some scaling factor 829 * () => p1.Dist(p0) / 3, 830 * () => p1.Dist(p0) / 3 831 * ], {type:'scale'}); 832 * tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt); 833 * 834 * </pre><div id="JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66" class="jxgbox" style="width: 300px; height: 300px;"></div> 835 * <script type="text/javascript"> 836 * (function() { 837 * var board = JXG.JSXGraph.initBoard('JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66', 838 * {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}); 839 * var p0 = board.create('point', [0, 0], {name: 'p_0'}); 840 * var p1 = board.create('point', [3, 0], {name: 'p_1'}); 841 * var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'}); 842 * 843 * // If p_0 is dragged, translate p_1 and text accordingly 844 * var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'}); 845 * tOff.bindTo(txt); 846 * tOff.bindTo(p1); 847 * 848 * // Rotate text around p_0 by dragging point p_1 849 * var tRot = board.create('transform', [ 850 * () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'}); 851 * tRot.bindTo(txt); 852 * 853 * // Scale text by dragging point "p_1" 854 * // We do this by 855 * // - moving text by -p_0 (inverse of transformation tOff), 856 * // - scale the text (because scaling is relative to (0,0)) 857 * // - move the text back by +p_0 858 * var tOffInv = board.create('transform', [ 859 * () => -p0.X(), 860 * () => -p0.Y() 861 * ], {type:'translate'}); 862 * var tScale = board.create('transform', [ 863 * // Some scaling factor 864 * () => p1.Dist(p0) / 3, 865 * () => p1.Dist(p0) / 3 866 * ], {type:'scale'}); 867 * tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt); 868 * 869 * })(); 870 * 871 * </script><pre> 872 * 873 */ 874 JXG.createTransform = function (board, parents, attributes) { 875 return new JXG.Transformation(board, attributes.type, parents); 876 }; 877 878 JXG.registerElement('transform', JXG.createTransform); 879 880 export default JXG.Transformation; 881 // export default { 882 // Transformation: JXG.Transformation, 883 // createTransform: JXG.createTransform 884 // }; 885