      1 /*
      2     Copyright 2008-2017
      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 <http://www.gnu.org/licenses/>
     29     and <http://opensource.org/licenses/MIT/>.
     30  */
     31
     32
     33 /*global JXG: true, define: true*/
     34 /*jslint nomen: true, plusplus: true*/
     35
     36 /*depends:
     37  jxg
     38  base/constants
     39  math/math
     40  utils/type
     41  */
     42
     43 /**
     44  * @fileoverview This file contains code for transformations of geometrical objects.
     45  */
     46
     47 define([
     48     'jxg', 'base/constants', 'math/math', 'utils/type'
     49 ], function (JXG, Const, Mat, Type) {
     50
     51     "use strict";
     52
     53     /**
     54      * A transformation consists of a 3x3 matrix, i.e. it is a projective transformation.
     55      * @class Creates a new transformation object. Do not use this constructor to create a transformation. Use {@link JXG.Board#create} with
     56      * type {@link Transformation} instead.
     57      * @constructor
     58      * @param {JXG.Board} board The board the new circle is drawn on.
     59      * @param {String} type Can be
     60      * <ul><li> 'translate'
     61      * <li> 'scale'
     62      * <li> 'reflect'
     63      * <li> 'rotate'
     64      * <li> 'shear'
     65      * <li> 'generic'
     66      * </ul>
     67      * @param {Object} params The parameters depend on the transformation type
     68      *
     69      * <p>
     70      * Translation matrix:
     71      * <pre>
     72      * ( 1  0  0)   ( z )
     73      * ( a  1  0) * ( x )
     74      * ( b  0  1)   ( y )
     75      * </pre>
     76      *
     77      * <p>
     78      * Scale matrix:
     79      * <pre>
     80      * ( 1  0  0)   ( z )
     81      * ( 0  a  0) * ( x )
     82      * ( 0  0  b)   ( y )
     83      * </pre>
     84      *
     85      * <p>
     86      * A rotation matrix with angle a (in Radians)
     87      * <pre>
     88      * ( 1    0        0      )   ( z )
     89      * ( 0    cos(a)   -sin(a)) * ( x )
     90      * ( 0    sin(a)   cos(a) )   ( y )
     91      * </pre>
     92      *
     93      * <p>
     94      * Shear matrix:
     95      * <pre>
     96      * ( 1  0  0)   ( z )
     97      * ( 0  1  a) * ( x )
     98      * ( 0  b  1)   ( y )
     99      * </pre>
    100      *
    101      * <p>Generic transformation:
    102      * <pre>
    103      * ( a  b  c )   ( z )
    104      * ( d  e  f ) * ( x )
    105      * ( g  h  i )   ( y )
    106      * </pre>
    107      *
    108      */
    109     JXG.Transformation = function (board, type, params) {
    110         this.elementClass = Const.OBJECT_CLASS_OTHER;
    111         this.matrix = [
    112             [1, 0, 0],
    113             [0, 1, 0],
    114             [0, 0, 1]
    115         ];
    116         this.board = board;
    117         this.isNumericMatrix = false;
    118         this.setMatrix(board, type, params);
    119
    120         this.methodMap = {
    121             apply: 'apply',
    122             applyOnce: 'applyOnce',
    123             bindTo: 'bindTo',
    124             bind: 'bind',
    125             melt: 'melt'
    126         };
    127     };
    128
    129     JXG.Transformation.prototype = {};
    130
    131     JXG.extend(JXG.Transformation.prototype, /** @lends JXG.Transformation.prototype */ {
    132         /**
    133          * @private
    134          * @returns {JXG.Transform} returns pointer to itself
    135          */
    136         update: function () {
    137             return this;
    138         },
    139
    140         /**
    141          * Set the transformation matrix for different types of standard transforms.
    142          * @param {JXG.Board} board
    143          * @param {String} type   Transformation type, possible values are
    144          *                        'translate', 'scale', 'reflect', 'rotate',
    145          *                        'shear', 'generic'.
    146          * @param {Array} params Parameters for the various transformation types.
    147          *
    148          * <p>These are
    149          * @param {Array} x,y Shift vector (number or function) in case of 'translate'.
    150          * @param {Array} scale_x,scale_y Scale vector (number or function) in case of 'scale'.
    151          * @param {Array} line|point_pair|"four coordinates" In case of 'reflect' the parameters could
    152          *                be a line, a pair of points or four number (or functions) p_x, p_y, q_x, q_y,
    153          *                determining a line through points (p_x, p_y) and (q_x, q_y).
    154          * @param {Array} angle,x,y In case of 'rotate' the parameters are an angle or angle function,
    155          *                returning the angle in Radians and - optionally - a coordinate pair or a point defining the
    156          *                returning the angle in Radians and - optionally - a coordinate pair defining the
    157          *                reotation center. If the rotation center is not given, the transformation rotates around (0,0).
    158          * @param {Array} shear_x,shear_y Shear vector (number or function) in case of 'shear'.
    159          * @param {Array} a,b,c,d,e,f,g,h,i Nine matrix entries (numbers or functions) for a generic
    160          *                projective transformation  in case of 'generic'.
    161          *
    162          * <p>A transformation with a generic matrix looks like:
    163          * <pre>
    164          * ( a  b  c )   ( z )
    165          * ( d  e  f ) * ( x )
    166          * ( g  h  i )   ( y )
    167          * </pre>
    168          *
    169          */
    170         setMatrix: function (board, type, params) {
    171             var i;
    172
    173             this.isNumericMatrix = true;
    174
    175             for (i = 0; i < params.length; i++) {
    176                 if (typeof params[i] !== 'number') {
    177                     this.isNumericMatrix = false;
    178                     break;
    179                 }
    180             }
    181
    182             if (type === 'translate') {
    183                 if (params.length !== 2) {
    184                     throw new Error("JSXGraph: translate transformation needs 2 parameters.");
    185                 }
    186                 this.evalParam = Type.createEvalFunction(board, params, 2);
    187                 this.update = function () {
    188                     this.matrix[1][0] = this.evalParam(0);
    189                     this.matrix[2][0] = this.evalParam(1);
    190                 };
    191             } else if (type === 'scale') {
    192                 if (params.length !== 2) {
    193                     throw new Error("JSXGraph: scale transformation needs 2 parameters.");
    194                 }
    195                 this.evalParam = Type.createEvalFunction(board, params, 2);
    196                 this.update = function () {
    197                     this.matrix[1][1] = this.evalParam(0); // x
    198                     this.matrix[2][2] = this.evalParam(1); // y
    199                 };
    200             // Input: line or two points
    201             } else if (type === 'reflect') {
    202                 // line or two points
    203                 if (params.length < 4) {
    204                     params[0] = board.select(params[0]);
    205                 }
    206
    207                 // two points
    208                 if (params.length === 2) {
    209                     params[1] = board.select(params[1]);
    210                 }
    211
    212                 // 4 coordinates [px,py,qx,qy]
    213                 if (params.length === 4) {
    214                     this.evalParam = Type.createEvalFunction(board, params, 4);
    215                 }
    216
    217                 this.update = function () {
    218                     var x, y, z, xoff, yoff, d,
    219                         v, p;
    220                     // Determine homogeneous coordinates of reflections axis
    221                     // line
    222                     if (params.length === 1) {
    223                         v = params[0].stdform;
    224                     // two points
    225                     } else if (params.length === 2) {
    226                         v = Mat.crossProduct(params[1].coords.usrCoords, params[0].coords.usrCoords);
    227                     // two points coordinates [px,py,qx,qy]
    228                     } else if (params.length === 4) {
    229                         v = Mat.crossProduct(
    230                             [1, this.evalParam(2), this.evalParam(3)],
    231                             [1, this.evalParam(0), this.evalParam(1)]
    232                         );
    233                     }
    234
    235                     // Project origin to the line.  This gives a finite point p
    236                     x = v[1];
    237                     y = v[2];
    238                     z = v[0];
    239                     p = [-z * x, -z * y, x * x + y * y];
    240                     d = p[2];
    241
    242                     // Normalize p
    243                     xoff = p[0] / p[2];
    244                     yoff = p[1] / p[2];
    245
    246                     // x, y is the direction of the line
    247                     x = -v[2];
    248                     y =  v[1];
    249
    250                     this.matrix[1][1] = (x * x - y * y) / d;
    251                     this.matrix[1][2] = 2 * x * y / d;
    252                     this.matrix[2][1] = this.matrix[1][2];
    253                     this.matrix[2][2] = -this.matrix[1][1];
    254                     this.matrix[1][0] = xoff * (1 - this.matrix[1][1]) - yoff * this.matrix[1][2];
    255                     this.matrix[2][0] = 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) {
    266                         params[1] = board.select(params[1]);
    267                     }
    268                 }
    269
    270                 this.update = function () {
    271                     var x, 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                             x = params[1].X();
    288                             y = params[1].Y();
    289                         }
    290                         this.matrix[1][0] = x * (1 - co) + y * si;
    291                         this.matrix[2][0] = y * (1 - co) - x * si;
    292                     }
    293                 };
    294             } else if (type === 'shear') {
    295                 if (params.length !== 2) {
    296                     throw new Error("JSXGraph: shear transformation needs 2 parameters.");
    297                 }
    298
    299                 this.evalParam = Type.createEvalFunction(board, params, 2);
    300                 this.update = function () {
    301                     this.matrix[1][2] = this.evalParam(0);
    302                     this.matrix[2][1] = this.evalParam(1);
    303                 };
    304             } else if (type === 'generic') {
    305                 if (params.length !== 9) {
    306                     throw new Error("JSXGraph: generic transformation needs 9 parameters.");
    307                 }
    308
    309                 this.evalParam = Type.createEvalFunction(board, params, 9);
    310
    311                 this.update = function () {
    312                     this.matrix[0][0] = this.evalParam(0);
    313                     this.matrix[0][1] = this.evalParam(1);
    314                     this.matrix[0][2] = this.evalParam(2);
    315                     this.matrix[1][0] = this.evalParam(3);
    316                     this.matrix[1][1] = this.evalParam(4);
    317                     this.matrix[1][2] = this.evalParam(5);
    318                     this.matrix[2][0] = this.evalParam(6);
    319                     this.matrix[2][1] = this.evalParam(7);
    320                     this.matrix[2][2] = this.evalParam(8);
    321                 };
    322             }
    323         },
    324
    325         /**
    326          * Transform a GeometryElement:
    327          * First, the transformation matrix is updated, the do the matrix-vector-multiplication.
    328          * @param {JXG.GeometryElement} p element which is transformed
    329          * @param {String} 'self' Apply the transformation to the initialCoords instead of the coords if this is set.
    330          * @returns {Array}
    331          */
    332         apply: function (p, self) {
    333             this.update();
    334
    335             if (Type.exists(self)) {
    336                 return Mat.matVecMult(this.matrix, p.initialCoords.usrCoords);
    337             }
    338             return Mat.matVecMult(this.matrix, p.coords.usrCoords);
    339         },
    340
    341         /**
    342          * Applies a transformation once to a GeometryElement.
    343          * If it is a free point, then it can be dragged around later
    344          * and will overwrite the transformed coordinates.
    345          * @param {JXG.Point,Array} p
    346          */
    347         applyOnce: function (p) {
    348             var c, len, i;
    349
    350             if (!Type.isArray(p)) {
    351                 p = [p];
    352             }
    353
    354             len = p.length;
    355
    356             for (i = 0; i < len; i++) {
    357                 this.update();
    358                 c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords);
    359                 p[i].coords.setCoordinates(Const.COORDS_BY_USER, c);
    360             }
    361         },
    362
    363         /**
    364          * Binds a transformation to a GeometryElement. In every update of the
    365          * GeometryElement, the transformation is executed.
    366          * @param  {Array,JXG.Object} p JXG.Object or array of JXG.Object to
    367          *                            which the transformation is bound to.
    368          */
    369         bindTo: function (p) {
    370             var i, len;
    371             if (Type.isArray(p)) {
    372                 len = p.length;
    373
    374                 for (i = 0; i < len; i++) {
    375                     p[i].transformations.push(this);
    376                 }
    377             } else {
    378                 p.transformations.push(this);
    379             }
    380         },
    381
    382         /**
    383          * Unused
    384          * @deprecated Use setAttribute
    385          * @param term
    386          */
    387         setProperty: function (term) {
    388             JXG.deprecated('Transformation.setProperty()', 'Transformation.setAttribute()');
    389         },
    390
    391         /**
    392          * Empty method. Unused.
    393          * @param {Object} term Key-value pairs of the attributes.
    394          */
    395         setAttribute: function (term) { },
    396
    397         /**
    398          * Combine two transformations to one transformations. This only works if
    399          * the both transformation  matrices consist of numbers, solely (and do not
    400          * contain functions).
    401          *
    402          * Multiplies the transformation with a transformation t from the left.
    403          * i.e. (this) = (t) join (this)
    404          * @param  {JXG.Transform} t Transformation which is the left multiplicand
    405          * @returns {JXG.Transform} the transformation object.
    406          */
    407         melt: function (t) {
    408             var res = [], i, len, len0, k, s, j;
    409
    410             len = t.matrix.length;
    411             len0 = this.matrix[0].length;
    412
    413             for (i = 0; i < len; i++) {
    414                 res[i] = [];
    415             }
    416
    417             this.update();
    418             t.update();
    419
    420             for (i = 0; i < len; i++) {
    421                 for (j = 0; j < len0; j++) {
    422                     s = 0;
    423                     for (k = 0; k < len; k++) {
    424                         s += t.matrix[i][k] * this.matrix[k][j];
    425                     }
    426                     res[i][j] = s;
    427                 }
    428             }
    429
    430             this.update = function () {
    431                 var len = this.matrix.length,
    432                     len0 = this.matrix[0].length;
    433
    434                 for (i = 0; i < len; i++) {
    435                     for (j = 0; j < len0; j++) {
    436                         this.matrix[i][j] = res[i][j];
    437                     }
    438                 }
    439             };
    440             return this;
    441         },
    442
    443         // documented in element.js
    444         // Not yet, since transformations are not listed in board.objects.
    445         getParents: function () {
    446             var p = [[].concat.apply([], this.matrix)];
    447
    448             if (this.parents.length !== 0) {
    449                 p = this.parents;
    450             }
    451
    452             return p;
    453         }
    454
    455     });
    456
    457     /**
    458      * @class This element is used to provide projective transformations.
    459      * @pseudo
    460      * @description A transformation consists of a 3x3 matrix, i.e. it is a projective transformation.
    461      * @name Transformation
    462      * @augments JXG.Transformation
    463      * @constructor
    464      * @type JXG.Transformation
    465      * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
    466      * @param {number,function} The parameters depend on the transformation type, supplied as attribute 'type'.
    467      * Possible transformation types are
    468      * <ul><li> 'translate'
    469      * <li> 'scale'
    470      * <li> 'reflect'
    471      * <li> 'rotate'
    472      * <li> 'shear'
    473      * <li> 'generic'
    474      * </ul>
    475      * The transformation matrix then looks like:
    476      * <p>
    477      * Translation matrix:
    478      * <pre>
    479      * ( 1  0  0)   ( z )
    480      * ( a  1  0) * ( x )
    481      * ( b  0  1)   ( y )
    482      * </pre>
    483      *
    484      * <p>
    485      * Scale matrix:
    486      * <pre>
    487      * ( 1  0  0)   ( z )
    488      * ( 0  a  0) * ( x )
    489      * ( 0  0  b)   ( y )
    490      * </pre>
    491      *
    492      * <p>
    493      * A rotation matrix with angle a (in Radians)
    494      * <pre>
    495      * ( 1    0        0      )   ( z )
    496      * ( 0    cos(a)   -sin(a)) * ( x )
    497      * ( 0    sin(a)   cos(a) )   ( y )
    498      * </pre>
    499      *
    500      * <p>
    501      * Shear matrix:
    502      * <pre>
    503      * ( 1  0  0)   ( z )
    504      * ( 0  1  a) * ( x )
    505      * ( 0  b  1)   ( y )
    506      * </pre>
    507      *
    508      * <p>Generic transformation:
    509      * <pre>
    510      * ( a  b  c )   ( z )
    511      * ( d  e  f ) * ( x )
    512      * ( g  h  i )   ( y )
    513      * </pre>
    514      *
    515      * @example
    516      * // The point B is determined by taking twice the vector A from the origin
    517      *
    518      * var p0 = board.create('point', [0, 3], {name: 'A'}),
    519      *     t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type: 'translate'}),
    520      *     p1 = board.create('point', [p0, t], {color: 'blue'});
    521      *
    522      * </pre><div class="jxgbox" id="14167b0c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    523      * <script type="text/javascript">
    524      *     (function() {
    525      *         var board = JXG.JSXGraph.initBoard('14167b0c-2ad3-11e5-8dd9-901b0e1b8723',
    526      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    527      *     var p0 = board.create('point', [0, 3], {name: 'A'}),
    528      *         t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type:'translate'}),
    529      *         p1 = board.create('point', [p0, t], {color: 'blue'});
    530      *
    531      *     })();
    532      *
    533      * </script><pre>
    534      *
    535      * @example
    536      * // The point B is the result of scaling the point A with factor 2 in horizontal direction
    537      * // and with factor 0.5 in vertical direction.
    538      *
    539      * var p1 = board.create('point', [1, 1]),
    540      *     t = board.create('transform', [2, 0.5], {type: 'scale'}),
    541      *     p2 = board.create('point', [p1, t], {color: 'blue'});
    542      *
    543      * </pre><div class="jxgbox" id="a6827a72-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    544      * <script type="text/javascript">
    545      *     (function() {
    546      *         var board = JXG.JSXGraph.initBoard('a6827a72-2ad3-11e5-8dd9-901b0e1b8723',
    547      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    548      *     var p1 = board.create('point', [1, 1]),
    549      *         t = board.create('transform', [2, 0.5], {type: 'scale'}),
    550      *         p2 = board.create('point', [p1, t], {color: 'blue'});
    551      *
    552      *     })();
    553      *
    554      * </script><pre>
    555      *
    556      * @example
    557      * // The point B is rotated around C which gives point D. The angle is determined
    558      * // by the vertical height of point A.
    559      *
    560      * var p0 = board.create('point', [0, 3], {name: 'A'}),
    561      *     p1 = board.create('point', [1, 1]),
    562      *     p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
    563      *
    564      *     // angle, rotation center:
    565      *     t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
    566      *     p3 = board.create('point', [p1, t], {color: 'blue'});
    567      *
    568      * </pre><div class="jxgbox" id="747cf11e-2ad4-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    569      * <script type="text/javascript">
    570      *     (function() {
    571      *         var board = JXG.JSXGraph.initBoard('747cf11e-2ad4-11e5-8dd9-901b0e1b8723',
    572      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    573      *     var p0 = board.create('point', [0, 3], {name: 'A'}),
    574      *         p1 = board.create('point', [1, 1]),
    575      *         p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
    576      *
    577      *         // angle, rotation center:
    578      *         t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
    579      *         p3 = board.create('point', [p1, t], {color: 'blue'});
    580      *
    581      *     })();
    582      *
    583      * </script><pre>
    584      *
    585      * @example
    586      * // A concatenation of several transformations.
    587      * var p1 = board.create('point', [1, 1]),
    588      *     t1 = board.create('transform', [-2, -1], {type: 'translate'}),
    589      *     t2 = board.create('transform', [Math.PI/4], {type: 'rotate'}),
    590      *     t3 = board.create('transform', [2, 1], {type: 'translate'}),
    591      *     p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
    592      *
    593      * </pre><div class="jxgbox" id="f516d3de-2ad5-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    594      * <script type="text/javascript">
    595      *     (function() {
    596      *         var board = JXG.JSXGraph.initBoard('f516d3de-2ad5-11e5-8dd9-901b0e1b8723',
    597      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    598      *     var p1 = board.create('point', [1, 1]),
    599      *         t1 = board.create('transform', [-2, -1], {type:'translate'}),
    600      *         t2 = board.create('transform', [Math.PI/4], {type:'rotate'}),
    601      *         t3 = board.create('transform', [2, 1], {type:'translate'}),
    602      *         p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
    603      *
    604      *     })();
    605      *
    606      * </script><pre>
    607      *
    608      * @example
    609      * // Reflection of point A
    610      * var p1 = board.create('point', [1, 1]),
    611      *     p2 = board.create('point', [1, 3]),
    612      *     p3 = board.create('point', [-2, 0]),
    613      *     l = board.create('line', [p2, p3]),
    614      *     t = board.create('transform', [l], {type: 'reflect'}),  // Possible are l, l.id, l.name
    615      *     p4 = board.create('point', [p1, t], {color: 'blue'});
    616      *
    617      * </pre><div class="jxgbox" id="6f374a04-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    618      * <script type="text/javascript">
    619      *     (function() {
    620      *         var board = JXG.JSXGraph.initBoard('6f374a04-2ad6-11e5-8dd9-901b0e1b8723',
    621      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    622      *     var p1 = board.create('point', [1, 1]),
    623      *         p2 = board.create('point', [1, 3]),
    624      *         p3 = board.create('point', [-2, 0]),
    625      *         l = board.create('line', [p2, p3]),
    626      *         t = board.create('transform', [l], {type:'reflect'}),  // Possible are l, l.id, l.name
    627      *         p4 = board.create('point', [p1, t], {color: 'blue'});
    628      *
    629      *     })();
    630      *
    631      * </script><pre>
    632      *
    633      * @example
    634      * // One time application of a transform to points A, B
    635      * var p1 = board.create('point', [1, 1]),
    636      *     p2 = board.create('point', [1, 1]),
    637      *     t = board.create('transform', [3, 2], {type: 'shear'});
    638      * t.applyOnce([p1, p2]);
    639      *
    640      * </pre><div class="jxgbox" id="b6cee1c4-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    641      * <script type="text/javascript">
    642      *     (function() {
    643      *         var board = JXG.JSXGraph.initBoard('b6cee1c4-2ad6-11e5-8dd9-901b0e1b8723',
    644      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    645      *     var p1 = board.create('point', [1, 1]),
    646      *         p2 = board.create('point', [-1, -2]),
    647      *         t = board.create('transform', [3, 2], {type: 'shear'});
    648      *     t.applyOnce([p1, p2]);
    649      *
    650      *     })();
    651      *
    652      * </script><pre>
    653      *
    654      * @example
    655      * // Construct a square of side length 2 with the
    656      * // help of transformations
    657      *     var sq = [],
    658      *         right = board.create('transform', [2, 0], {type: 'translate'}),
    659      *         up = board.create('transform', [0, 2], {type: 'translate'}),
    660      *         pol, rot, p0;
    661      *
    662      *     // The first point is free
    663      *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
    664      *
    665      *     // Construct the other free points by transformations
    666      *     sq[1] = board.create('point', [sq[0], right]),
    667      *     sq[2] = board.create('point', [sq[0], [right, up]]),
    668      *     sq[3] = board.create('point', [sq[0], up]),
    669      *
    670      *     // Polygon through these four points
    671      *     pol = board.create('polygon', sq, {
    672      *             fillColor:'blue',
    673      *             gradient:'radial',
    674      *             gradientsecondcolor:'white',
    675      *             gradientSecondOpacity:'0'
    676      *     }),
    677      *
    678      *     p0 = board.create('point', [0, 3], {name: 'angle'}),
    679      *     // Rotate the square around point sq[0] by dragging A
    680      *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
    681      *
    682      *     // Apply the rotation to all but the first point of the square
    683      *     rot.bindTo(sq.slice(1));
    684      *
    685      * </pre><div class="jxgbox" id="c7f9097e-2ad7-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
    686      * <script type="text/javascript">
    687      *     (function() {
    688      *         var board = JXG.JSXGraph.initBoard('c7f9097e-2ad7-11e5-8dd9-901b0e1b8723',
    689      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    690      *     // Construct a square of side length 2 with the
    691      *     // help of transformations
    692      *     var sq = [],
    693      *         right = board.create('transform', [2, 0], {type: 'translate'}),
    694      *         up = board.create('transform', [0, 2], {type: 'translate'}),
    695      *         pol, rot, p0;
    696      *
    697      *     // The first point is free
    698      *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
    699      *
    700      *     // Construct the other free points by transformations
    701      *     sq[1] = board.create('point', [sq[0], right]),
    702      *     sq[2] = board.create('point', [sq[0], [right, up]]),
    703      *     sq[3] = board.create('point', [sq[0], up]),
    704      *
    705      *     // Polygon through these four points
    706      *     pol = board.create('polygon', sq, {
    707      *             fillColor:'blue',
    708      *             gradient:'radial',
    709      *             gradientsecondcolor:'white',
    710      *             gradientSecondOpacity:'0'
    711      *     }),
    712      *
    713      *     p0 = board.create('point', [0, 3], {name: 'angle'}),
    714      *     // Rotate the square around point sq[0] by dragging A
    715      *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
    716      *
    717      *     // Apply the rotation to all but the first point of the square
    718      *     rot.bindTo(sq.slice(1));
    719      *
    720      *     })();
    721      *
    722      * </script><pre>
    723      *
    724      */
    725     JXG.createTransform = function (board, parents, attributes) {
    726         return new JXG.Transformation(board, attributes.type, parents);
    727     };
    728
    729     JXG.registerElement('transform', JXG.createTransform);
    730
    731     return {
    732         Transformation: JXG.Transformation,
    733         createTransform: JXG.createTransform
    734     };
    735 });
    736