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