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