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