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