1 /*
  2     Copyright 2008-2026
  3         Matthias Ehmann,
  4         Carsten Miller,
  5         Andreas Walter,
  6         Alfred Wassermann
  7 
  8     This file is part of JSXGraph.
  9 
 10     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 11 
 12     You can redistribute it and/or modify it under the terms of the
 13 
 14       * GNU Lesser General Public License as published by
 15         the Free Software Foundation, either version 3 of the License, or
 16         (at your option) any later version
 17       OR
 18       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 19 
 20     JSXGraph is distributed in the hope that it will be useful,
 21     but WITHOUT ANY WARRANTY; without even the implied warranty of
 22     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 23     GNU Lesser General Public License for more details.
 24 
 25     You should have received a copy of the GNU Lesser General Public License and
 26     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 27     and <https://opensource.org/licenses/MIT/>.
 28  */
 29 /*global JXG:true, define: true*/
 30 
 31 import JXG from "../jxg.js";
 32 import Const from "../base/constants.js";
 33 import Mat from "../math/math.js";
 34 import Geometry from "../math/geometry.js";
 35 import Tiling from "../math/tiling.js";
 36 import Type from "../utils/type.js";
 37 
 38 /**
 39  * Constructor for 3D surfaces.
 40  * @class Creates a new 3D surface object. Do not use this constructor to create a 3D surface. Use {@link JXG.View3D#create} with type {@link Surface3D} instead.
 41  *
 42  * @augments JXG.GeometryElement3D
 43  * @augments JXG.GeometryElement
 44  * @param {View3D} view
 45  * @param {Function} F
 46  * @param {Function} X
 47  * @param {Function} Y
 48  * @param {Function} Z
 49  * @param {Array} range_u
 50  * @param {Array} range_v
 51  * @param {Object} attributes
 52  * @see JXG.Board#generateName
 53  */
 54 JXG.Surface3D = function (view, F, X, Y, Z, range_u, range_v, attributes) {
 55     this.constructor(
 56         view.board,
 57         attributes,
 58         Const.OBJECT_TYPE_SURFACE3D,
 59         Const.OBJECT_CLASS_3D
 60     );
 61     this.constructor3D(view, 'surface3d');
 62 
 63     this.board.finalizeAdding(this);
 64 
 65     /**
 66      * Internal function defining the surface
 67      * without applying any transformations.
 68      *
 69      * @function
 70      * @param {Number} u
 71      * @param {Number} v
 72      * @returns Array [x, y, z] of length 3
 73      * @private
 74      */
 75     this._F = F;
 76 
 77     /**
 78      * Internal function which maps (u, v) to x; i.e. it defines the x-coordinate of the surface
 79      * without applying any transformations.
 80      * @function
 81      * @param {Number} u
 82      * @param {Number} v
 83      * @returns Number
 84      * @private
 85      */
 86     this._X = X;
 87 
 88     /**
 89      * Internal function which maps (u, v) to y; i.e. it defines the y-coordinate of the surface
 90      * without applying any transformations.
 91      * @function
 92      * @param {Number} u
 93      * @param {Number} v
 94      * @returns Number
 95      * @private
 96      */
 97     this._Y = Y;
 98 
 99     /**
100      * Internal function which maps (u, v) to z; i.e. it defines the z-coordinate of the surface
101      * without applying any transformations.
102      * @function
103      * @param {Number} u
104      * @param {Number} v
105      * @returns Number
106      * @private
107      */
108     this._Z = Z;
109 
110     if (this._F !== null) {
111         this._X = function (u, v) {
112             return this._F(u, v)[0];
113         };
114         this._Y = function (u, v) {
115             return this._F(u, v)[1];
116         };
117         this._Z = function (u, v) {
118             return this._F(u, v)[2];
119         };
120     } else {
121         if (this._X !== null) {
122             this._F = function(u, v) {
123                 return [this._X(u, v), this._Y(u, v), this._Z(u, v)];
124             };
125         }
126     }
127 
128     /**
129      * If the surface is constructed with attribute `style:'triangle'` or `style:'rectangle'`,
130      * a polyhodron3d-element is used for visualization.
131      *
132      * @name polyhedron
133      * @memberOf JXG.Surface3D
134      * @type Polyhedron3D
135      * @default null
136      * @private
137      */
138     this.polyhedron = null;
139 
140     this.range_u = range_u;
141     this.range_v = range_v;
142 
143     this.dataX = null;
144     this.dataY = null;
145     this.dataZ = null;
146     this.points = [];
147 };
148 
149 JXG.Surface3D.prototype = new JXG.GeometryElement();
150 
151 Type.copyPrototypeMethods(JXG.Surface3D, JXG.GeometryElement3D, 'constructor3D');
152 Type.copyMethodMap(JXG.Surface3D, {
153     // TODO
154 });
155 
156 JXG.extend(
157     JXG.Surface3D.prototype,
158     /** @lends JXG.Surface3D.prototype */ {
159 
160         /**
161          * Update the 3D coordinates of the wireframe mesh.
162          * @returns {JXG.Surface3D} Reference to the element.
163          * @see JXG.Surface3D#updateCoords
164          */
165         updateWireframe: function () {
166             var steps_u, steps_v,
167                 i_u, i_v,
168                 r_u, r_v,
169                 s_u, s_v,
170                 e_u, e_v,
171                 delta_u, delta_v,
172                 u, v,
173                 c3d = [1, 0, 0, 0];
174 
175             if (this.evalVisProp('type') !== 'wireframe') {
176                 return this;
177             }
178             this.points = [];
179 
180             steps_u = Math.max(this.evalVisProp('stepsu'), 1);
181             steps_v = Math.max(this.evalVisProp('stepsv'), 1);
182             r_u = Type.evaluate(this.range_u);
183             r_v = Type.evaluate(this.range_v);
184             s_u = Type.evaluate(r_u[0]);
185             s_v = Type.evaluate(r_v[0]);
186             e_u = Type.evaluate(r_u[1]);
187             e_v = Type.evaluate(r_v[1]);
188             delta_u = (e_u - s_u) / (steps_u);
189             delta_v = (e_v - s_v) / (steps_v);
190 
191             for (i_u = 0, u = s_u; i_u <= steps_u; i_u++, u += delta_u) {
192                 this.points.push([]);
193                 for (i_v = 0, v = s_v; i_v <= steps_v; i_v++, v += delta_v) {
194                     c3d = this.F(u, v);
195                     c3d.unshift(1);
196                     this.points[i_u].push(c3d);
197                 }
198             }
199 
200             return this;
201         },
202 
203         /**
204          * Update the coordinates of the wireframe model of the surface3d.
205          * Applies either transformation or updates wireframe coordinates.
206          *
207          * @returns {JXG.Surface3D} Reference to the element.
208          * @see JXG.Surface3D#updateWireframe
209          * @see JXG.Surface3D#updateTransform
210          */
211         updateCoords: function () {
212             if (this._F !== null) {
213                 this.updateWireframe();
214             } else {
215                 this.updateTransform();
216             }
217             return this;
218         },
219 
220         /**
221          * Generic function which evaluates the function term of the surface
222          * and applies its transformations.
223          * @param {Number} u
224          * @param {Number} v
225          * @returns
226          */
227         evalF: function(u, v) {
228             var t, i,
229                 c3d = [0, 0, 0, 0];
230 
231             if (this.transformations.length === 0 || !Type.exists(this.baseElement)) {
232                 c3d = this._F(u, v);
233                 return c3d;
234             }
235 
236             t = this.transformations;
237             for (i = 0; i < t.length; i++) {
238                 t[i].update();
239             }
240 
241             if (this === this.baseElement) {
242                 c3d = this._F(u, v);
243             } else {
244                 c3d = this.baseElement.evalF(u, v);
245             }
246             c3d.unshift(1);
247             c3d = Mat.matVecMult(t[0].matrix, c3d);
248             for (i = 1; i < t.length; i++) {
249                 c3d = Mat.matVecMult(t[i].matrix, c3d);
250             }
251 
252             return c3d.slice(1);
253         },
254 
255         /**
256          * Function defining the surface plus applying transformations.
257          * @param {Number} u
258          * @param {Number} v
259         * @returns Array [x, y, z] of length 3
260          */
261         F: function(u, v) {
262             return this.evalF(u, v);
263         },
264 
265         /**
266         * Function which maps (u, v) to z; i.e. it defines the x-coordinate of the surface
267         * plus applying transformations.
268         * @param {Number} u
269         * @param {Number} v
270         * @returns Number
271         */
272         X: function(u, v) {
273             return this.evalF(u, v)[0];
274         },
275 
276         /**
277         * Function which maps (u, v) to y; i.e. it defines the y-coordinate of the surface
278         * plus applying transformations.
279         * @param {Number} u
280         * @param {Number} v
281         * @returns Number
282         */
283         Y: function(u, v) {
284             return this.evalF(u, v)[1];
285         },
286 
287         /**
288         * Function which maps (u, v) to z; i.e. it defines the z-coordinate of the surface
289         * plus applying transformations.
290         * @param {Number} u
291         * @param {Number} v
292         * @returns Number
293         */
294         Z: function(u, v) {
295             return this.evalF(u, v)[2];
296         },
297 
298         /**
299          * @class
300          * @ignore
301          */
302         updateDataArray2D: function () {
303             var i, j, len_u, len_v,
304                 dataX = [],
305                 dataY = [],
306                 c2d,
307                 steps_u = this.evalVisProp('stepsu'),
308                 steps_v = this.evalVisProp('stepsv');
309 
310             len_u = this.points.length;
311             if (len_u !== 0) {
312                 len_v = this.points[0].length;
313 
314                 for (i = 0; i < len_u; i++) {
315                     if (steps_u > 0) { // If steps_u == 0: create 1 dimensional wireframe
316                         for (j = 0; j < len_v; j++) {
317                             c2d = this.view.project3DTo2D(this.points[i][j]);
318                             dataX.push(c2d[1]);
319                             dataY.push(c2d[2]);
320                         }
321                     }
322                     dataX.push(NaN);
323                     dataY.push(NaN);
324                 }
325 
326                 for (j = 0; j < len_v; j++) {
327                     if (steps_v > 0) { // If steps_v == 0: create 1 dimensional wireframe
328                         for (i = 0; i < len_u; i++) {
329                             c2d = this.view.project3DTo2D(this.points[i][j]);
330                             dataX.push(c2d[1]);
331                             dataY.push(c2d[2]);
332                         }
333                     }
334                     dataX.push(NaN);
335                     dataY.push(NaN);
336                 }
337             }
338 
339             return {X: dataX, Y: dataY};
340         },
341 
342         addTransform: function (el, transform) {
343             this.addTransformGeneric(el, transform);
344             return this;
345         },
346 
347         removeTransform: function (transform) {
348             this.removeTransformGeneric(transform);
349             return this;
350         },
351 
352         clearTransforms: function () {
353             this.clearTransformsGeneric();
354             return this;
355         },
356 
357         updateTransform: function () {
358             var t, c, i, j, k,
359                 len_u, len_v;
360 
361             if (this.transformations.length === 0 || this.baseElement === null ||
362                 Type.exists(this._F) // Transformations have only to be applied here
363                                      // if the curve is defined by arrays
364             ) {
365                 return this;
366             }
367 
368             t = this.transformations;
369             for (i = 0; i < t.length; i++) {
370                 t[i].update();
371             }
372             if (this !== this.baseElement) {
373                 this.points = [];
374             }
375 
376             len_u = this.baseElement.points.length;
377             if (len_u > 0) {
378                 len_v = this.baseElement.points[0].length;
379                 for (i = 0; i < len_u; i++) {
380                     if (this !== this.baseElement) {
381                         this.points.push([]);
382                     }
383                     for (j = 0; j < len_v; j++) {
384                         if (this === this.baseElement) {
385                             c = this.points[i][j];
386                         } else {
387                             c = this.baseElement.points[i][j];
388                         }
389                         for (k = 0; k < t.length; k++) {
390                             c = Mat.matVecMult(t[k].matrix, c);
391                         }
392 
393                         if (this === this.baseElement) {
394                             this.points[i][j] = c;
395                         } else {
396                             this.points[i].push(c);
397                         }
398                     }
399                 }
400             }
401 
402             return this;
403         },
404 
405         updateDataArray: function() { /* stub */ },
406 
407         update: function () {
408             if (this.needsUpdate) {
409                 this.updateDataArray();
410                 this.updateCoords();
411             }
412             return this;
413         },
414 
415         updateRenderer: function () {
416             this.needsUpdate = false;
417             return this;
418         },
419 
420         projectCoords: function (p, params) {
421             return Geometry.projectCoordsToParametric(p, this, 2, params);
422         }
423 
424         // Use method from element3d.js
425         // projectScreenCoords: function (pScr, params, cyclic) {
426         //     // this.initParamsIfNeeded(params);
427         //     return Geometry.projectScreenCoordsToParametric(pScr, this, params, cyclic);
428         // }
429     }
430 );
431 
432 /**
433  * @class A 3D parametric surface visualizes a map (u, v) → [X(u, v), Y(u, v), Z(u, v)].
434  * @pseudo
435  * @description A 3D parametric surface is defined by a function
436  *    <i>F: R<sup>2</sup> → R<sup>3</sup></i>.
437  *
438  * @name ParametricSurface3D
439  * @augments Curve
440  * @constructor
441  * @type Object
442  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
443  *
444  * @param {Function_Function_Function_Array,Function_Array,Function} F<sub>X</sub>,F<sub>Y</sub>,F<sub>Z</sub>,rangeU,rangeV F<sub>X</sub>(u,v), F<sub>Y</sub>(u,v), F<sub>Z</sub>(u,v)
445  * are functions returning a number, rangeU is the array containing lower and upper bound for the range of parameter u, rangeV is the array containing lower and
446  * upper bound for the range of parameter v. rangeU and rangeV may also be functions returning an array of length two.
447  * @param {Function_Array,Function_Array,Function} F,rangeU,rangeV Alternatively: F<sub>[X,Y,Z]</sub>(u,v)
448  * a function returning an array [x,y,z] of numbers, rangeU and rangeV as above.
449  *
450  * @example
451  * var view = board.create('view3d',
452  * 		        [[-6, -3], [8, 8],
453  * 		        [[-5, 5], [-5, 5], [-5, 5]]]);
454  *
455  * // Sphere
456  * var c = view.create('parametricsurface3d', [
457  *     (u, v) => 2 * Math.sin(u) * Math.cos(v),
458  *     (u, v) => 2 * Math.sin(u) * Math.sin(v),
459  *     (u, v) => 2 * Math.cos(u),
460  *     [0, 2 * Math.PI],
461  *     [0, Math.PI]
462  * ], {
463  *     strokeColor: '#ff0000',
464  *     stepsU: 30,
465  *     stepsV: 30
466  * });
467  *
468  * </pre><div id="JXG52da0ecc-1ba9-4d41-850c-36e5120025a5" class="jxgbox" style="width: 500px; height: 500px;"></div>
469  * <script type="text/javascript">
470  *     (function() {
471  *         var board = JXG.JSXGraph.initBoard('JXG52da0ecc-1ba9-4d41-850c-36e5120025a5',
472  *             {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false});
473  *     var view = board.create('view3d',
474  *            [[-6, -3], [8, 8],
475  *            [[-5, 5], [-5, 5], [-5, 5]]]);
476  *
477  *     // Sphere
478  *     var c = view.create('parametricsurface3d', [
479  *         (u, v) => 2 * Math.sin(u) * Math.cos(v),
480  *         (u, v) => 2 * Math.sin(u) * Math.sin(v),
481  *         (u, v) => 2 * Math.cos(u),
482  *         [0, 2 * Math.PI],
483  *         [0, Math.PI]
484  *     ], {
485  *         strokeColor: '#ff0000',
486  *         stepsU: 20,
487  *         stepsV: 20
488  *     });
489  *     })();
490  *
491  * </script><pre>
492  *
493  */
494 JXG.createParametricSurface3D = function (board, parents, attributes) {
495     var view = parents[0],
496         F, X, Y, Z,
497         range_u, range_v, attr, attr2d,
498         base = null,
499         transform = null,
500         coords, surface,// steps,
501         tiling, type,
502         // colormap:
503         m, ma, mi, ma_a, mi_a, s, v,
504         el;
505 
506     if (parents.length === 3) {
507         base = parents[1];
508         transform = parents[2];
509         F = null;
510         X = null;
511         Y = null;
512         Z = null;
513 
514     } else if (parents.length === 4) {
515         // [view, F, range_u, range_v]
516         F = parents[1];
517         range_u = parents[2];
518         range_v = parents[3];
519         X = null;
520         Y = null;
521         Z = null;
522     } else {
523         // [view, X, Y, Z, range_u, range_v]
524         X = parents[1];
525         Y = parents[2];
526         Z = parents[3];
527         range_u = parents[4];
528         range_v = parents[5];
529         F = null;
530     }
531 
532     attr = Type.copyAttributes(attributes, board.options, 'surface3d');
533     el = new JXG.Surface3D(view, F, X, Y, Z, range_u, range_v, attr);
534 
535     tiling = el.evalVisProp('tiling');
536     type = el.evalVisProp('type');
537 
538     // Wireframe
539     attr2d = el.setAttr2D(attr);
540     el.element2D = view.create("curve", [[], []], attr2d);
541     el.element2D.view = view;
542     el.element2D.dump = false;
543     if (base !== null) {
544         el.addTransform(base, transform);
545         el.addParents(base);
546     }
547 
548     /**
549      * @class
550      * @ignore
551      */
552     el.element2D.updateDataArray = function () {
553         var ret = el.updateDataArray2D();
554         this.dataX = ret.X;
555         this.dataY = ret.Y;
556     };
557     el.addChild(el.element2D);
558     el.inherits.push(el.element2D);
559     el.element2D.setParents(el);
560 
561     // Set style
562     if (type !== 'wireframe') {
563 
564         if (tiling === 'triangle' || tiling === 'rectangle') {
565             if (tiling === 'triangle') {
566                 // Check for tiling of surface: triangle
567                 // In case tiling is set to triangle, we use JXG.Math.Tiling.triangulation
568                 // to create a polyhedron representing the surface3d
569 
570                 // Steps used for triangulation is chosen as the maximum of stepsU and stepsV (see options3d)
571                 // steps = Math.max(el.evalVisProp('stepsu'), el.evalVisProp('stepsv'));
572 
573                 // Uses steps and range of surface3d to create a base of triangles across the visible area of the surface3d object
574                 surface = Tiling.triangulation(
575                     [el.range_u[0], el.range_v[0]],
576                     [el.range_u[0], el.range_v[1]],
577                     [el.range_u[1], el.range_v[1]],
578                     [el.range_u[1], el.range_v[0]],
579                     // Given ratio or equilateral triangle if stepsV==0
580                     el.evalVisProp('stepsu'), el.evalVisProp('stepsv')
581                 );
582 
583             } else if (tiling === "rectangle") {
584                 // Check for tiling of functiongraph3d: rectangle
585                 // In case tiling is set to rectangle, we use JXG.Math.Tiling.rectangulation
586                 // to create a polyhedron representing the surface3d
587 
588                 // Use stepsU, stepsV (see options3d) and range of surface3d to create a base of rectangles across the visible area of the surface3d object
589                 surface = Tiling.rectangulation(
590                     [el.range_u[0], el.range_v[0]],
591                     [el.range_u[0], el.range_v[1]],
592                     [el.range_u[1], el.range_v[1]],
593                     [el.range_u[1], el.range_v[0]],
594                     el.evalVisProp('stepsu'), el.evalVisProp('stepsv')
595                 );
596             }
597         }
598 
599         // attr.polyhedron.shader.enabled = false;
600         // attr.polyhedron.fillcolorarray = ['none'];
601         el.element2D.setAttribute({ visible: false });
602         // Eliminate the call to the expensive el.updateDataArray();
603         el.element2D.updateDataArray = function() {};
604 
605         // mapMeshTo3D is used to map the 2d-points created with triangulation / rectangulation to 3D.
606         // These points are realized as functions to enable dynamic changes to the surface3d,
607         // stores the dynamic points in coords
608         coords = Tiling.mapMeshTo3D(surface, el);
609 
610         // Reincorporate the dynamic points in coords into surface
611         surface = [coords, surface[1]];
612 
613         if (type === 'colormap') {
614             attr.polyhedron.shader.enabled = false;
615 
616             // Static
617             m = el.evalVisProp('colormap.max');
618             ma = m[0];
619             ma_a = m[1];
620             m = el.evalVisProp('colormap.min');
621             mi = m[0];
622             mi_a = m[1];
623             s = el.evalVisProp('colormap.s');
624             v = el.evalVisProp('colormap.v');
625 
626             attr.polyhedron.fillcolorarray = [];
627             attr.polyhedron.fillcolor = (self) => {
628                     var j, hsl,
629                         z = 0,
630                         p = self.polyhedron,
631                         face = p.faces[self.faceNumber],
632                         le = face.length;
633 
634                     // Dynamic version
635                     // m = self.evalVisProp('max');
636                     // ma = m[0];
637                     // ma_a = m[1];
638                     // m = self.evalVisProp('min');
639                     // mi = m[0];
640                     // mi_a = m[1];
641                     if (le !== 0) {
642                         for (j = 0; j < le; j++) {
643                             z += p.coords[face[j]][3];
644                         }
645                         z /= le;
646                     }
647                     z = mi_a + (z - mi) * (ma_a - mi_a) / (ma - mi);
648 
649                     // hsl = JXG.hsv2hsl(z, el.evalVisProp('colormap.s'), el.evalVisProp('colormap.v')); // Dynamic version - slower
650                     hsl = JXG.hsv2hsl(z, s, v);
651                     return `hsl(${z} ${hsl[1] * 100}% ${hsl[2] * 100}%)`;
652                 };
653         } else if (type === 'shader') {
654             attr.polyhedron.shader.enabled = true;
655         } else {
656             // colorarray
657             attr.polyhedron.shader.enabled = false;
658         }
659 
660         // Create the polyhedron representing the parametricsurface3d
661         el.polyhedron = view.create('polyhedron3d', surface, attr.polyhedron);
662         el.addChild(el.polyhedron);
663         el.inherits.push(el.polyhedron);
664         el.polyhedron.setParents(el);
665     }
666     // Wireframe
667     el.element2D.prepareUpdate().update();
668     if (!board.isSuspendedUpdate) {
669         el.element2D.updateVisibility().updateRenderer();
670     }
671 
672     return el;
673 };
674 JXG.registerElement("parametricsurface3d", JXG.createParametricSurface3D);
675 
676 /**
677  * @class A 3D functiongraph visualizes a map (x, y) → f(x, y).
678  * The graph is a {@link Curve3D} element.
679  * @pseudo
680  * @description A 3D function graph is defined by a function
681  *    <i>F: R<sup>2</sup> → R</i>.
682  *
683  * @name Functiongraph3D
684  * @augments ParametricSurface3D
685  * @constructor
686  * @type Object
687  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
688  * @param {Function,String_Array_Array} F,rangeX,rangeY  F(x,y) is a function returning a number (or a JessieCode string), rangeX is the array containing
689  * lower and upper bound for the range of x, rangeY is the array containing
690  * lower and upper bound for the range of y.
691  * @example
692  * var box = [-5, 5];
693  * var view = board.create('view3d',
694  *     [
695  *         [-6, -3], [8, 8],
696  *         [box, box, box]
697  *     ],
698  *     {
699  *         xPlaneRear: {visible: false},
700  *         yPlaneRear: {visible: false},
701  *     });
702  *
703  * // Function F to be plotted
704  * var F = (x, y) => Math.sin(x * y / 4);
705  *
706  * // 3D surface
707  * var c = view.create('functiongraph3d', [
708  *     F,
709  *     box, // () => [-s.Value()*5, s.Value() * 5],
710  *     box, // () => [-s.Value()*5, s.Value() * 5],
711  * ], {
712  *     strokeWidth: 0.5,
713  *     stepsU: 70,
714  *     stepsV: 70
715  * });
716  *
717  * </pre><div id="JXG87646dd4-9fe5-4c21-8734-089abc612515" class="jxgbox" style="width: 500px; height: 500px;"></div>
718  * <script type="text/javascript">
719  *     (function() {
720  *         var board = JXG.JSXGraph.initBoard('JXG87646dd4-9fe5-4c21-8734-089abc612515',
721  *             {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false});
722  *     var box = [-5, 5];
723  *     var view = board.create('view3d',
724  *         [
725  *             [-6, -3], [8, 8],
726  *             [box, box, box]
727  *         ],
728  *         {
729  *             xPlaneRear: {visible: false},
730  *             yPlaneRear: {visible: false},
731  *         });
732  *
733  *     // Function F to be plotted
734  *     var F = (x, y) => Math.sin(x * y / 4);
735  *
736  *     // 3D surface
737  *     var c = view.create('functiongraph3d', [
738  *         F,
739  *         box, // () => [-s.Value()*5, s.Value() * 5],
740  *         box, // () => [-s.Value()*5, s.Value() * 5],
741  *     ], {
742  *         strokeWidth: 0.5,
743  *         stepsU: 70,
744  *         stepsV: 70
745  *     });
746  *     })();
747  *
748  * </script><pre>
749  *
750  */
751 JXG.createFunctiongraph3D = function (board, parents, attributes) {
752     var view = parents[0],
753         X = function (u, v) {
754             return u;
755         },
756         Y = function (u, v) {
757             return v;
758         },
759         Z = Type.createFunction(parents[1], board, 'x, y'),
760         range_u = parents[2],
761         range_v = parents[3],
762         el;
763 
764     el = view.create("parametricsurface3d", [X, Y, Z, range_u, range_v], attributes);
765     el.elType = 'functiongraph3d';
766 
767     return el;
768 };
769 JXG.registerElement("functiongraph3d", JXG.createFunctiongraph3D);
770