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