1 /*
  2     Copyright 2008-2025
  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 transformation is part of.
 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  * <li> 'matrix'
 59  * </ul>
 60  * @param {Object} params The parameters depend on the transformation type
 61  *
 62  * <p>
 63  * Translation matrix:
 64  * <pre>
 65  * ( 1  0  0)   ( z )
 66  * ( a  1  0) * ( x )
 67  * ( b  0  1)   ( y )
 68  * </pre>
 69  *
 70  * <p>
 71  * Scale matrix:
 72  * <pre>
 73  * ( 1  0  0)   ( z )
 74  * ( 0  a  0) * ( x )
 75  * ( 0  0  b)   ( y )
 76  * </pre>
 77  *
 78  * <p>
 79  * A rotation matrix with angle a (in Radians)
 80  * <pre>
 81  * ( 1    0        0      )   ( z )
 82  * ( 0    cos(a)   -sin(a)) * ( x )
 83  * ( 0    sin(a)   cos(a) )   ( y )
 84  * </pre>
 85  *
 86  * <p>
 87  * Shear matrix:
 88  * <pre>
 89  * ( 1  0  0)   ( z )
 90  * ( 0  1  a) * ( x )
 91  * ( 0  b  1)   ( y )
 92  * </pre>
 93  *
 94  * <p>Generic transformation:
 95  * <pre>
 96  * ( a  b  c )   ( z )
 97  * ( d  e  f ) * ( x )
 98  * ( g  h  i )   ( y )
 99  * </pre>
100  *
101  */
102 JXG.Transformation = function (board, type, params, is3D) {
103     this.elementClass = Const.OBJECT_CLASS_OTHER;
104     this.type = Const.OBJECT_TYPE_TRANSFORMATION;
105 
106     if (is3D) {
107         this.is3D = true;
108         this.matrix = [
109             [1, 0, 0, 0],
110             [0, 1, 0, 0],
111             [0, 0, 1, 0],
112             [0, 0, 0, 1]
113         ];
114     } else {
115         this.is3D = false;
116         this.matrix = [
117             [1, 0, 0],
118             [0, 1, 0],
119             [0, 0, 1]
120         ];
121     }
122 
123     this.board = board;
124     this.isNumericMatrix = false;
125     if (this.is3D) {
126         this.setMatrix3D(params[0] /* view3d */, type, params.slice(1));
127     } else {
128         this.setMatrix(board, type, params);
129     }
130 
131     this.methodMap = {
132         apply: "apply",
133         applyOnce: "applyOnce",
134         bindTo: "bindTo",
135         bind: "bindTo",
136         melt: "melt",
137         meltTo: "meltTo"
138     };
139 };
140 
141 JXG.Transformation.prototype = {};
142 
143 JXG.extend(
144     JXG.Transformation.prototype,
145     /** @lends JXG.Transformation.prototype */ {
146         /**
147          * Updates the numerical data for the transformation, i.e. the entry of the subobject matrix.
148          * @returns {JXG.Transform} returns pointer to itself
149          */
150         update: function () {
151             return this;
152         },
153 
154         /**
155          * Set the transformation matrix for different types of standard transforms.
156          * @param {JXG.Board} board
157          * @param {String} type   Transformation type, possible values are
158          *                        'translate', 'scale', 'reflect', 'rotate',
159          *                        'shear', 'generic'.
160          * @param {Array} params Parameters for the various transformation types.
161          *
162          * <p>A transformation with a generic matrix looks like:
163          * <pre>
164          * ( a  b  c )   ( z )
165          * ( d  e  f ) * ( x )
166          * ( g  h  i )   ( y )
167          * </pre>
168          *
169          * The transformation matrix then looks like:
170          * <p>
171          * Translation matrix:
172          * <pre>
173          * ( 1  0  0)   ( z )
174          * ( a  1  0) * ( x )
175          * ( b  0  1)   ( y )
176          * </pre>
177          *
178          * <p>
179          * Scale matrix:
180          * <pre>
181          * ( 1  0  0)   ( z )
182          * ( 0  a  0) * ( x )
183          * ( 0  0  b)   ( y )
184          * </pre>
185          *
186          * <p>
187          * A rotation matrix with angle a (in Radians)
188          * <pre>
189          * ( 1    0        0      )   ( z )
190          * ( 0    cos(a)   -sin(a)) * ( x )
191          * ( 0    sin(a)   cos(a) )   ( y )
192          * </pre>
193          *
194          * <p>
195          * Shear matrix:
196          * <pre>
197          * ( 1  0  0)   ( z )
198          * ( 0  1  a) * ( x )
199          * ( 0  b  1)   ( y )
200          * </pre>
201          *
202          * <p>Generic transformation (9 parameters):
203          * <pre>
204          * ( a  b  c )   ( z )
205          * ( d  e  f ) * ( x )
206          * ( g  h  i )   ( y )
207          * </pre>
208          *
209          * <p>Matrix:
210          * <pre>
211          * (         )   ( z )
212          * (    M    ) * ( x )
213          * (         )   ( y )
214          * </pre>
215          */
216         setMatrix: function (board, type, params) {
217             var i;
218                 // e, obj; // Handle dependencies
219 
220             this.isNumericMatrix = true;
221             for (i = 0; i < params.length; i++) {
222                 if (typeof params[i] !== "number") {
223                     this.isNumericMatrix = false;
224                     break;
225                 }
226             }
227 
228             if (type === "translate") {
229                 if (params.length !== 2) {
230                     throw new Error("JSXGraph: translate transformation needs 2 parameters.");
231                 }
232                 this.evalParam = Type.createEvalFunction(board, params, 2);
233                 this.update = function () {
234                     this.matrix[1][0] = this.evalParam(0);
235                     this.matrix[2][0] = this.evalParam(1);
236                 };
237             } else if (type === "scale") {
238                 if (params.length !== 2) {
239                     throw new Error("JSXGraph: scale transformation needs 2 parameters.");
240                 }
241                 this.evalParam = Type.createEvalFunction(board, params, 2);
242                 this.update = function () {
243                     this.matrix[1][1] = this.evalParam(0); // x
244                     this.matrix[2][2] = this.evalParam(1); // y
245                 };
246                 // Input: line or two points
247             } else if (type === "reflect") {
248                 // line or two points
249                 if (params.length < 4) {
250                     params[0] = board.select(params[0]);
251                 }
252 
253                 // two points
254                 if (params.length === 2) {
255                     params[1] = board.select(params[1]);
256                 }
257 
258                 // 4 coordinates [px,py,qx,qy]
259                 if (params.length === 4) {
260                     this.evalParam = Type.createEvalFunction(board, params, 4);
261                 }
262 
263                 this.update = function () {
264                     var x, y, z, xoff, yoff, d, v, p;
265                     // Determine homogeneous coordinates of reflections axis
266                     // line
267                     if (params.length === 1) {
268                         v = params[0].stdform;
269                     } else if (params.length === 2) {
270                         // two points
271                         v = Mat.crossProduct(
272                             params[1].coords.usrCoords,
273                             params[0].coords.usrCoords
274                         );
275                     } else if (params.length === 4) {
276                         // two points coordinates [px,py,qx,qy]
277                         v = Mat.crossProduct(
278                             [1, this.evalParam(2), this.evalParam(3)],
279                             [1, this.evalParam(0), this.evalParam(1)]
280                         );
281                     }
282 
283                     // Project origin to the line. This gives a finite point p
284                     x = v[1];
285                     y = v[2];
286                     z = v[0];
287                     p = [-z * x, -z * y, x * x + y * y];
288                     d = p[2];
289 
290                     // Normalize p
291                     xoff = p[0] / p[2];
292                     yoff = p[1] / p[2];
293 
294                     // x, y is the direction of the line
295                     x = -v[2];
296                     y = v[1];
297 
298                     this.matrix[1][1] = (x * x - y * y) / d;
299                     this.matrix[1][2] = (2 * x * y) / d;
300                     this.matrix[2][1] = this.matrix[1][2];
301                     this.matrix[2][2] = -this.matrix[1][1];
302                     this.matrix[1][0] =
303                         xoff * (1 - this.matrix[1][1]) - yoff * this.matrix[1][2];
304                     this.matrix[2][0] =
305                         yoff * (1 - this.matrix[2][2]) - xoff * this.matrix[2][1];
306                 };
307             } else if (type === "rotate") {
308                 if (params.length === 3) {
309                     // angle, x, y
310                     this.evalParam = Type.createEvalFunction(board, params, 3);
311                 } else if (params.length > 0 && params.length <= 2) {
312                     // angle, p or angle
313                     this.evalParam = Type.createEvalFunction(board, params, 1);
314 
315                     if (params.length === 2 && !Type.isArray(params[1])) {
316                         params[1] = board.select(params[1]);
317                     }
318                 }
319 
320                 this.update = function () {
321                     var x,
322                         y,
323                         beta = this.evalParam(0),
324                         co = Math.cos(beta),
325                         si = Math.sin(beta);
326 
327                     this.matrix[1][1] = co;
328                     this.matrix[1][2] = -si;
329                     this.matrix[2][1] = si;
330                     this.matrix[2][2] = co;
331 
332                     // rotate around [x,y] otherwise rotate around [0,0]
333                     if (params.length > 1) {
334                         if (params.length === 3) {
335                             x = this.evalParam(1);
336                             y = this.evalParam(2);
337                         } else {
338                             if (Type.isArray(params[1])) {
339                                 x = params[1][0];
340                                 y = params[1][1];
341                             } else {
342                                 x = params[1].X();
343                                 y = params[1].Y();
344                             }
345                         }
346                         this.matrix[1][0] = x * (1 - co) + y * si;
347                         this.matrix[2][0] = y * (1 - co) - x * si;
348                     }
349                 };
350             } else if (type === "shear") {
351                 if (params.length !== 2) {
352                     throw new Error("JSXGraph: shear transformation needs 2 parameters.");
353                 }
354 
355                 this.evalParam = Type.createEvalFunction(board, params, 2);
356                 this.update = function () {
357                     this.matrix[1][2] = this.evalParam(0);
358                     this.matrix[2][1] = this.evalParam(1);
359                 };
360             } else if (type === "generic") {
361                 if (params.length !== 9) {
362                     throw new Error("JSXGraph: generic transformation needs 9 parameters.");
363                 }
364 
365                 this.evalParam = Type.createEvalFunction(board, params, 9);
366 
367                 this.update = function () {
368                     this.matrix[0][0] = this.evalParam(0);
369                     this.matrix[0][1] = this.evalParam(1);
370                     this.matrix[0][2] = this.evalParam(2);
371                     this.matrix[1][0] = this.evalParam(3);
372                     this.matrix[1][1] = this.evalParam(4);
373                     this.matrix[1][2] = this.evalParam(5);
374                     this.matrix[2][0] = this.evalParam(6);
375                     this.matrix[2][1] = this.evalParam(7);
376                     this.matrix[2][2] = this.evalParam(8);
377                 };
378             } else if (type === "matrix") {
379                 if (params.length !== 1) {
380                     throw new Error("JSXGraph: transformation of type 'matrix' needs 1 parameter.");
381                 }
382 
383                 this.evalParam = params[0].slice();
384                 this.update = function () {
385                     var i, j;
386                     for (i = 0; i < 3; i++) {
387                         for (j = 0; j < 3; j++) {
388                             this.matrix[i][j] = Type.evaluate(this.evalParam[i][j]);
389                         }
390                     }
391                 };
392             }
393 
394             // Handle dependencies
395             // NO: transformations do not have method addParents
396             // if (Type.exists(this.evalParam)) {
397             //     for (e in this.evalParam.deps) {
398             //         obj = this.evalParam.deps[e];
399             //         this.addParents(obj);
400             //         obj.addChild(this);
401             //     }
402             // }
403         },
404 
405         /**
406          * Set the 3D transformation matrix for different types of standard transforms.
407          * @param {JXG.Board} board
408          * @param {String} type   Transformation type, possible values are
409          *                        'translate', 'scale', 'rotate',
410          *                        'rotateX', 'rotateY', 'rotateZ',
411          *                        'shear', 'generic'.
412          * @param {Array} params Parameters for the various transformation types.
413          *
414          * <p>A transformation with a generic matrix looks like:
415          * <pre>
416          * ( a  b  c  d)   ( w )
417          * ( e  f  g  h) * ( x )
418          * ( i  j  k  l)   ( y )
419          * ( m  n  o  p)   ( z )
420          * </pre>
421          *
422          * The transformation matrix then looks like:
423          * <p>
424          * Translation matrix:
425          * <pre>
426          * ( 1  0  0  0)   ( w )
427          * ( a  1  0  0) * ( x )
428          * ( b  0  1  0)   ( y )
429          * ( c  0  0  1)   ( z )
430          * </pre>
431          *
432          * <p>
433          * Scale matrix:
434          * <pre>
435          * ( 1  0  0  0)   ( w )
436          * ( 0  a  0  0) * ( x )
437          * ( 0  0  b  0)   ( y )
438          * ( 0  0  0  c)   ( z )
439          * </pre>
440          *
441          * <p>
442          * rotateX: a rotation matrix with angle a (in Radians)
443          * <pre>
444          * ( 1    0        0             )   ( z )
445          * ( 0    1        0         0   ) * ( x )
446          * ( 0    0      cos(a)   -sin(a)) * ( x )
447          * ( 0    0      sin(a)   cos(a) )   ( y )
448          * </pre>
449          *
450          * <p>
451          * rotateY: a rotation matrix with angle a (in Radians)
452          * <pre>
453          * ( 1      0       0           )   ( z )
454          * ( 0    cos(a)    0    -sin(a)) * ( x )
455          * ( 0      0       1       0   ) * ( x )
456          * ( 0    sin(a)    0    cos(a) )   ( y )
457          * </pre>
458          *
459          * <p>
460          * rotateZ: a rotation matrix with angle a (in Radians)
461          * <pre>
462          * ( 1      0                0  )   ( z )
463          * ( 0    cos(a)   -sin(a)   0  ) * ( x )
464          * ( 0    sin(a)   cos(a)    0  )   ( y )
465          * ( 0      0         0      1  ) * ( x )
466          * </pre>
467          *
468          * <p>
469          * rotate: a rotation matrix with angle a (in Radians)
470          * and normal <i>n</i>.
471          *
472          */
473         setMatrix3D: function(view, type, params) {
474             var i,
475                 board = view.board;
476 
477             this.isNumericMatrix = true;
478             for (i = 0; i < params.length; i++) {
479                 if (typeof params[i] !== "number") {
480                     this.isNumericMatrix = false;
481                     break;
482                 }
483             }
484 
485             if (type === "translate") {
486                 if (params.length !== 3) {
487                     throw new Error("JSXGraph: 3D translate transformation needs 3 parameters.");
488                 }
489                 this.evalParam = Type.createEvalFunction(board, params, 3);
490                 this.update = function () {
491                     this.matrix[1][0] = this.evalParam(0);
492                     this.matrix[2][0] = this.evalParam(1);
493                     this.matrix[3][0] = this.evalParam(2);
494                 };
495             } else if (type === 'scale') {
496                 if (params.length !== 3 && params.length !== 4) {
497                     throw new Error("JSXGraph: 3D scale transformation needs either 3 or 4 parameters.");
498                 }
499                 this.evalParam = Type.createEvalFunction(board, params, 3);
500                 this.update = function () {
501                     var x = this.evalParam(0),
502                         y = this.evalParam(1),
503                         z = this.evalParam(2);
504 
505                     this.matrix[1][1] = x;
506                     this.matrix[2][2] = y;
507                     this.matrix[3][3] = z;
508                 };
509             } else if (type === 'rotateX') {
510                 if (params.length > 0 && params.length <= 2) {
511                     this.evalParam = Type.createEvalFunction(board, params, 1);
512                 }
513 
514                 this.update = function () {
515                     var a = this.evalParam(0),
516                         co = Math.cos(a),
517                         si = Math.sin(a);
518 
519                     this.matrix[2][2] = co;
520                     this.matrix[2][3] = -si;
521                     this.matrix[3][2] = si;
522                     this.matrix[3][3] = co;
523                 };
524             } else if (type === 'rotateY') {
525                 if (params.length > 0 && params.length <= 2) {
526                     this.evalParam = Type.createEvalFunction(board, params, 1);
527                 }
528                 this.update = function () {
529                     var a = this.evalParam(0),
530                         co = Math.cos(a),
531                         si = Math.sin(a);
532 
533                     this.matrix[1][1] = co;
534                     this.matrix[1][3] = si;
535                     this.matrix[3][1] = -si;
536                     this.matrix[3][3] = co;
537                 };
538             } else if (type === 'rotateZ') {
539                 if (params.length > 0 && params.length <= 2) {
540                     this.evalParam = Type.createEvalFunction(board, params, 1);
541                 }
542                 this.update = function () {
543                     var a = this.evalParam(0),
544                         co = Math.cos(a),
545                         si = Math.sin(a);
546 
547                     this.matrix[1][1] = co;
548                     this.matrix[1][2] = -si;
549                     this.matrix[2][1] = si;
550                     this.matrix[2][2] = co;
551                 };
552             } else if (type === 'rotate') {
553                 if (params.length === 2) {
554                     this.evalParam = Type.createEvalFunction(board, params, 2);
555                 }
556                 this.update = function () {
557                     var a = this.evalParam(0), // angle
558                         n = this.evalParam(1), // normal
559                         co = Math.cos(a),
560                         si = Math.sin(a),
561                         n1, n2, n3,
562                         nrm = Mat.norm(n);
563 
564                     if (n.length === 3) {
565                         n1 = n[0] / nrm;
566                         n2 = n[1] / nrm;
567                         n3 = n[2] / nrm;
568                     } else {
569                         n1 = n[1] / nrm;
570                         n2 = n[2] / nrm;
571                         n3 = n[3] / nrm;
572                     }
573 
574                     this.matrix = [
575                         [1, 0, 0, 0],
576                         [0, n1 * n1 * (1 - co) +      co, n1 * n2 * (1 - co) - n3 * si, n1 * n3 * (1 - co) + n2 * si],
577                         [0, n2 * n1 * (1 - co) + n3 * si, n2 * n2 * (1 - co) +      co, n2 * n3 * (1 - co) - n1 * si],
578                         [0, n3 * n1 * (1 - co) - n2 * si, n3 * n2 * (1 - co) + n1 * si, n3 * n3 * (1 - co) +      co]
579                     ];
580                 };
581             }
582         },
583 
584         /**
585          * Transform a point element, that are: {@link Point}, {@link Text}, {@link Image}, {@link Point3D}.
586          * First, the transformation matrix is updated, then do the matrix-vector-multiplication.
587          * <p>
588          * Restricted to 2D transformations.
589          *
590          * @private
591          * @param {JXG.GeometryElement} p element which is transformed
592          * @param {String} 'self' Apply the transformation to the initialCoords instead of the coords if this is set.
593          * @returns {Array}
594          */
595         apply: function (p, self) {
596             var c;
597 
598             this.update();
599             if (this.is3D) {
600                 c = p.coords;
601             } else if (Type.exists(self)) {
602                 c = p.initialCoords.usrCoords;
603             } else {
604                 c = p.coords.usrCoords;
605             }
606 
607             return Mat.matVecMult(this.matrix, c);
608         },
609 
610         /**
611          * Applies a transformation once to a point element, that are: {@link Point}, {@link Text}, {@link Image}, {@link Point3D} or to an array of such elements.
612          * If it is a free 2D point, then it can be dragged around later
613          * and will overwrite the transformed coordinates.
614          * @param {JXG.Point|Array} p
615          */
616         applyOnce: function (p) {
617             var c, len, i;
618 
619             if (!Type.isArray(p)) {
620                 p = [p];
621             }
622 
623             len = p.length;
624             for (i = 0; i < len; i++) {
625                 this.update();
626                 if (this.is3D) {
627                     p[i].coords = Mat.matVecMult(this.matrix, p[i].coords);
628                 } else {
629                     c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords);
630                     p[i].coords.setCoordinates(Const.COORDS_BY_USER, c);
631                 }
632             }
633         },
634 
635         /**
636          * Binds a transformation to a GeometryElement or an array of elements. In every update of the
637          * GeometryElement(s), the transformation is executed. That means, in order to immediately
638          * apply the transformation after calling bindTo, a call of board.update() has to follow.
639          * <p>
640          * The transformation is simply appended to the existing list of transformations of the object.
641          * It is not fused (melt) with an existing transformation.
642          *
643          * @param  {Array|JXG.Object} el JXG.Object or array of JXG.Object to
644          *                            which the transformation is bound to.
645          * @see JXG.Transformation.meltTo
646          */
647         bindTo: function (el) {
648             var i, len;
649             if (Type.isArray(el)) {
650                 len = el.length;
651 
652                 for (i = 0; i < len; i++) {
653                     el[i].transformations.push(this);
654                 }
655             } else {
656                 el.transformations.push(this);
657             }
658         },
659 
660         /**
661          * Binds a transformation to a GeometryElement or an array of elements. In every update of the
662          * GeometryElement(s), the transformation is executed. That means, in order to immediately
663          * apply the transformation after calling meltTo, a call of board.update() has to follow.
664          * <p>
665          * In case the last transformation of the element and this transformation are static,
666          * i.e. the transformation matrices do not depend on other elements,
667          * the transformation will be fused into (multiplied with) the last transformation of
668          * the element. Thus, the list of transformations is kept small.
669          * If the transformation will be the first transformation ot the element, it will be cloned
670          * to prevent side effects.
671          *
672          * @param  {Array|JXG.Object} el JXG.Object or array of JXG.Objects to
673          *                            which the transformation is bound to.
674          *
675          * @see JXG.Transformation#bindTo
676          */
677         meltTo: function (el) {
678             var i, elt, t;
679 
680             if (Type.isArray(el)) {
681                 for (i = 0; i < el.length; i++) {
682                     this.meltTo(el[i]);
683                 }
684             } else {
685                 elt = el.transformations;
686                 if (elt.length > 0 &&
687                     elt[elt.length - 1].isNumericMatrix &&
688                     this.isNumericMatrix
689                 ) {
690                     elt[elt.length - 1].melt(this);
691                 } else {
692                     // Use a clone of the transformation.
693                     // Otherwise, if the transformation is meltTo twice
694                     // the transformation will be changed.
695                     t = this.clone();
696                     elt.push(t);
697                 }
698             }
699         },
700 
701         /**
702          * Create a copy of the transformation in case it is static, i.e.
703          * if the transformation matrix does not depend on other elements.
704          * <p>
705          * If the transformation matrix is not static, null will be returned.
706          *
707          * @returns {JXG.Transformation}
708          */
709         clone: function() {
710             var t = null;
711 
712             if (this.isNumericMatrix) {
713                 t = new JXG.Transformation(this.board, 'none', []);
714                 t.matrix = this.matrix.slice();
715             }
716 
717             return t;
718         },
719 
720         /**
721          * Unused
722          * @deprecated Use setAttribute
723          * @param term
724          */
725         setProperty: function (term) {
726             JXG.deprecated("Transformation.setProperty()", "Transformation.setAttribute()");
727         },
728 
729         /**
730          * Empty method. Unused.
731          * @param {Object} term Key-value pairs of the attributes.
732          */
733         setAttribute: function (term) {},
734 
735         /**
736          * Combine two transformations to one transformation. This only works if
737          * both of transformation matrices consist of numbers solely, and do not
738          * contain functions.
739          *
740          * Multiplies the transformation with a transformation t from the left.
741          * i.e. (this) = (t) join (this)
742          * @param  {JXG.Transform} t Transformation which is the left multiplicand
743          * @returns {JXG.Transform} the transformation object.
744          */
745         melt: function (t) {
746             var res = [];
747 
748             this.update();
749             t.update();
750 
751             res = Mat.matMatMult(t.matrix, this.matrix);
752 
753             this.update = function () {
754                 this.matrix = res;
755             };
756 
757             return this;
758         },
759 
760         // Documented in element.js
761         // Not yet, since transformations are not listed in board.objects.
762         getParents: function () {
763             var p = [[].concat.apply([], this.matrix)];
764 
765             if (this.parents.length !== 0) {
766                 p = this.parents;
767             }
768 
769             return p;
770         }
771     }
772 );
773 
774 /**
775  * @class Define projective 2D transformations like translation, rotation, reflection.
776  * @pseudo
777  * @description A transformation consists of a 3x3 matrix, i.e. it is a projective transformation.
778  * <p>
779  * Internally, a transformation is applied to an element by multiplying the 3x3 matrix from the left to
780  * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order
781  * (z, x, y). The matrix has the form
782  * <pre>
783  * ( a  b  c )   ( z )
784  * ( d  e  f ) * ( x )
785  * ( g  h  i )   ( y )
786  * </pre>
787  * where in general a=1. If b = c = 0, the transformation is called <i>affine</i>.
788  * In this case, finite points will stay finite. This is not the case for general projective coordinates.
789  * <p>
790  * Transformations acting on texts and images are considered to be affine, i.e. b and c are ignored.
791  *
792  * @name Transformation
793  * @augments JXG.Transformation
794  * @constructor
795  * @type JXG.Transformation
796  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
797  * @param {number|function|JXG.GeometryElement} parameters The parameters depend on the transformation type, supplied as attribute 'type'.
798  * Possible transformation types are
799  * <ul>
800  * <li> 'translate'
801  * <li> 'scale'
802  * <li> 'reflect'
803  * <li> 'rotate'
804  * <li> 'shear'
805  * <li> 'generic'
806  * <li> 'matrix'
807  * </ul>
808  * <p>Valid parameters for these types are:
809  * <dl>
810  * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y</b> Translation vector (two numbers or functions).
811  * The transformation matrix for x = a and y = b has the form:
812  * <pre>
813  * ( 1  0  0)   ( z )
814  * ( a  1  0) * ( x )
815  * ( b  0  1)   ( y )
816  * </pre>
817  * </dd>
818  * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y</b> Scale vector (two numbers or functions).
819  * The transformation matrix for scale_x = a and scale_y = b has the form:
820  * <pre>
821  * ( 1  0  0)   ( z )
822  * ( 0  a  0) * ( x )
823  * ( 0  0  b)   ( y )
824  * </pre>
825  * </dd>
826  * <dt><b><tt>type:"rotate"</tt></b></dt><dd> <b>alpha, [point | x, y]</b> The parameters are the angle value in Radians
827  *     (a number or function), and optionally a coordinate pair (two numbers or functions) or a point element defining the
828  *                rotation center. If the rotation center is not given, the transformation rotates around (0,0).
829  * The transformation matrix for angle a and rotating around (0, 0) has the form:
830  * <pre>
831  * ( 1    0        0      )   ( z )
832  * ( 0    cos(a)   -sin(a)) * ( x )
833  * ( 0    sin(a)   cos(a) )   ( y )
834  * </pre>
835  * </dd>
836  * <dt><b><tt>type:"shear"</tt></b></dt><dd><b>shear_x, shear_y</b> Shear vector (two numbers or functions).
837  * The transformation matrix for shear_x = a and shear_y = b has the form:
838  * <pre>
839  * ( 1  0  0)   ( z )
840  * ( 0  1  a) * ( x )
841  * ( 0  b  1)   ( y )
842  * </pre>
843  * </dd>
844  * <dt><b><tt>type:"reflect"</tt></b></dt><dd>The parameters can either be:
845  *    <ul>
846  *      <li> <b>line</b> a line element,
847  *      <li> <b>p, q</b> two point elements,
848  *      <li> <b>p_x, p_y, q_x, q_y</b> four numbers or functions  determining a line through points (p_x, p_y) and (q_x, q_y).
849  *    </ul>
850  * </dd>
851  * <dt><b><tt>type:"generic"</tt></b></dt><dd><b>a, b, c, d, e, f, g, h, i</b> Nine matrix entries (numbers or functions)
852  *  for a generic projective transformation.
853  * The matrix has the form
854  * <pre>
855  * ( a  b  c )   ( z )
856  * ( d  e  f ) * ( x )
857  * ( g  h  i )   ( y )
858  * </pre>
859  * </dd>
860  * <dt><b><tt>type:"matrix"</tt></b></dt><dd><b>M</b> 3x3 transformation matrix containing numbers or functions</dd>
861  * </dl>
862  *
863  *
864  * @see JXG.Transformation#setMatrix
865  *
866  * @example
867  * // The point B is determined by taking twice the vector A from the origin
868  *
869  * var p0 = board.create('point', [0, 3], {name: 'A'}),
870  *     t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type: 'translate'}),
871  *     p1 = board.create('point', [p0, t], {color: 'blue'});
872  *
873  * </pre><div class="jxgbox" id="JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
874  * <script type="text/javascript">
875  *     (function() {
876  *         var board = JXG.JSXGraph.initBoard('JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723',
877  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
878  *     var p0 = board.create('point', [0, 3], {name: 'A'}),
879  *         t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type:'translate'}),
880  *         p1 = board.create('point', [p0, t], {color: 'blue'});
881  *
882  *     })();
883  *
884  * </script><pre>
885  *
886  * @example
887  * // The point B is the result of scaling the point A with factor 2 in horizontal direction
888  * // and with factor 0.5 in vertical direction.
889  *
890  * var p1 = board.create('point', [1, 1]),
891  *     t = board.create('transform', [2, 0.5], {type: 'scale'}),
892  *     p2 = board.create('point', [p1, t], {color: 'blue'});
893  *
894  * </pre><div class="jxgbox" id="JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
895  * <script type="text/javascript">
896  *     (function() {
897  *         var board = JXG.JSXGraph.initBoard('JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723',
898  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
899  *     var p1 = board.create('point', [1, 1]),
900  *         t = board.create('transform', [2, 0.5], {type: 'scale'}),
901  *         p2 = board.create('point', [p1, t], {color: 'blue'});
902  *
903  *     })();
904  *
905  * </script><pre>
906  *
907  * @example
908  * // The point B is rotated around C which gives point D. The angle is determined
909  * // by the vertical height of point A.
910  *
911  * var p0 = board.create('point', [0, 3], {name: 'A'}),
912  *     p1 = board.create('point', [1, 1]),
913  *     p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
914  *
915  *     // angle, rotation center:
916  *     t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
917  *     p3 = board.create('point', [p1, t], {color: 'blue'});
918  *
919  * </pre><div class="jxgbox" id="JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
920  * <script type="text/javascript">
921  *     (function() {
922  *         var board = JXG.JSXGraph.initBoard('JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723',
923  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
924  *     var p0 = board.create('point', [0, 3], {name: 'A'}),
925  *         p1 = board.create('point', [1, 1]),
926  *         p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
927  *
928  *         // angle, rotation center:
929  *         t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
930  *         p3 = board.create('point', [p1, t], {color: 'blue'});
931  *
932  *     })();
933  *
934  * </script><pre>
935  *
936  * @example
937  * // A concatenation of several transformations.
938  * var p1 = board.create('point', [1, 1]),
939  *     t1 = board.create('transform', [-2, -1], {type: 'translate'}),
940  *     t2 = board.create('transform', [Math.PI/4], {type: 'rotate'}),
941  *     t3 = board.create('transform', [2, 1], {type: 'translate'}),
942  *     p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
943  *
944  * </pre><div class="jxgbox" id="JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
945  * <script type="text/javascript">
946  *     (function() {
947  *         var board = JXG.JSXGraph.initBoard('JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723',
948  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
949  *     var p1 = board.create('point', [1, 1]),
950  *         t1 = board.create('transform', [-2, -1], {type:'translate'}),
951  *         t2 = board.create('transform', [Math.PI/4], {type:'rotate'}),
952  *         t3 = board.create('transform', [2, 1], {type:'translate'}),
953  *         p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
954  *
955  *     })();
956  *
957  * </script><pre>
958  *
959  * @example
960  * // Reflection of point A
961  * var p1 = board.create('point', [1, 1]),
962  *     p2 = board.create('point', [1, 3]),
963  *     p3 = board.create('point', [-2, 0]),
964  *     l = board.create('line', [p2, p3]),
965  *     t = board.create('transform', [l], {type: 'reflect'}),  // Possible are l, l.id, l.name
966  *     p4 = board.create('point', [p1, t], {color: 'blue'});
967  *
968  * </pre><div class="jxgbox" id="JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
969  * <script type="text/javascript">
970  *     (function() {
971  *         var board = JXG.JSXGraph.initBoard('JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723',
972  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
973  *     var p1 = board.create('point', [1, 1]),
974  *         p2 = board.create('point', [1, 3]),
975  *         p3 = board.create('point', [-2, 0]),
976  *         l = board.create('line', [p2, p3]),
977  *         t = board.create('transform', [l], {type:'reflect'}),  // Possible are l, l.id, l.name
978  *         p4 = board.create('point', [p1, t], {color: 'blue'});
979  *
980  *     })();
981  *
982  * </script><pre>
983  *
984  * @example
985  * // Type: 'matrix'
986  *         var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]);
987  *         var t1 = board.create('transform', [
988  *             [
989  *                 [1, 0, 0],
990  *                 [0, 1, 0],
991  *                 [() => y.Value(), 0, 1]
992  *             ]
993  *         ], {type: 'matrix'});
994  *
995  *         var A = board.create('point', [2, -3]);
996  *         var B = board.create('point', [A, t1]);
997  *
998  * </pre><div id="JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec" class="jxgbox" style="width: 300px; height: 300px;"></div>
999  * <script type="text/javascript">
1000  *     (function() {
1001  *         var board = JXG.JSXGraph.initBoard('JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec',
1002  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1003  *             var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]);
1004  *             var t1 = board.create('transform', [
1005  *                 [
1006  *                     [1, 0, 0],
1007  *                     [0, 1, 0],
1008  *                     [() => y.Value(), 0, 1]
1009  *                 ]
1010  *             ], {type: 'matrix'});
1011  *
1012  *             var A = board.create('point', [2, -3]);
1013  *             var B = board.create('point', [A, t1]);
1014  *
1015  *     })();
1016  *
1017  * </script><pre>
1018  *
1019  * @example
1020  * // One time application of a transform to points A, B
1021  * var p1 = board.create('point', [1, 1]),
1022  *     p2 = board.create('point', [-1, -2]),
1023  *     t = board.create('transform', [3, 2], {type: 'shear'});
1024  * t.applyOnce([p1, p2]);
1025  *
1026  * </pre><div class="jxgbox" id="JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1027  * <script type="text/javascript">
1028  *     (function() {
1029  *         var board = JXG.JSXGraph.initBoard('JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723',
1030  *             {boundingbox: [-8, 8, 8, -8], axis: true, showcopyright: false, shownavigation: false});
1031  *     var p1 = board.create('point', [1, 1]),
1032  *         p2 = board.create('point', [-1, -2]),
1033  *         t = board.create('transform', [3, 2], {type: 'shear'});
1034  *     t.applyOnce([p1, p2]);
1035  *
1036  *     })();
1037  *
1038  * </script><pre>
1039  *
1040  * @example
1041  * // Construct a square of side length 2 with the
1042  * // help of transformations
1043  *     var sq = [],
1044  *         right = board.create('transform', [2, 0], {type: 'translate'}),
1045  *         up = board.create('transform', [0, 2], {type: 'translate'}),
1046  *         pol, rot, p0;
1047  *
1048  *     // The first point is free
1049  *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
1050  *
1051  *     // Construct the other free points by transformations
1052  *     sq[1] = board.create('point', [sq[0], right]),
1053  *     sq[2] = board.create('point', [sq[0], [right, up]]),
1054  *     sq[3] = board.create('point', [sq[0], up]),
1055  *
1056  *     // Polygon through these four points
1057  *     pol = board.create('polygon', sq, {
1058  *             fillColor:'blue',
1059  *             gradient:'radial',
1060  *             gradientsecondcolor:'white',
1061  *             gradientSecondOpacity:'0'
1062  *     }),
1063  *
1064  *     p0 = board.create('point', [0, 3], {name: 'angle'}),
1065  *     // Rotate the square around point sq[0] by dragging A vertically.
1066  *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
1067  *
1068  *     // Apply the rotation to all but the first point of the square
1069  *     rot.bindTo(sq.slice(1));
1070  *
1071  * </pre><div class="jxgbox" id="JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1072  * <script type="text/javascript">
1073  *     (function() {
1074  *         var board = JXG.JSXGraph.initBoard('JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723',
1075  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1076  *     // Construct a square of side length 2 with the
1077  *     // help of transformations
1078  *     var sq = [],
1079  *         right = board.create('transform', [2, 0], {type: 'translate'}),
1080  *         up = board.create('transform', [0, 2], {type: 'translate'}),
1081  *         pol, rot, p0;
1082  *
1083  *     // The first point is free
1084  *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
1085  *
1086  *     // Construct the other free points by transformations
1087  *     sq[1] = board.create('point', [sq[0], right]),
1088  *     sq[2] = board.create('point', [sq[0], [right, up]]),
1089  *     sq[3] = board.create('point', [sq[0], up]),
1090  *
1091  *     // Polygon through these four points
1092  *     pol = board.create('polygon', sq, {
1093  *             fillColor:'blue',
1094  *             gradient:'radial',
1095  *             gradientsecondcolor:'white',
1096  *             gradientSecondOpacity:'0'
1097  *     }),
1098  *
1099  *     p0 = board.create('point', [0, 3], {name: 'angle'}),
1100  *     // Rotate the square around point sq[0] by dragging A vertically.
1101  *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
1102  *
1103  *     // Apply the rotation to all but the first point of the square
1104  *     rot.bindTo(sq.slice(1));
1105  *
1106  *     })();
1107  *
1108  * </script><pre>
1109  *
1110  * @example
1111  * // Text transformation
1112  * var p0 = board.create('point', [0, 0], {name: 'p_0'});
1113  * var p1 = board.create('point', [3, 0], {name: 'p_1'});
1114  * var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'});
1115  *
1116  * // If p_0 is dragged, translate p_1 and text accordingly
1117  * var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'});
1118  * tOff.bindTo(txt);
1119  * tOff.bindTo(p1);
1120  *
1121  * // Rotate text around p_0 by dragging point p_1
1122  * var tRot = board.create('transform', [
1123  *     () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'});
1124  * tRot.bindTo(txt);
1125  *
1126  * // Scale text by dragging point "p_1"
1127  * // We do this by
1128  * // - moving text by -p_0 (inverse of transformation tOff),
1129  * // - scale the text (because scaling is relative to (0,0))
1130  * // - move the text back by +p_0
1131  * var tOffInv = board.create('transform', [
1132  *         () => -p0.X(),
1133  *         () => -p0.Y()
1134  * ], {type:'translate'});
1135  * var tScale = board.create('transform', [
1136  *         // Some scaling factor
1137  *         () => p1.Dist(p0) / 3,
1138  *         () => p1.Dist(p0) / 3
1139  * ], {type:'scale'});
1140  * tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt);
1141  *
1142  * </pre><div id="JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66" class="jxgbox" style="width: 300px; height: 300px;"></div>
1143  * <script type="text/javascript">
1144  *     (function() {
1145  *         var board = JXG.JSXGraph.initBoard('JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66',
1146  *             {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false});
1147  *     var p0 = board.create('point', [0, 0], {name: 'p_0'});
1148  *     var p1 = board.create('point', [3, 0], {name: 'p_1'});
1149  *     var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'});
1150  *
1151  *     // If p_0 is dragged, translate p_1 and text accordingly
1152  *     var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'});
1153  *     tOff.bindTo(txt);
1154  *     tOff.bindTo(p1);
1155  *
1156  *     // Rotate text around p_0 by dragging point p_1
1157  *     var tRot = board.create('transform', [
1158  *         () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'});
1159  *     tRot.bindTo(txt);
1160  *
1161  *     // Scale text by dragging point "p_1"
1162  *     // We do this by
1163  *     // - moving text by -p_0 (inverse of transformation tOff),
1164  *     // - scale the text (because scaling is relative to (0,0))
1165  *     // - move the text back by +p_0
1166  *     var tOffInv = board.create('transform', [
1167  *             () => -p0.X(),
1168  *             () => -p0.Y()
1169  *     ], {type:'translate'});
1170  *     var tScale = board.create('transform', [
1171  *             // Some scaling factor
1172  *             () => p1.Dist(p0) / 3,
1173  *             () => p1.Dist(p0) / 3
1174  *     ], {type:'scale'});
1175  *     tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt);
1176  *
1177  *     })();
1178  *
1179  * </script><pre>
1180  *
1181  */
1182 JXG.createTransform = function (board, parents, attributes) {
1183     return new JXG.Transformation(board, attributes.type, parents);
1184 };
1185 
1186 JXG.registerElement('transform', JXG.createTransform);
1187 
1188 /**
1189  * @class Define projective 3D transformations like translation, rotation, reflection.
1190  * @pseudo
1191  * @description A transformation consists of a 4x4 matrix, i.e. it is a projective transformation.
1192  * <p>
1193  * Internally, a transformation is applied to an element by multiplying the 4x4 matrix from the left to
1194  * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order
1195  * (w, x, y, z). If the coordinate is a finite point, w=1. The matrix has the form
1196  * <pre>
1197  * ( a b c d)   ( w )
1198  * ( e f g h) * ( x )
1199  * ( i j k l)   ( y )
1200  * ( m n o p)   ( z )
1201  * </pre>
1202  * where in general a=1. If b = c = d = 0, the transformation is called <i>affine</i>.
1203  * In this case, finite points will stay finite. This is not the case for general projective coordinates.
1204  * <p>
1205  *
1206  * @name Transformation3D
1207  * @augments JXG.Transformation
1208  * @constructor
1209  * @type JXG.Transformation
1210  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1211  * @param {number|function|JXG.GeometryElement3D} parameters The parameters depend on the transformation type, supplied as attribute 'type'.
1212  *  Possible transformation types are
1213  * <ul>
1214  * <li> 'translate'
1215  * <li> 'scale'
1216  * <li> 'rotate'
1217  * <li> 'rotateX'
1218  * <li> 'rotateY'
1219  * <li> 'rotateZ'
1220  * </ul>
1221  * <p>Valid parameters for these types are:
1222  * <dl>
1223  * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y, z</b> Translation vector (three numbers or functions).
1224  * The transformation matrix for x = a, y = b, and z = c has the form:
1225  * <pre>
1226  * ( 1  0  0  0)   ( w )
1227  * ( a  1  0  0) * ( x )
1228  * ( b  0  1  0)   ( y )
1229  * ( c  0  0  c)   ( z )
1230  * </pre>
1231  * </dd>
1232  * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y, scale_z</b> Scale vector (three numbers or functions).
1233  * The transformation matrix for scale_x = a, scale_y = b, scale_z = c has the form:
1234  * <pre>
1235  * ( 1  0  0  0)   ( w )
1236  * ( 0  a  0  0) * ( x )
1237  * ( 0  0  b  0)   ( y )
1238  * ( 0  0  0  c)   ( z )
1239  * </pre>
1240  * </dd>
1241  * <dt><b><tt>type:"rotate"</tt></b></dt><dd><b>a, n</b> angle (in radians), normal.
1242  * Rotate with angle a around the normal vector n.
1243  * </dd>
1244  * <dt><b><tt>type:"rotateX"</tt></b></dt><dd><b>a</b> angle (in radians).
1245  * Rotate with angle a around the normal vector (1, 0, 0).
1246  * </dd>
1247  * <dt><b><tt>type:"rotateY"</tt></b></dt><dd><b>a</b> angle (in radians).
1248  * Rotate with angle a around the normal vector (0, 1, 0).
1249  * </dd>
1250  * <dt><b><tt>type:"rotateZ"</tt></b></dt><dd><b>a</b> angle (in radians).
1251  * Rotate with angle a around the normal vector (0, 0, 1).
1252  * </dd>
1253  * </dl>
1254 */
1255 JXG.createTransform3D = function (board, parents, attributes) {
1256     return new JXG.Transformation(board, attributes.type, parents, true);
1257 };
1258 
1259 JXG.registerElement('transform3d', JXG.createTransform3D);
1260 
1261 export default JXG.Transformation;
1262 
1263