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;
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