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