1 /* 2 Copyright 2008-2022 3 Matthias Ehmann, 4 Michael Gerhaeuser, 5 Carsten Miller, 6 Bianca Valentin, 7 Alfred Wassermann, 8 Peter Wilfahrt 9 10 This file is part of JSXGraph. 11 12 JSXGraph is free software dual licensed under the GNU LGPL or MIT License. 13 14 You can redistribute it and/or modify it under the terms of the 15 16 * GNU Lesser General Public License as published by 17 the Free Software Foundation, either version 3 of the License, or 18 (at your option) any later version 19 OR 20 * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT 21 22 JSXGraph is distributed in the hope that it will be useful, 23 but WITHOUT ANY WARRANTY; without even the implied warranty of 24 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 25 GNU Lesser General Public License for more details. 26 27 You should have received a copy of the GNU Lesser General Public License and 28 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/> 29 and <http://opensource.org/licenses/MIT/>. 30 */ 31 32 33 /*global JXG: true, define: true, window: true*/ 34 /*jslint nomen: true, plusplus: true*/ 35 36 /* depends: 37 jxg 38 base/constants 39 base/coords 40 base/element 41 math/math 42 utils/type 43 */ 44 45 /** 46 * @fileoverview In this file the ForeignObject element is defined. 47 */ 48 49 define([ 50 'jxg', 'base/constants', 'base/coords', 'base/element', 'math/math', 'utils/type', 'base/coordselement' 51 ], function (JXG, Const, Coords, GeometryElement, Mat, Type, CoordsElement) { 52 53 "use strict"; 54 55 /** 56 * Construct and handle SVG foreignObjects. 57 * 58 * @class Creates a new foreignObject object. Do not use this constructor to create a foreignObject. Use {@link JXG.Board#create} with 59 * type {@link foreignobject} instead. 60 * @augments JXG.GeometryElement 61 * @augments JXG.CoordsElement 62 * @param {string|JXG.Board} board The board the new foreignObject is drawn on. 63 * @param {Array} coordinates An array with the user coordinates of the foreignObject. 64 * @param {Object} attributes An object containing visual and - optionally - a name and an id. 65 * @param {string|function} url An URL string or a function returning an URL string. 66 * @param {Array} size Array containing width and height of the foreignObject in user coordinates. 67 * 68 */ 69 JXG.ForeignObject = function (board, coords, attributes, content, size) { 70 this.constructor(board, attributes, Const.OBJECT_TYPE_FOREIGNOBJECT, Const.OBJECT_CLASS_OTHER); 71 this.element = this.board.select(attributes.anchor); 72 this.coordsConstructor(coords); 73 74 this._useUserSize = false; 75 76 /** 77 * Array of length two containing [width, height] of the foreignObject in pixel. 78 * @type Array 79 */ 80 this.size = [1, 1]; 81 if (Type.exists(size) && size.length > 0) { 82 this._useUserSize = true; 83 84 this.W = Type.createFunction(size[0], this.board, ''); 85 this.H = Type.createFunction(size[1], this.board, ''); 86 this.usrSize = [this.W(), this.H()]; 87 } 88 89 // this.size = [Math.abs(this.usrSize[0] * board.unitX), Math.abs(this.usrSize[1] * board.unitY)]; 90 91 /** 92 * 'href' of the foreignObject. 93 * @type {string} 94 */ 95 this.content = content; 96 97 this.elType = 'foreignobject'; 98 99 // span contains the anchor point and the two vectors 100 // spanning the foreignObject rectangle. 101 // this.span = [ 102 // this.coords.usrCoords.slice(0), 103 // [this.coords.usrCoords[0], this.W(), 0], 104 // [this.coords.usrCoords[0], 0, this.H()] 105 // ]; 106 //this.parent = board.select(attributes.anchor); 107 108 this.id = this.board.setId(this, 'Im'); 109 110 this.board.renderer.drawForeignObject(this); 111 this.board.finalizeAdding(this); 112 113 this.methodMap = JXG.deepCopy(this.methodMap, { 114 addTransformation: 'addTransform', 115 trans: 'addTransform' 116 }); 117 }; 118 119 JXG.ForeignObject.prototype = new GeometryElement(); 120 Type.copyPrototypeMethods(JXG.ForeignObject, CoordsElement, 'coordsConstructor'); 121 122 JXG.extend(JXG.ForeignObject.prototype, /** @lends JXG.ForeignObject.prototype */ { 123 124 /** 125 * Checks whether (x,y) is over or near the image; 126 * @param {Number} x Coordinate in x direction, screen coordinates. 127 * @param {Number} y Coordinate in y direction, screen coordinates. 128 * @returns {Boolean} True if (x,y) is over the image, False otherwise. 129 */ 130 hasPoint: function (x, y) { 131 var dx, dy, r, type, prec, 132 c, v, p, dot, 133 len = this.transformations.length; 134 135 if (Type.isObject(Type.evaluate(this.visProp.precision))) { 136 type = this.board._inputDevice; 137 prec = Type.evaluate(this.visProp.precision[type]); 138 } else { 139 // 'inherit' 140 prec = this.board.options.precision.hasPoint; 141 } 142 143 // Easy case: no transformation 144 if (len === 0) { 145 dx = x - this.coords.scrCoords[1]; 146 dy = this.coords.scrCoords[2] - y; 147 r = prec; 148 149 return dx >= -r && dx - this.size[0] <= r && 150 dy >= -r && dy - this.size[1] <= r; 151 } 152 153 // foreignObject is transformed 154 c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board); 155 // v is the vector from anchor point to the drag point 156 c = c.usrCoords; 157 v = [c[0] - this.span[0][0], 158 c[1] - this.span[0][1], 159 c[2] - this.span[0][2]]; 160 dot = Mat.innerProduct; // shortcut 161 162 // Project the drag point to the sides. 163 p = dot(v, this.span[1]); 164 if (0 <= p && p <= dot(this.span[1], this.span[1])) { 165 p = dot(v, this.span[2]); 166 167 if (0 <= p && p <= dot(this.span[2], this.span[2])) { 168 return true; 169 } 170 } 171 return false; 172 }, 173 174 /** 175 * Recalculate the coordinates of lower left corner and the width and height. 176 * 177 * @returns {JXG.ForeignObject} A reference to the element 178 * @private 179 */ 180 update: function (fromParent) { 181 if (!this.needsUpdate) { 182 return this; 183 } 184 this.updateCoords(fromParent); 185 this.updateSize(); 186 // this.updateSpan(); 187 return this; 188 }, 189 190 /** 191 * Send an update request to the renderer. 192 * @private 193 */ 194 updateRenderer: function () { 195 return this.updateRendererGeneric('updateForeignObject'); 196 }, 197 198 /** 199 * Updates the internal arrays containing size of the foreignObject. 200 * @returns {JXG.ForeignObject} A reference to the element 201 * @private 202 */ 203 updateSize: function () { 204 var bb = [0, 0]; 205 206 if (this._useUserSize) { 207 this.usrSize = [this.W(), this.H()]; 208 this.size = [Math.abs(this.usrSize[0] * this.board.unitX), 209 Math.abs(this.usrSize[1] * this.board.unitY)]; 210 } else { 211 if (this.rendNode.hasChildNodes()) { 212 bb = this.rendNode.childNodes[0].getBoundingClientRect(); 213 this.size = [bb.width, bb.height]; 214 } 215 } 216 217 return this; 218 }, 219 220 /** 221 * Update the anchor point of the foreignObject, i.e. the lower left corner 222 * and the two vectors which span the rectangle. 223 * @returns {JXG.ForeignObject} A reference to the element 224 * @private 225 * 226 */ 227 updateSpan: function () { 228 var i, j, len = this.transformations.length, v = []; 229 230 if (len === 0) { 231 this.span = [[this.Z(), this.X(), this.Y()], 232 [this.Z(), this.W(), 0], 233 [this.Z(), 0, this.H()]]; 234 } else { 235 // v contains the three defining corners of the rectangle/image 236 v[0] = [this.Z(), this.X(), this.Y()]; 237 v[1] = [this.Z(), this.X() + this.W(), this.Y()]; 238 v[2] = [this.Z(), this.X(), this.Y() + this.H()]; 239 240 // Transform the three corners 241 for (i = 0; i < len; i++) { 242 for (j = 0; j < 3; j++) { 243 v[j] = Mat.matVecMult(this.transformations[i].matrix, v[j]); 244 } 245 } 246 // Normalize the vectors 247 for (j = 0; j < 3; j++) { 248 v[j][1] /= v[j][0]; 249 v[j][2] /= v[j][0]; 250 v[j][0] /= v[j][0]; 251 } 252 // Compute the two vectors spanning the rectangle 253 // by subtracting the anchor point. 254 for (j = 1; j < 3; j++) { 255 v[j][0] -= v[0][0]; 256 v[j][1] -= v[0][1]; 257 v[j][2] -= v[0][2]; 258 } 259 this.span = v; 260 } 261 262 return this; 263 }, 264 265 addTransform: function (transform) { 266 var i; 267 268 if (Type.isArray(transform)) { 269 for (i = 0; i < transform.length; i++) { 270 this.transformations.push(transform[i]); 271 } 272 } else { 273 this.transformations.push(transform); 274 } 275 276 return this; 277 }, 278 279 // Documented in element.js 280 getParents: function () { 281 var p = [this.url, [this.Z(), this.X(), this.Y()], this.usrSize]; 282 283 if (this.parents.length !== 0) { 284 p = this.parents; 285 } 286 287 return p; 288 }, 289 290 /** 291 * Set the width and height of the foreignObject. After setting a new size, 292 * board.update() or foreignobject.fullUpdate() 293 * has to be called to make the change visible. 294 * @param {number, function, string} width Number, function or string 295 * that determines the new width of the foreignObject 296 * @param {number, function, string} height Number, function or string 297 * that determines the new height of the foreignObject 298 * @returns {JXG.ForeignObject} A reference to the element 299 * 300 */ 301 setSize: function(width, height) { 302 this.W = Type.createFunction(width, this.board, ''); 303 this.H = Type.createFunction(height, this.board, ''); 304 this._useUserSize = true; 305 306 return this; 307 }, 308 309 /** 310 * Returns the width of the foreignObject in user coordinates. 311 * @returns {number} width of the image in user coordinates 312 */ 313 W: function() {}, // Needed for docs, defined in constructor 314 315 /** 316 * Returns the height of the foreignObject in user coordinates. 317 * @returns {number} height of the image in user coordinates 318 */ 319 H: function() {} // Needed for docs, defined in constructor 320 }); 321 322 /** 323 * @class This element is used to provide a constructor for arbitrary content in 324 * an SVG foreignObject container. 325 * <p> 326 * Instead of board.create('foreignobject') the shortcut board.create('fo') may be used. 327 * 328 * <p style="background-color:#dddddd; padding:10px"><b>NOTE:</b> In Safari up to version 15, a foreignObject does not obey the layer structure 329 * if it contains <video> or <iframe> tags, as well as elements which are 330 * positioned with <tt>position:absolute|relative|fixed</tt>. In this case, the foreignobject will be 331 * "above" the JSXGraph construction. 332 * </p> 333 * 334 * @pseudo 335 * @description 336 * @name ForeignObject 337 * @augments JXG.ForeignObject 338 * @constructor 339 * @type JXG.ForeignObject 340 * 341 * @param {String} content HTML content of the foreignObject. May also be <video> or <iframe> 342 * @param {Array} position Position of the foreignObject given by [x, y] in user coordinates. Same as for images. 343 * @param {Array} [size] (Optional) argument size of the foreignObject in user coordinates. If not given, size is specified by the HTML attributes 344 * or CSS properties of the content. 345 * 346 * @see Image 347 * 348 * @example 349 * var p = board.create('point', [1, 7], {size: 16}); 350 * var fo = board.create('foreignobject', [ 351 * '<video width="300" height="200" src="https://eucbeniki.sio.si/vega2/278/Video_metanje_oge_.mp4" type="html5video" controls>', 352 * [0, -3], [9, 6]], 353 * {layer: 8, fixed: true} 354 * ); 355 * 356 * </pre><div id="JXG0c122f2c-3671-4a28-80a9-f4c523eeda89" class="jxgbox" style="width: 500px; height: 500px;"></div> 357 * <script type="text/javascript"> 358 * (function() { 359 * var board = JXG.JSXGraph.initBoard('JXG0c122f2c-3671-4a28-80a9-f4c523eeda89', 360 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 361 * var p = board.create('point', [1, 7], {size: 16}); 362 * var fo = board.create('foreignobject', [ 363 * '<video width="300" height="200" src="https://eucbeniki.sio.si/vega2/278/Video_metanje_oge_.mp4" type="html5video" controls>', 364 * [0, -3], [9, 6]], 365 * {layer: 8, fixed: true} 366 * ); 367 * 368 * })(); 369 * 370 * </script><pre> 371 * 372 * @example 373 * var p = board.create('point', [1, 7], {size: 16}); 374 * var fo = board.create('fo', [ 375 * '<div style="background-color:blue; color: yellow; padding:20px; width:200px; height:50px; ">Hello</div>', 376 * [-7, -6]], 377 * {layer: 1, fixed: false} 378 * ); 379 * 380 * </pre><div id="JXG1759c868-1a4a-4767-802c-91f84902e3ec" class="jxgbox" style="width: 500px; height: 500px;"></div> 381 * <script type="text/javascript"> 382 * (function() { 383 * var board = JXG.JSXGraph.initBoard('JXG1759c868-1a4a-4767-802c-91f84902e3ec', 384 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 385 * var p = board.create('point', [1, 7], {size: 16}); 386 * var fo = board.create('foreignobject', [ 387 * '<div style="background-color:blue; color: yellow; padding:20px; width:200px; height:50px; ">Hello</div>', 388 * [-7, -6]], 389 * {layer: 1, fixed: false} 390 * ); 391 * 392 * })(); 393 * 394 * </script><pre> 395 * 396 * @example 397 * board.renderer.container.style.backgroundColor = 'lightblue'; 398 * var points = []; 399 * points.push( board.create('point', [-2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 am'}) ); 400 * points.push( board.create('point', [0, 3.5], {fixed:false,color: 'yellow', size: 6,name:'12 pm'}) ); 401 * points.push( board.create('point', [2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 pm'}) ); 402 * 403 * var fo = board.create('fo', [ 404 * '<video width="100%" height="100%" src="https://benedu.net/moodle/aaimg/ajx_img/astro/tr/1vd.mp4" type="html5video" controls>', 405 * [-6, -4], [12, 8]], 406 * {layer: 0, fixed: true} 407 * ); 408 * 409 * var f = JXG.Math.Numerics.lagrangePolynomial(points); 410 * var graph = board.create('functiongraph', [f, -10, 10], {fixed:true,strokeWidth:3, layer: 8}); 411 * 412 * </pre><div id="JXGc3fc5520-13aa-4f66-abaa-42e9dc3fbf3f" class="jxgbox" style="width: 500px; height: 500px;"></div> 413 * <script type="text/javascript"> 414 * (function() { 415 * var board = JXG.JSXGraph.initBoard('JXGc3fc5520-13aa-4f66-abaa-42e9dc3fbf3f', 416 * {boundingbox: [-6,4,6,-4], axis: true, showcopyright: false, shownavigation: false}); 417 * board.renderer.container.style.backgroundColor = 'lightblue'; 418 * var points = []; 419 * points.push( board.create('point', [-2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 am'}) ); 420 * points.push( board.create('point', [0, 3.5], {fixed:false,color: 'yellow', size: 6,name:'12 pm'}) ); 421 * points.push( board.create('point', [2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 pm'}) ); 422 * 423 * var fo = board.create('fo', [ 424 * '<video width="100%" height="100%" src="https://benedu.net/moodle/aaimg/ajx_img/astro/tr/1vd.mp4" type="html5video" controls>', 425 * [-6, -4], [12, 8]], 426 * {layer: 0, fixed: true} 427 * ); 428 * 429 * var f = JXG.Math.Numerics.lagrangePolynomial(points); 430 * var graph = board.create('functiongraph', [f, -10, 10], {fixed:true,strokeWidth:3, layer: 8}); 431 * 432 * })(); 433 * 434 * </script><pre> 435 * 436 * Video "24-hour time-lapse in Cascais, Portugal. Produced by Nuno Miguel Duarte" adapted from 437 * <a href="https://www.pbslearningmedia.org/resource/buac18-k2-sci-ess-sunposition/changing-position-of-the-sun-in-the-sky/">https://www.pbslearningmedia.org/resource/buac18-k2-sci-ess-sunposition/changing-position-of-the-sun-in-the-sky/</a>, 438 * ©2016 Nuno Miguel Duarte. 439 * 440 */ 441 JXG.createForeignObject = function (board, parents, attributes) { 442 var attr, fo, 443 content = parents[0], 444 coords = parents[1], 445 size = []; 446 447 if (parents.length >= 2) { 448 size = parents[2]; 449 } 450 451 attr = Type.copyAttributes(attributes, board.options, 'foreignobject'); 452 fo = CoordsElement.create(JXG.ForeignObject, board, coords, attr, content, size); 453 if (!fo) { 454 throw new Error("JSXGraph: Can't create foreignObject with parent types '" + 455 (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + 456 "\nPossible parent types: [string, [x, y], [w, h]], [string, [x, y]], [element,transformation]"); 457 } 458 459 return fo; 460 }; 461 462 JXG.registerElement('foreignobject', JXG.createForeignObject); 463 JXG.registerElement('fo', JXG.createForeignObject); 464 465 return { 466 ForeignObject: JXG.ForeignObject, 467 createForeignobject: JXG.createForeignObject 468 }; 469 }); 470