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     this.methodMap = Type.deepCopy(this.methodMap, {
149         // TODO
150     });
151 };
152 JXG.Surface3D.prototype = new JXG.GeometryElement();
153 Type.copyPrototypeMethods(JXG.Surface3D, JXG.GeometryElement3D, 'constructor3D');
154 
155 JXG.extend(
156     JXG.Surface3D.prototype,
157     /** @lends JXG.Surface3D.prototype */ {
158 
159         /**
160          * Update the 3D coordinates of the wireframe mesh.
161          * @returns {JXG.Surface3D} Reference to the element.
162          * @see JXG.Surface3D#updateCoords
163          */
164         updateWireframe: function () {
165             var steps_u, steps_v,
166                 i_u, i_v,
167                 r_u, r_v,
168                 s_u, s_v,
169                 e_u, e_v,
170                 delta_u, delta_v,
171                 u, v,
172                 c3d = [1, 0, 0, 0];
173 
174             if (this.evalVisProp('type') !== 'wireframe') {
175                 return this;
176             }
177             this.points = [];
178 
179             steps_u = Math.max(this.evalVisProp('stepsu'), 1);
180             steps_v = Math.max(this.evalVisProp('stepsv'), 1);
181             r_u = Type.evaluate(this.range_u);
182             r_v = Type.evaluate(this.range_v);
183             s_u = Type.evaluate(r_u[0]);
184             s_v = Type.evaluate(r_v[0]);
185             e_u = Type.evaluate(r_u[1]);
186             e_v = Type.evaluate(r_v[1]);
187             delta_u = (e_u - s_u) / (steps_u);
188             delta_v = (e_v - s_v) / (steps_v);
189 
190             for (i_u = 0, u = s_u; i_u <= steps_u; i_u++, u += delta_u) {
191                 this.points.push([]);
192                 for (i_v = 0, v = s_v; i_v <= steps_v; i_v++, v += delta_v) {
193                     c3d = this.F(u, v);
194                     c3d.unshift(1);
195                     this.points[i_u].push(c3d);
196                 }
197             }
198 
199             return this;
200         },
201 
202         /**
203          * Update the coordinates of the wireframe model of the surface3d.
204          * Applies either transformation or updates wireframe coordinates.
205          *
206          * @returns {JXG.Surface3D} Reference to the element.
207          * @see JXG.Surface3D#updateWireframe
208          * @see JXG.Surface3D#updateTransform
209          */
210         updateCoords: function () {
211             if (this._F !== null) {
212                 this.updateWireframe();
213             } else {
214                 this.updateTransform();
215             }
216             return this;
217         },
218 
219         /**
220          * Generic function which evaluates the function term of the surface
221          * and applies its transformations.
222          * @param {Number} u
223          * @param {Number} v
224          * @returns
225          */
226         evalF: function(u, v) {
227             var t, i,
228                 c3d = [0, 0, 0, 0];
229 
230             if (this.transformations.length === 0 || !Type.exists(this.baseElement)) {
231                 c3d = this._F(u, v);
232                 return c3d;
233             }
234 
235             t = this.transformations;
236             for (i = 0; i < t.length; i++) {
237                 t[i].update();
238             }
239 
240             if (this === this.baseElement) {
241                 c3d = this._F(u, v);
242             } else {
243                 c3d = this.baseElement.evalF(u, v);
244             }
245             c3d.unshift(1);
246             c3d = Mat.matVecMult(t[0].matrix, c3d);
247             for (i = 1; i < t.length; i++) {
248                 c3d = Mat.matVecMult(t[i].matrix, c3d);
249             }
250 
251             return c3d.slice(1);
252         },
253 
254         /**
255          * Function defining the surface plus applying transformations.
256          * @param {Number} u
257          * @param {Number} v
258         * @returns Array [x, y, z] of length 3
259          */
260         F: function(u, v) {
261             return this.evalF(u, v);
262         },
263 
264         /**
265         * Function which maps (u, v) to z; i.e. it defines the x-coordinate of the surface
266         * plus applying transformations.
267         * @param {Number} u
268         * @param {Number} v
269         * @returns Number
270         */
271         X: function(u, v) {
272             return this.evalF(u, v)[0];
273         },
274 
275         /**
276         * Function which maps (u, v) to y; i.e. it defines the y-coordinate of the surface
277         * plus applying transformations.
278         * @param {Number} u
279         * @param {Number} v
280         * @returns Number
281         */
282         Y: function(u, v) {
283             return this.evalF(u, v)[1];
284         },
285 
286         /**
287         * Function which maps (u, v) to z; i.e. it defines the z-coordinate of the surface
288         * plus applying transformations.
289         * @param {Number} u
290         * @param {Number} v
291         * @returns Number
292         */
293         Z: function(u, v) {
294             return this.evalF(u, v)[2];
295         },
296 
297         /**
298          * @class
299          * @ignore
300          */
301         updateDataArray2D: function () {
302             var i, j, len_u, len_v,
303                 dataX = [],
304                 dataY = [],
305                 c2d,
306                 steps_u = this.evalVisProp('stepsu'),
307                 steps_v = this.evalVisProp('stepsv');
308 
309             len_u = this.points.length;
310             if (len_u !== 0) {
311                 len_v = this.points[0].length;
312 
313                 for (i = 0; i < len_u; i++) {
314                     if (steps_u > 0) { // If steps_u == 0: create 1 dimensional wireframe
315                         for (j = 0; j < len_v; j++) {
316                             c2d = this.view.project3DTo2D(this.points[i][j]);
317                             dataX.push(c2d[1]);
318                             dataY.push(c2d[2]);
319                         }
320                     }
321                     dataX.push(NaN);
322                     dataY.push(NaN);
323                 }
324 
325                 for (j = 0; j < len_v; j++) {
326                     if (steps_v > 0) { // If steps_v == 0: create 1 dimensional wireframe
327                         for (i = 0; i < len_u; i++) {
328                             c2d = this.view.project3DTo2D(this.points[i][j]);
329                             dataX.push(c2d[1]);
330                             dataY.push(c2d[2]);
331                         }
332                     }
333                     dataX.push(NaN);
334                     dataY.push(NaN);
335                 }
336             }
337 
338             return {X: dataX, Y: dataY};
339         },
340 
341         addTransform: function (el, transform) {
342             this.addTransformGeneric(el, transform);
343             return this;
344         },
345 
346         updateTransform: function () {
347             var t, c, i, j, k,
348                 len_u, len_v;
349 
350             if (this.transformations.length === 0 || this.baseElement === null ||
351                 Type.exists(this._F) // Transformations have only to be applied here
352                                      // if the curve is defined by arrays
353             ) {
354                 return this;
355             }
356 
357             t = this.transformations;
358             for (i = 0; i < t.length; i++) {
359                 t[i].update();
360             }
361             if (this !== this.baseElement) {
362                 this.points = [];
363             }
364 
365             len_u = this.baseElement.points.length;
366             if (len_u > 0) {
367                 len_v = this.baseElement.points[0].length;
368                 for (i = 0; i < len_u; i++) {
369                     if (this !== this.baseElement) {
370                         this.points.push([]);
371                     }
372                     for (j = 0; j < len_v; j++) {
373                         if (this === this.baseElement) {
374                             c = this.points[i][j];
375                         } else {
376                             c = this.baseElement.points[i][j];
377                         }
378                         for (k = 0; k < t.length; k++) {
379                             c = Mat.matVecMult(t[k].matrix, c);
380                         }
381 
382                         if (this === this.baseElement) {
383                             this.points[i][j] = c;
384                         } else {
385                             this.points[i].push(c);
386                         }
387                     }
388                 }
389             }
390 
391             return this;
392         },
393 
394         updateDataArray: function() { /* stub */ },
395 
396         update: function () {
397             if (this.needsUpdate) {
398                 this.updateDataArray();
399                 this.updateCoords();
400             }
401             return this;
402         },
403 
404         updateRenderer: function () {
405             this.needsUpdate = false;
406             return this;
407         },
408 
409         projectCoords: function (p, params) {
410             return Geometry.projectCoordsToParametric(p, this, 2, params);
411         }
412 
413         // Use method from element3d.js
414         // projectScreenCoords: function (pScr, params, cyclic) {
415         //     // this.initParamsIfNeeded(params);
416         //     return Geometry.projectScreenCoordsToParametric(pScr, this, params, cyclic);
417         // }
418     }
419 );
420 
421 /**
422  * @class A 3D parametric surface visualizes a map (u, v) → [X(u, v), Y(u, v), Z(u, v)].
423  * @pseudo
424  * @description A 3D parametric surface is defined by a function
425  *    <i>F: R<sup>2</sup> → R<sup>3</sup></i>.
426  *
427  * @name ParametricSurface3D
428  * @augments Curve
429  * @constructor
430  * @type Object
431  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
432  *
433  * @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)
434  * 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
435  * upper bound for the range of parameter v. rangeU and rangeV may also be functions returning an array of length two.
436  * @param {Function_Array,Function_Array,Function} F,rangeU,rangeV Alternatively: F<sub>[X,Y,Z]</sub>(u,v)
437  * a function returning an array [x,y,z] of numbers, rangeU and rangeV as above.
438  *
439  * @example
440  * var view = board.create('view3d',
441  * 		        [[-6, -3], [8, 8],
442  * 		        [[-5, 5], [-5, 5], [-5, 5]]]);
443  *
444  * // Sphere
445  * var c = view.create('parametricsurface3d', [
446  *     (u, v) => 2 * Math.sin(u) * Math.cos(v),
447  *     (u, v) => 2 * Math.sin(u) * Math.sin(v),
448  *     (u, v) => 2 * Math.cos(u),
449  *     [0, 2 * Math.PI],
450  *     [0, Math.PI]
451  * ], {
452  *     strokeColor: '#ff0000',
453  *     stepsU: 30,
454  *     stepsV: 30
455  * });
456  *
457  * </pre><div id="JXG52da0ecc-1ba9-4d41-850c-36e5120025a5" class="jxgbox" style="width: 500px; height: 500px;"></div>
458  * <script type="text/javascript">
459  *     (function() {
460  *         var board = JXG.JSXGraph.initBoard('JXG52da0ecc-1ba9-4d41-850c-36e5120025a5',
461  *             {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false});
462  *     var view = board.create('view3d',
463  *            [[-6, -3], [8, 8],
464  *            [[-5, 5], [-5, 5], [-5, 5]]]);
465  *
466  *     // Sphere
467  *     var c = view.create('parametricsurface3d', [
468  *         (u, v) => 2 * Math.sin(u) * Math.cos(v),
469  *         (u, v) => 2 * Math.sin(u) * Math.sin(v),
470  *         (u, v) => 2 * Math.cos(u),
471  *         [0, 2 * Math.PI],
472  *         [0, Math.PI]
473  *     ], {
474  *         strokeColor: '#ff0000',
475  *         stepsU: 20,
476  *         stepsV: 20
477  *     });
478  *     })();
479  *
480  * </script><pre>
481  *
482  */
483 JXG.createParametricSurface3D = function (board, parents, attributes) {
484     var view = parents[0],
485         F, X, Y, Z,
486         range_u, range_v, attr, attr2d,
487         base = null,
488         transform = null,
489         coords, surface,// steps,
490         tiling, type,
491         // colormap:
492         m, ma, mi, ma_a, mi_a, s, v,
493         el;
494 
495     if (parents.length === 3) {
496         base = parents[1];
497         transform = parents[2];
498         F = null;
499         X = null;
500         Y = null;
501         Z = null;
502 
503     } else if (parents.length === 4) {
504         // [view, F, range_u, range_v]
505         F = parents[1];
506         range_u = parents[2];
507         range_v = parents[3];
508         X = null;
509         Y = null;
510         Z = null;
511     } else {
512         // [view, X, Y, Z, range_u, range_v]
513         X = parents[1];
514         Y = parents[2];
515         Z = parents[3];
516         range_u = parents[4];
517         range_v = parents[5];
518         F = null;
519     }
520 
521     attr = Type.copyAttributes(attributes, board.options, 'surface3d');
522     el = new JXG.Surface3D(view, F, X, Y, Z, range_u, range_v, attr);
523 
524     tiling = el.evalVisProp('tiling');
525     type = el.evalVisProp('type');
526 
527     // Wireframe
528     attr2d = el.setAttr2D(attr);
529     el.element2D = view.create("curve", [[], []], attr2d);
530     el.element2D.view = view;
531     if (base !== null) {
532         el.addTransform(base, transform);
533         el.addParents(base);
534     }
535 
536     /**
537      * @class
538      * @ignore
539      */
540     el.element2D.updateDataArray = function () {
541         var ret = el.updateDataArray2D();
542         this.dataX = ret.X;
543         this.dataY = ret.Y;
544     };
545     el.addChild(el.element2D);
546     el.inherits.push(el.element2D);
547     el.element2D.setParents(el);
548 
549     // Set style
550     if (type !== 'wireframe') {
551 
552         if (tiling === 'triangle' || tiling === 'rectangle') {
553             if (tiling === 'triangle') {
554                 // Check for tiling of surface: triangle
555                 // In case tiling is set to triangle, we use JXG.Math.Tiling.triangulation
556                 // to create a polyhedron representing the surface3d
557 
558                 // Steps used for triangulation is chosen as the maximum of stepsU and stepsV (see options3d)
559                 // steps = Math.max(el.evalVisProp('stepsu'), el.evalVisProp('stepsv'));
560 
561                 // Uses steps and range of surface3d to create a base of triangles across the visible area of the surface3d object
562                 surface = Tiling.triangulation(
563                     [el.range_u[0], el.range_v[0]],
564                     [el.range_u[0], el.range_v[1]],
565                     [el.range_u[1], el.range_v[1]],
566                     [el.range_u[1], el.range_v[0]],
567                     // Given ratio or equilateral triangle if stepsV==0
568                     el.evalVisProp('stepsu'), el.evalVisProp('stepsv')
569                 );
570 
571             } else if (tiling === "rectangle") {
572                 // Check for tiling of functiongraph3d: rectangle
573                 // In case tiling is set to rectangle, we use JXG.Math.Tiling.rectangulation
574                 // to create a polyhedron representing the surface3d
575 
576                 // Use stepsU, stepsV (see options3d) and range of surface3d to create a base of rectangles across the visible area of the surface3d object
577                 surface = Tiling.rectangulation(
578                     [el.range_u[0], el.range_v[0]],
579                     [el.range_u[0], el.range_v[1]],
580                     [el.range_u[1], el.range_v[1]],
581                     [el.range_u[1], el.range_v[0]],
582                     el.evalVisProp('stepsu'), el.evalVisProp('stepsv')
583                 );
584             }
585         }
586 
587         // attr.polyhedron.shader.enabled = false;
588         // attr.polyhedron.fillcolorarray = ['none'];
589         el.element2D.setAttribute({ visible: false });
590         // Eliminate the call to the expensive el.updateDataArray();
591         el.element2D.updateDataArray = function() {};
592 
593         // mapMeshTo3D is used to map the 2d-points created with triangulation / rectangulation to 3D.
594         // These points are realized as functions to enable dynamic changes to the surface3d
595         // saves the dynamic points in coords
596         coords = Tiling.mapMeshTo3D(surface, el);
597 
598         // Reincorporate the dynamic points in coords into surface
599         surface = [coords, surface[1]];
600 
601         if (type === 'colormap') {
602             attr.polyhedron.shader.enabled = false;
603 
604             // Static
605             m = el.evalVisProp('colormap.max');
606             ma = m[0];
607             ma_a = m[1];
608             m = el.evalVisProp('colormap.min');
609             mi = m[0];
610             mi_a = m[1];
611             s = el.evalVisProp('colormap.s');
612             v = el.evalVisProp('colormap.v');
613 
614             attr.polyhedron.fillcolorarray = [];
615             attr.polyhedron.fillcolor = (self) => {
616                     var j, hsl,
617                         z = 0,
618                         p = self.polyhedron,
619                         face = p.faces[self.faceNumber],
620                         le = face.length;
621 
622                     // Dynamic version
623                     // m = self.evalVisProp('max');
624                     // ma = m[0];
625                     // ma_a = m[1];
626                     // m = self.evalVisProp('min');
627                     // mi = m[0];
628                     // mi_a = m[1];
629                     if (le !== 0) {
630                         for (j = 0; j < le; j++) {
631                             z += p.coords[face[j]][3];
632                         }
633                         z /= le;
634                     }
635                     z = mi_a + (z - mi) * (ma_a - mi_a) / (ma - mi);
636 
637                     // hsl = JXG.hsv2hsl(z, el.evalVisProp('colormap.s'), el.evalVisProp('colormap.v')); // Dynamic version - slower
638                     hsl = JXG.hsv2hsl(z, s, v);
639                     return `hsl(${z} ${hsl[1] * 100}% ${hsl[2] * 100}%)`;
640                 };
641         } else if (type === 'shader') {
642             attr.polyhedron.shader.enabled = true;
643         } else {
644             // colorarray
645             attr.polyhedron.shader.enabled = false;
646         }
647 
648         // Create the polyhedron representing the parametricsurface3d
649         el.polyhedron = view.create('polyhedron3d', surface, attr.polyhedron);
650         el.addChild(el.polyhedron);
651         el.inherits.push(el.polyhedron);
652         el.polyhedron.setParents(el);
653     }
654     // Wireframe
655     el.element2D.prepareUpdate().update();
656     if (!board.isSuspendedUpdate) {
657         el.element2D.updateVisibility().updateRenderer();
658     }
659 
660     return el;
661 };
662 JXG.registerElement("parametricsurface3d", JXG.createParametricSurface3D);
663 
664 /**
665  * @class A 3D functiongraph visualizes a map (x, y) → f(x, y).
666  * The graph is a {@link Curve3D} element.
667  * @pseudo
668  * @description A 3D function graph is defined by a function
669  *    <i>F: R<sup>2</sup> → R</i>.
670  *
671  * @name Functiongraph3D
672  * @augments ParametricSurface3D
673  * @constructor
674  * @type Object
675  * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
676  * @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
677  * lower and upper bound for the range of x, rangeY is the array containing
678  * lower and upper bound for the range of y.
679  * @example
680  * var box = [-5, 5];
681  * var view = board.create('view3d',
682  *     [
683  *         [-6, -3], [8, 8],
684  *         [box, box, box]
685  *     ],
686  *     {
687  *         xPlaneRear: {visible: false},
688  *         yPlaneRear: {visible: false},
689  *     });
690  *
691  * // Function F to be plotted
692  * var F = (x, y) => Math.sin(x * y / 4);
693  *
694  * // 3D surface
695  * var c = view.create('functiongraph3d', [
696  *     F,
697  *     box, // () => [-s.Value()*5, s.Value() * 5],
698  *     box, // () => [-s.Value()*5, s.Value() * 5],
699  * ], {
700  *     strokeWidth: 0.5,
701  *     stepsU: 70,
702  *     stepsV: 70
703  * });
704  *
705  * </pre><div id="JXG87646dd4-9fe5-4c21-8734-089abc612515" class="jxgbox" style="width: 500px; height: 500px;"></div>
706  * <script type="text/javascript">
707  *     (function() {
708  *         var board = JXG.JSXGraph.initBoard('JXG87646dd4-9fe5-4c21-8734-089abc612515',
709  *             {boundingbox: [-8, 8, 8,-8], axis: false, pan: {enabled: false}, showcopyright: false, shownavigation: false});
710  *     var box = [-5, 5];
711  *     var view = board.create('view3d',
712  *         [
713  *             [-6, -3], [8, 8],
714  *             [box, box, box]
715  *         ],
716  *         {
717  *             xPlaneRear: {visible: false},
718  *             yPlaneRear: {visible: false},
719  *         });
720  *
721  *     // Function F to be plotted
722  *     var F = (x, y) => Math.sin(x * y / 4);
723  *
724  *     // 3D surface
725  *     var c = view.create('functiongraph3d', [
726  *         F,
727  *         box, // () => [-s.Value()*5, s.Value() * 5],
728  *         box, // () => [-s.Value()*5, s.Value() * 5],
729  *     ], {
730  *         strokeWidth: 0.5,
731  *         stepsU: 70,
732  *         stepsV: 70
733  *     });
734  *     })();
735  *
736  * </script><pre>
737  *
738  */
739 JXG.createFunctiongraph3D = function (board, parents, attributes) {
740     var view = parents[0],
741         X = function (u, v) {
742             return u;
743         },
744         Y = function (u, v) {
745             return v;
746         },
747         Z = Type.createFunction(parents[1], board, 'x, y'),
748         range_u = parents[2],
749         range_v = parents[3],
750         el;
751 
752     el = view.create("parametricsurface3d", [X, Y, Z, range_u, range_v], attributes);
753     el.elType = 'functiongraph3d';
754 
755     return el;
756 };
757 JXG.registerElement("functiongraph3d", JXG.createFunctiongraph3D);
758