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