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