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                 params.splice(1, 0, [1, 0, 0]);
511                 this.setMatrix3D(view, 'rotate', params);
512             } else if (type === 'rotateY') {
513                 params.splice(1, 0, [0, 1, 0]);
514                 this.setMatrix3D(view, 'rotate', params);
515             } else if (type === 'rotateZ') {
516                 params.splice(1, 0, [0, 0, 1]);
517                 this.setMatrix3D(view, 'rotate', params);
518             } else if (type === 'rotate') {
519                 if (params.length < 2) {
520                     throw new Error("JSXGraph: 3D rotate transformation needs 2 or 3 parameters.");
521                 }
522                 if (params.length === 3 && !Type.isFunction(params[2]) && !Type.isArray(params[2])) {
523                     this.evalParam = Type.createEvalFunction(board, params, 2);
524                     params[2] = view.select(params[2]);
525                 } else {
526                     this.evalParam = Type.createEvalFunction(board, params, params.length);
527                 }
528                 this.update = function () {
529                     var a = this.evalParam(0), // angle
530                         n = this.evalParam(1), // normal
531                         p = [1, 0, 0, 0],
532                         co = Math.cos(a),
533                         si = Math.sin(a),
534                         n1, n2, n3,
535                         m1 = [
536                             [1, 0, 0, 0],
537                             [0, 1, 0, 0],
538                             [0, 0, 1, 0],
539                             [0, 0, 0, 1]
540                         ],
541                         m2 = [
542                             [1, 0, 0, 0],
543                             [0, 1, 0, 0],
544                             [0, 0, 1, 0],
545                             [0, 0, 0, 1]
546                         ],
547                         nrm = Mat.norm(n);
548 
549                     if (n.length === 3) {
550                         n1 = n[0] / nrm;
551                         n2 = n[1] / nrm;
552                         n3 = n[2] / nrm;
553                     } else {
554                         n1 = n[1] / nrm;
555                         n2 = n[2] / nrm;
556                         n3 = n[3] / nrm;
557                     }
558                     if (params.length === 3) {
559                         if (params.length === 3 && Type.exists(params[2].is3D)) {
560                             p = params[2].coords.slice();
561                         } else {
562                             p = this.evalParam(2);
563                         }
564                         if (p.length === 3) {
565                             p.unshift(1);
566                         }
567                         m1[1][0] = -p[1];
568                         m1[2][0] = -p[2];
569                         m1[3][0] = -p[3];
570 
571                         m2[1][0] = p[1];
572                         m2[2][0] = p[2];
573                         m2[3][0] = p[3];
574                     }
575 
576                     this.matrix = [
577                         [1, 0, 0, 0],
578                         [0, n1 * n1 * (1 - co) +      co, n1 * n2 * (1 - co) - n3 * si, n1 * n3 * (1 - co) + n2 * si],
579                         [0, n2 * n1 * (1 - co) + n3 * si, n2 * n2 * (1 - co) +      co, n2 * n3 * (1 - co) - n1 * si],
580                         [0, n3 * n1 * (1 - co) - n2 * si, n3 * n2 * (1 - co) + n1 * si, n3 * n3 * (1 - co) +      co]
581                     ];
582                     this.matrix = Mat.matMatMult(this.matrix, m1);
583                     this.matrix = Mat.matMatMult(m2, this.matrix);
584                 };
585             }
586         },
587 
588         /**
589          * Transform a point element, that are: {@link Point}, {@link Text}, {@link Image}, {@link Point3D}.
590          * First, the transformation matrix is updated, then do the matrix-vector-multiplication.
591          * <p>
592          * Restricted to 2D transformations.
593          *
594          * @private
595          * @param {JXG.GeometryElement} p element which is transformed
596          * @param {String} 'self' Apply the transformation to the initialCoords instead of the coords if this is set.
597          * @returns {Array}
598          */
599         apply: function (p, self) {
600             var c;
601 
602             this.update();
603             if (this.is3D) {
604                 c = p.coords;
605             } else if (Type.exists(self)) {
606                 c = p.initialCoords.usrCoords;
607             } else {
608                 c = p.coords.usrCoords;
609             }
610 
611             return Mat.matVecMult(this.matrix, c);
612         },
613 
614         /**
615          * 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.
616          * If it is a free 2D point, then it can be dragged around later
617          * and will overwrite the transformed coordinates.
618          * @param {JXG.Point|Array} p
619          */
620         applyOnce: function (p) {
621             var c, len, i;
622 
623             if (!Type.isArray(p)) {
624                 p = [p];
625             }
626 
627             len = p.length;
628             for (i = 0; i < len; i++) {
629                 this.update();
630                 if (this.is3D) {
631                     p[i].coords = Mat.matVecMult(this.matrix, p[i].coords);
632                 } else {
633                     c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords);
634                     p[i].coords.setCoordinates(Const.COORDS_BY_USER, c);
635                 }
636             }
637         },
638 
639         /**
640          * Binds a transformation to a GeometryElement or an array of elements. In every update of the
641          * GeometryElement(s), the transformation is executed. That means, in order to immediately
642          * apply the transformation after calling bindTo, a call of board.update() has to follow.
643          * <p>
644          * The transformation is simply appended to the existing list of transformations of the object.
645          * It is not fused (melt) with an existing transformation.
646          *
647          * @param  {Array|JXG.Object} el JXG.Object or array of JXG.Object to
648          *                            which the transformation is bound to.
649          * @see JXG.Transformation.meltTo
650          */
651         bindTo: function (el) {
652             var i, len;
653             if (Type.isArray(el)) {
654                 len = el.length;
655 
656                 for (i = 0; i < len; i++) {
657                     el[i].transformations.push(this);
658                 }
659             } else {
660                 el.transformations.push(this);
661             }
662         },
663 
664         /**
665          * Binds a transformation to a GeometryElement or an array of elements. In every update of the
666          * GeometryElement(s), the transformation is executed. That means, in order to immediately
667          * apply the transformation after calling meltTo, a call of board.update() has to follow.
668          * <p>
669          * In case the last transformation of the element and this transformation are static,
670          * i.e. the transformation matrices do not depend on other elements,
671          * the transformation will be fused into (multiplied with) the last transformation of
672          * the element. Thus, the list of transformations is kept small.
673          * If the transformation will be the first transformation ot the element, it will be cloned
674          * to prevent side effects.
675          *
676          * @param  {Array|JXG.Object} el JXG.Object or array of JXG.Objects to
677          *                            which the transformation is bound to.
678          *
679          * @see JXG.Transformation#bindTo
680          */
681         meltTo: function (el) {
682             var i, elt, t;
683 
684             if (Type.isArray(el)) {
685                 for (i = 0; i < el.length; i++) {
686                     this.meltTo(el[i]);
687                 }
688             } else {
689                 elt = el.transformations;
690                 if (elt.length > 0 &&
691                     elt[elt.length - 1].isNumericMatrix &&
692                     this.isNumericMatrix
693                 ) {
694                     elt[elt.length - 1].melt(this);
695                 } else {
696                     // Use a clone of the transformation.
697                     // Otherwise, if the transformation is meltTo twice
698                     // the transformation will be changed.
699                     t = this.clone();
700                     elt.push(t);
701                 }
702             }
703         },
704 
705         /**
706          * Create a copy of the transformation in case it is static, i.e.
707          * if the transformation matrix does not depend on other elements.
708          * <p>
709          * If the transformation matrix is not static, null will be returned.
710          *
711          * @returns {JXG.Transformation}
712          */
713         clone: function() {
714             var t = null;
715 
716             if (this.isNumericMatrix) {
717                 t = new JXG.Transformation(this.board, 'none', []);
718                 t.matrix = this.matrix.slice();
719             }
720 
721             return t;
722         },
723 
724         /**
725          * Unused
726          * @deprecated Use setAttribute
727          * @param term
728          */
729         setProperty: function (term) {
730             JXG.deprecated("Transformation.setProperty()", "Transformation.setAttribute()");
731         },
732 
733         /**
734          * Empty method. Unused.
735          * @param {Object} term Key-value pairs of the attributes.
736          */
737         setAttribute: function (term) {},
738 
739         /**
740          * Combine two transformations to one transformation. This only works if
741          * both of transformation matrices consist of numbers solely, and do not
742          * contain functions.
743          *
744          * Multiplies the transformation with a transformation t from the left.
745          * i.e. (this) = (t) join (this)
746          * @param  {JXG.Transform} t Transformation which is the left multiplicand
747          * @returns {JXG.Transform} the transformation object.
748          */
749         melt: function (t) {
750             var res = [];
751 
752             this.update();
753             t.update();
754 
755             res = Mat.matMatMult(t.matrix, this.matrix);
756 
757             this.update = function () {
758                 this.matrix = res;
759             };
760 
761             return this;
762         },
763 
764         // Documented in element.js
765         // Not yet, since transformations are not listed in board.objects.
766         getParents: function () {
767             var p = [[].concat.apply([], this.matrix)];
768 
769             if (this.parents.length !== 0) {
770                 p = this.parents;
771             }
772 
773             return p;
774         }
775     }
776 );
777 
778 /**
779  * @class Define projective 2D transformations like translation, rotation, reflection.
780  * @pseudo
781  * @description A transformation consists of a 3x3 matrix, i.e. it is a projective transformation.
782  * <p>
783  * Internally, a transformation is applied to an element by multiplying the 3x3 matrix from the left to
784  * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order
785  * (z, x, y). The matrix has the form
786  * <pre>
787  * ( a  b  c )   ( z )
788  * ( d  e  f ) * ( x )
789  * ( g  h  i )   ( y )
790  * </pre>
791  * where in general a=1. If b = c = 0, the transformation is called <i>affine</i>.
792  * In this case, finite points will stay finite. This is not the case for general projective coordinates.
793  * <p>
794  * Transformations acting on texts and images are considered to be affine, i.e. b and c are ignored.
795  *
796  * @name Transformation
797  * @augments JXG.Transformation
798  * @constructor
799  * @type JXG.Transformation
800  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
801  * @param {number|function|JXG.GeometryElement} parameters The parameters depend on the transformation type, supplied as attribute 'type'.
802  * Possible transformation types are
803  * <ul>
804  * <li> 'translate'
805  * <li> 'scale'
806  * <li> 'reflect'
807  * <li> 'rotate'
808  * <li> 'shear'
809  * <li> 'generic'
810  * <li> 'matrix'
811  * </ul>
812  * <p>Valid parameters for these types are:
813  * <dl>
814  * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y</b> Translation vector (two numbers or functions).
815  * The transformation matrix for x = a and y = b has the form:
816  * <pre>
817  * ( 1  0  0)   ( z )
818  * ( a  1  0) * ( x )
819  * ( b  0  1)   ( y )
820  * </pre>
821  * </dd>
822  * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y</b> Scale vector (two numbers or functions).
823  * The transformation matrix for scale_x = a and scale_y = b has the form:
824  * <pre>
825  * ( 1  0  0)   ( z )
826  * ( 0  a  0) * ( x )
827  * ( 0  0  b)   ( y )
828  * </pre>
829  * </dd>
830  * <dt><b><tt>type:"rotate"</tt></b></dt><dd> <b>alpha, [point | x, y]</b> The parameters are the angle value in Radians
831  *     (a number or function), and optionally a coordinate pair (two numbers or functions) or a point element defining the
832  *                rotation center. If the rotation center is not given, the transformation rotates around (0,0).
833  * The transformation matrix for angle a and rotating around (0, 0) has the form:
834  * <pre>
835  * ( 1    0        0      )   ( z )
836  * ( 0    cos(a)   -sin(a)) * ( x )
837  * ( 0    sin(a)   cos(a) )   ( y )
838  * </pre>
839  * </dd>
840  * <dt><b><tt>type:"shear"</tt></b></dt><dd><b>shear_x, shear_y</b> Shear vector (two numbers or functions).
841  * The transformation matrix for shear_x = a and shear_y = b has the form:
842  * <pre>
843  * ( 1  0  0)   ( z )
844  * ( 0  1  a) * ( x )
845  * ( 0  b  1)   ( y )
846  * </pre>
847  * </dd>
848  * <dt><b><tt>type:"reflect"</tt></b></dt><dd>The parameters can either be:
849  *    <ul>
850  *      <li> <b>line</b> a line element,
851  *      <li> <b>p, q</b> two point elements,
852  *      <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).
853  *    </ul>
854  * </dd>
855  * <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)
856  *  for a generic projective transformation.
857  * The matrix has the form
858  * <pre>
859  * ( a  b  c )   ( z )
860  * ( d  e  f ) * ( x )
861  * ( g  h  i )   ( y )
862  * </pre>
863  * </dd>
864  * <dt><b><tt>type:"matrix"</tt></b></dt><dd><b>M</b> 3x3 transformation matrix containing numbers or functions</dd>
865  * </dl>
866  *
867  *
868  * @see JXG.Transformation#setMatrix
869  *
870  * @example
871  * // The point B is determined by taking twice the vector A from the origin
872  *
873  * var p0 = board.create('point', [0, 3], {name: 'A'}),
874  *     t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type: 'translate'}),
875  *     p1 = board.create('point', [p0, t], {color: 'blue'});
876  *
877  * </pre><div class="jxgbox" id="JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
878  * <script type="text/javascript">
879  *     (function() {
880  *         var board = JXG.JSXGraph.initBoard('JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723',
881  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
882  *     var p0 = board.create('point', [0, 3], {name: 'A'}),
883  *         t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type:'translate'}),
884  *         p1 = board.create('point', [p0, t], {color: 'blue'});
885  *
886  *     })();
887  *
888  * </script><pre>
889  *
890  * @example
891  * // The point B is the result of scaling the point A with factor 2 in horizontal direction
892  * // and with factor 0.5 in vertical direction.
893  *
894  * var p1 = board.create('point', [1, 1]),
895  *     t = board.create('transform', [2, 0.5], {type: 'scale'}),
896  *     p2 = board.create('point', [p1, t], {color: 'blue'});
897  *
898  * </pre><div class="jxgbox" id="JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
899  * <script type="text/javascript">
900  *     (function() {
901  *         var board = JXG.JSXGraph.initBoard('JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723',
902  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
903  *     var p1 = board.create('point', [1, 1]),
904  *         t = board.create('transform', [2, 0.5], {type: 'scale'}),
905  *         p2 = board.create('point', [p1, t], {color: 'blue'});
906  *
907  *     })();
908  *
909  * </script><pre>
910  *
911  * @example
912  * // The point B is rotated around C which gives point D. The angle is determined
913  * // by the vertical height of point A.
914  *
915  * var p0 = board.create('point', [0, 3], {name: 'A'}),
916  *     p1 = board.create('point', [1, 1]),
917  *     p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
918  *
919  *     // angle, rotation center:
920  *     t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
921  *     p3 = board.create('point', [p1, t], {color: 'blue'});
922  *
923  * </pre><div class="jxgbox" id="JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
924  * <script type="text/javascript">
925  *     (function() {
926  *         var board = JXG.JSXGraph.initBoard('JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723',
927  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
928  *     var p0 = board.create('point', [0, 3], {name: 'A'}),
929  *         p1 = board.create('point', [1, 1]),
930  *         p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
931  *
932  *         // angle, rotation center:
933  *         t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
934  *         p3 = board.create('point', [p1, t], {color: 'blue'});
935  *
936  *     })();
937  *
938  * </script><pre>
939  *
940  * @example
941  * // A concatenation of several transformations.
942  * var p1 = board.create('point', [1, 1]),
943  *     t1 = board.create('transform', [-2, -1], {type: 'translate'}),
944  *     t2 = board.create('transform', [Math.PI/4], {type: 'rotate'}),
945  *     t3 = board.create('transform', [2, 1], {type: 'translate'}),
946  *     p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
947  *
948  * </pre><div class="jxgbox" id="JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
949  * <script type="text/javascript">
950  *     (function() {
951  *         var board = JXG.JSXGraph.initBoard('JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723',
952  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
953  *     var p1 = board.create('point', [1, 1]),
954  *         t1 = board.create('transform', [-2, -1], {type:'translate'}),
955  *         t2 = board.create('transform', [Math.PI/4], {type:'rotate'}),
956  *         t3 = board.create('transform', [2, 1], {type:'translate'}),
957  *         p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
958  *
959  *     })();
960  *
961  * </script><pre>
962  *
963  * @example
964  * // Reflection of point A
965  * var p1 = board.create('point', [1, 1]),
966  *     p2 = board.create('point', [1, 3]),
967  *     p3 = board.create('point', [-2, 0]),
968  *     l = board.create('line', [p2, p3]),
969  *     t = board.create('transform', [l], {type: 'reflect'}),  // Possible are l, l.id, l.name
970  *     p4 = board.create('point', [p1, t], {color: 'blue'});
971  *
972  * </pre><div class="jxgbox" id="JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
973  * <script type="text/javascript">
974  *     (function() {
975  *         var board = JXG.JSXGraph.initBoard('JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723',
976  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
977  *     var p1 = board.create('point', [1, 1]),
978  *         p2 = board.create('point', [1, 3]),
979  *         p3 = board.create('point', [-2, 0]),
980  *         l = board.create('line', [p2, p3]),
981  *         t = board.create('transform', [l], {type:'reflect'}),  // Possible are l, l.id, l.name
982  *         p4 = board.create('point', [p1, t], {color: 'blue'});
983  *
984  *     })();
985  *
986  * </script><pre>
987  *
988  * @example
989  * // Type: 'matrix'
990  *         var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]);
991  *         var t1 = board.create('transform', [
992  *             [
993  *                 [1, 0, 0],
994  *                 [0, 1, 0],
995  *                 [() => y.Value(), 0, 1]
996  *             ]
997  *         ], {type: 'matrix'});
998  *
999  *         var A = board.create('point', [2, -3]);
1000  *         var B = board.create('point', [A, t1]);
1001  *
1002  * </pre><div id="JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec" class="jxgbox" style="width: 300px; height: 300px;"></div>
1003  * <script type="text/javascript">
1004  *     (function() {
1005  *         var board = JXG.JSXGraph.initBoard('JXGd2bfd46c-3c0c-45c5-a92b-583fad0eb3ec',
1006  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1007  *             var y = board.create('slider', [[-3, 1], [-3, 4], [0, 1, 6]]);
1008  *             var t1 = board.create('transform', [
1009  *                 [
1010  *                     [1, 0, 0],
1011  *                     [0, 1, 0],
1012  *                     [() => y.Value(), 0, 1]
1013  *                 ]
1014  *             ], {type: 'matrix'});
1015  *
1016  *             var A = board.create('point', [2, -3]);
1017  *             var B = board.create('point', [A, t1]);
1018  *
1019  *     })();
1020  *
1021  * </script><pre>
1022  *
1023  * @example
1024  * // One time application of a transform to points A, B
1025  * var p1 = board.create('point', [1, 1]),
1026  *     p2 = board.create('point', [-1, -2]),
1027  *     t = board.create('transform', [3, 2], {type: 'shear'});
1028  * t.applyOnce([p1, p2]);
1029  *
1030  * </pre><div class="jxgbox" id="JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1031  * <script type="text/javascript">
1032  *     (function() {
1033  *         var board = JXG.JSXGraph.initBoard('JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723',
1034  *             {boundingbox: [-8, 8, 8, -8], axis: true, showcopyright: false, shownavigation: false});
1035  *     var p1 = board.create('point', [1, 1]),
1036  *         p2 = board.create('point', [-1, -2]),
1037  *         t = board.create('transform', [3, 2], {type: 'shear'});
1038  *     t.applyOnce([p1, p2]);
1039  *
1040  *     })();
1041  *
1042  * </script><pre>
1043  *
1044  * @example
1045  * // Construct a square of side length 2 with the
1046  * // help of transformations
1047  *     var sq = [],
1048  *         right = board.create('transform', [2, 0], {type: 'translate'}),
1049  *         up = board.create('transform', [0, 2], {type: 'translate'}),
1050  *         pol, rot, p0;
1051  *
1052  *     // The first point is free
1053  *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
1054  *
1055  *     // Construct the other free points by transformations
1056  *     sq[1] = board.create('point', [sq[0], right]),
1057  *     sq[2] = board.create('point', [sq[0], [right, up]]),
1058  *     sq[3] = board.create('point', [sq[0], up]),
1059  *
1060  *     // Polygon through these four points
1061  *     pol = board.create('polygon', sq, {
1062  *             fillColor:'blue',
1063  *             gradient:'radial',
1064  *             gradientsecondcolor:'white',
1065  *             gradientSecondOpacity:'0'
1066  *     }),
1067  *
1068  *     p0 = board.create('point', [0, 3], {name: 'angle'}),
1069  *     // Rotate the square around point sq[0] by dragging A vertically.
1070  *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
1071  *
1072  *     // Apply the rotation to all but the first point of the square
1073  *     rot.bindTo(sq.slice(1));
1074  *
1075  * </pre><div class="jxgbox" id="JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
1076  * <script type="text/javascript">
1077  *     (function() {
1078  *         var board = JXG.JSXGraph.initBoard('JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723',
1079  *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
1080  *     // Construct a square of side length 2 with the
1081  *     // help of transformations
1082  *     var sq = [],
1083  *         right = board.create('transform', [2, 0], {type: 'translate'}),
1084  *         up = board.create('transform', [0, 2], {type: 'translate'}),
1085  *         pol, rot, p0;
1086  *
1087  *     // The first point is free
1088  *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
1089  *
1090  *     // Construct the other free points by transformations
1091  *     sq[1] = board.create('point', [sq[0], right]),
1092  *     sq[2] = board.create('point', [sq[0], [right, up]]),
1093  *     sq[3] = board.create('point', [sq[0], up]),
1094  *
1095  *     // Polygon through these four points
1096  *     pol = board.create('polygon', sq, {
1097  *             fillColor:'blue',
1098  *             gradient:'radial',
1099  *             gradientsecondcolor:'white',
1100  *             gradientSecondOpacity:'0'
1101  *     }),
1102  *
1103  *     p0 = board.create('point', [0, 3], {name: 'angle'}),
1104  *     // Rotate the square around point sq[0] by dragging A vertically.
1105  *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
1106  *
1107  *     // Apply the rotation to all but the first point of the square
1108  *     rot.bindTo(sq.slice(1));
1109  *
1110  *     })();
1111  *
1112  * </script><pre>
1113  *
1114  * @example
1115  * // Text transformation
1116  * var p0 = board.create('point', [0, 0], {name: 'p_0'});
1117  * var p1 = board.create('point', [3, 0], {name: 'p_1'});
1118  * var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'});
1119  *
1120  * // If p_0 is dragged, translate p_1 and text accordingly
1121  * var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'});
1122  * tOff.bindTo(txt);
1123  * tOff.bindTo(p1);
1124  *
1125  * // Rotate text around p_0 by dragging point p_1
1126  * var tRot = board.create('transform', [
1127  *     () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'});
1128  * tRot.bindTo(txt);
1129  *
1130  * // Scale text by dragging point "p_1"
1131  * // We do this by
1132  * // - moving text by -p_0 (inverse of transformation tOff),
1133  * // - scale the text (because scaling is relative to (0,0))
1134  * // - move the text back by +p_0
1135  * var tOffInv = board.create('transform', [
1136  *         () => -p0.X(),
1137  *         () => -p0.Y()
1138  * ], {type:'translate'});
1139  * var tScale = board.create('transform', [
1140  *         // Some scaling factor
1141  *         () => p1.Dist(p0) / 3,
1142  *         () => p1.Dist(p0) / 3
1143  * ], {type:'scale'});
1144  * tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt);
1145  *
1146  * </pre><div id="JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66" class="jxgbox" style="width: 300px; height: 300px;"></div>
1147  * <script type="text/javascript">
1148  *     (function() {
1149  *         var board = JXG.JSXGraph.initBoard('JXG50d6d546-3b91-41dd-8c0f-3eaa6cff7e66',
1150  *             {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false});
1151  *     var p0 = board.create('point', [0, 0], {name: 'p_0'});
1152  *     var p1 = board.create('point', [3, 0], {name: 'p_1'});
1153  *     var txt = board.create('text',[0.5, 0, 'Hello World'], {display:'html'});
1154  *
1155  *     // If p_0 is dragged, translate p_1 and text accordingly
1156  *     var tOff = board.create('transform', [() => p0.X(), () => p0.Y()], {type:'translate'});
1157  *     tOff.bindTo(txt);
1158  *     tOff.bindTo(p1);
1159  *
1160  *     // Rotate text around p_0 by dragging point p_1
1161  *     var tRot = board.create('transform', [
1162  *         () => Math.atan2(p1.Y() - p0.Y(), p1.X() - p0.X()), p0], {type:'rotate'});
1163  *     tRot.bindTo(txt);
1164  *
1165  *     // Scale text by dragging point "p_1"
1166  *     // We do this by
1167  *     // - moving text by -p_0 (inverse of transformation tOff),
1168  *     // - scale the text (because scaling is relative to (0,0))
1169  *     // - move the text back by +p_0
1170  *     var tOffInv = board.create('transform', [
1171  *             () => -p0.X(),
1172  *             () => -p0.Y()
1173  *     ], {type:'translate'});
1174  *     var tScale = board.create('transform', [
1175  *             // Some scaling factor
1176  *             () => p1.Dist(p0) / 3,
1177  *             () => p1.Dist(p0) / 3
1178  *     ], {type:'scale'});
1179  *     tOffInv.bindTo(txt); tScale.bindTo(txt); tOff.bindTo(txt);
1180  *
1181  *     })();
1182  *
1183  * </script><pre>
1184  *
1185  */
1186 JXG.createTransform = function (board, parents, attributes) {
1187     return new JXG.Transformation(board, attributes.type, parents);
1188 };
1189 
1190 JXG.registerElement('transform', JXG.createTransform);
1191 
1192 /**
1193  * @class Define projective 3D transformations like translation, rotation, reflection.
1194  * @pseudo
1195  * @description A transformation consists of a 4x4 matrix, i.e. it is a projective transformation.
1196  * <p>
1197  * Internally, a transformation is applied to an element by multiplying the 4x4 matrix from the left to
1198  * the homogeneous coordinates of the element. JSXGraph represents homogeneous coordinates in the order
1199  * (w, x, y, z). If the coordinate is a finite point, w=1. The matrix has the form
1200  * <pre>
1201  * ( a b c d)   ( w )
1202  * ( e f g h) * ( x )
1203  * ( i j k l)   ( y )
1204  * ( m n o p)   ( z )
1205  * </pre>
1206  * where in general a=1. If b = c = d = 0, the transformation is called <i>affine</i>.
1207  * In this case, finite points will stay finite. This is not the case for general projective coordinates.
1208  * <p>
1209  *
1210  * @name Transformation3D
1211  * @augments JXG.Transformation
1212  * @constructor
1213  * @type JXG.Transformation
1214  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
1215  * @param {number|function|JXG.GeometryElement3D} parameters The parameters depend on the transformation type, supplied as attribute 'type'.
1216  *  Possible transformation types are
1217  * <ul>
1218  * <li> 'translate'
1219  * <li> 'scale'
1220  * <li> 'rotate'
1221  * <li> 'rotateX'
1222  * <li> 'rotateY'
1223  * <li> 'rotateZ'
1224  * </ul>
1225  * <p>Valid parameters for these types are:
1226  * <dl>
1227  * <dt><b><tt>type:"translate"</tt></b></dt><dd><b>x, y, z</b> Translation vector (three numbers or functions).
1228  * The transformation matrix for x = a, y = b, and z = c has the form:
1229  * <pre>
1230  * ( 1  0  0  0)   ( w )
1231  * ( a  1  0  0) * ( x )
1232  * ( b  0  1  0)   ( y )
1233  * ( c  0  0  c)   ( z )
1234  * </pre>
1235  * </dd>
1236  * <dt><b><tt>type:"scale"</tt></b></dt><dd><b>scale_x, scale_y, scale_z</b> Scale vector (three numbers or functions).
1237  * The transformation matrix for scale_x = a, scale_y = b, scale_z = c has the form:
1238  * <pre>
1239  * ( 1  0  0  0)   ( w )
1240  * ( 0  a  0  0) * ( x )
1241  * ( 0  0  b  0)   ( y )
1242  * ( 0  0  0  c)   ( z )
1243  * </pre>
1244  * </dd>
1245  * <dt><b><tt>type:"rotate"</tt></b></dt><dd><b>a, n, [p=[0,0,0]]</b> angle (in radians), normal, [point].
1246  * Rotate with angle a around the normal vector n through the point p.
1247  * </dd>
1248  * <dt><b><tt>type:"rotateX"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point].
1249  * Rotate with angle a around the normal vector (1, 0, 0) through the point p.
1250  * </dd>
1251  * <dt><b><tt>type:"rotateY"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point].
1252  * Rotate with angle a around the normal vector (0, 1, 0) through the point p.
1253  * </dd>
1254  * <dt><b><tt>type:"rotateZ"</tt></b></dt><dd><b>a, [p=[0,0,0]]</b> angle (in radians), [point].
1255  * Rotate with angle a around the normal vector (0, 0, 1) through the point p.
1256  * </dd>
1257  * </dl>
1258  * @example
1259  *    var bound = [-5, 5];
1260  *    var view = board.create('view3d',
1261  *        [[-6, -3], [8, 8],
1262  *        [bound, bound, bound]];
1263  *
1264  *    var slid = board.create('slider', [[-4, 4], [0, 4], [0, 0, 5]])
1265  *
1266  *    var p1 = view.create('point3d', [1, 2, 2], { name: 'drag me', size: 5 });
1267  *
1268  *    // translate from p1 by some fixed or function amount
1269  *    var t1 = view.create('transform3d', [2, 3, 2], { type: 'translate' });
1270  *    var t2 = view.create('transform3d', [()=>slid.Value()+3,0,0], { type: 'translate' })
1271  *
1272  *    view.create('point3d', [p1, t1], { name: 'translate fixed', size: 5 });
1273  *    view.create('point3d', [p1, t2], { name: 'translate by func', size: 5 });
1274  * </pre><div id="JXG6c7d7404-758a-44eb-802c-0001" class="jxgbox" style="width: 300px; height: 300px;"></div>
1275  * <script type="text/javascript">
1276  *    var board = JXG.JSXGraph.initBoard('JXG6c7d7404-758a-44eb-802c-0001',
1277  *             {boundingbox: [-8, 8, 8,-8], pan: {enabled: false}, axis: false, showcopyright: false, shownavigation: false});
1278  *    var bound = [-5, 5];
1279  *    var view = board.create('view3d',
1280  *        [[-6, -3], [8, 8],
1281  *        [bound, bound, bound]]);
1282  *
1283  *    var slid = board.create('slider', [[-4, 4], [0, 4], [0, 0, 5]])
1284  *
1285  *    var p1 = view.create('point3d', [1, 2, 2], { name: 'drag me', size: 5 });
1286  *
1287  *    // translate from p1 by some fixed or function amount
1288  *    var t1 = view.create('transform3d', [2, 3, 2], { type: 'translate' });
1289  *    var t2 = view.create('transform3d', [()=>slid.Value()+3,0,0], { type: 'translate' })
1290  *
1291  *    view.create('point3d', [p1, t1], { name: 'translate fixed', size: 5 });
1292  *    view.create('point3d', [p1, t2], { name: 'translate by slider', size: 5 });
1293  * </script><pre>
1294  *
1295  */
1296 JXG.createTransform3D = function (board, parents, attributes) {
1297     return new JXG.Transformation(board, attributes.type, parents, true);
1298 };
1299 
1300 JXG.registerElement('transform3d', JXG.createTransform3D);
1301 
1302 export default JXG.Transformation;
1303 
1304