1 /* 2 Copyright 2008-2026 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 <https://www.gnu.org/licenses/> 29 and <https://opensource.org/licenses/MIT/>. 30 */ 31 32 /*global JXG: true, define: true, window: true*/ 33 /*jslint nomen: true, plusplus: true*/ 34 35 /** 36 * @fileoverview In this file the ForeignObject element is defined. 37 */ 38 39 import JXG from "../jxg.js"; 40 import Const from "./constants.js"; 41 import Coords from "./coords.js"; 42 import GeometryElement from "./element.js"; 43 import Mat from "../math/math.js"; 44 import Type from "../utils/type.js"; 45 import CoordsElement from "./coordselement.js"; 46 47 /** 48 * Construct and handle SVG foreignObjects. 49 * 50 * @class Creates a new foreignObject object. Do not use this constructor to create a foreignObject. Use {@link JXG.Board#create} with 51 * type {@link foreignobject} instead. 52 * @augments JXG.GeometryElement 53 * @augments JXG.CoordsElement 54 * @param {string|JXG.Board} board The board the new foreignObject is drawn on. 55 * @param {Array} coordinates An array with the user coordinates of the foreignObject. 56 * @param {Object} attributes An object containing visual and - optionally - a name and an id. 57 * @param {string|function} url An URL string or a function returning an URL string. 58 * @param {Array} size Array containing width and height of the foreignObject in user coordinates. 59 * 60 */ 61 JXG.ForeignObject = function (board, coords, attributes, content, size) { 62 this.constructor( 63 board, 64 attributes, 65 Const.OBJECT_TYPE_FOREIGNOBJECT, 66 Const.OBJECT_CLASS_OTHER 67 ); 68 this.element = this.board.select(attributes.anchor); 69 this.coordsConstructor(coords); 70 71 this._useUserSize = false; 72 73 /** 74 * Array of length two containing [width, height] of the foreignObject in pixel. 75 * @type Array 76 */ 77 this.size = [1, 1]; 78 if (Type.exists(size) && size.length > 0) { 79 this._useUserSize = true; 80 81 this.W = Type.createFunction(size[0], this.board, ""); 82 this.H = Type.createFunction(size[1], this.board, ""); 83 this.addParentsFromJCFunctions([this.W, this.H]); 84 85 this.usrSize = [this.W(), this.H()]; 86 } 87 88 // this.size = [Math.abs(this.usrSize[0] * board.unitX), Math.abs(this.usrSize[1] * board.unitY)]; 89 90 /** 91 * 'href' of the foreignObject. 92 * @type {string} 93 */ 94 this.content = content; 95 96 this.elType = 'foreignobject'; 97 98 // span contains the anchor point and the two vectors 99 // spanning the foreignObject rectangle. 100 // this.span = [ 101 // this.coords.usrCoords.slice(0), 102 // [this.coords.usrCoords[0], this.W(), 0], 103 // [this.coords.usrCoords[0], 0, this.H()] 104 // ]; 105 //this.parent = board.select(attributes.anchor); 106 107 this.id = this.board.setId(this, 'Im'); 108 109 this.board.renderer.drawForeignObject(this); 110 this.board.finalizeAdding(this); 111 }; 112 113 JXG.ForeignObject.prototype = new GeometryElement(); 114 115 Type.copyPrototypeMethods(JXG.ForeignObject, CoordsElement, 'coordsConstructor'); 116 Type.copyMethodMap(JXG.ForeignObject, { 117 addTransformation: "addTransform", 118 trans: "addTransform", 119 W: "W", 120 Width: "W", 121 H: "H", 122 Height: "H" 123 }); 124 125 JXG.extend( 126 JXG.ForeignObject.prototype, 127 /** @lends JXG.ForeignObject.prototype */ { 128 /** 129 * Checks whether (x,y) is over or near the image; 130 * @param {Number} x Coordinate in x direction, screen coordinates. 131 * @param {Number} y Coordinate in y direction, screen coordinates. 132 * @returns {Boolean} True if (x,y) is over the image, False otherwise. 133 */ 134 hasPoint: function (x, y) { 135 var dx, dy, r, type, prec, c, v, p, dot, 136 len = this.transformations.length; 137 138 if (Type.isObject(this.evalVisProp('precision'))) { 139 type = this.board._inputDevice; 140 prec = this.evalVisProp('precision.' + type); 141 } else { 142 // 'inherit' 143 prec = this.board.options.precision.hasPoint; 144 } 145 146 // Easy case: no transformation 147 if (len === 0) { 148 dx = x - this.coords.scrCoords[1]; 149 dy = this.coords.scrCoords[2] - y; 150 r = prec; 151 152 return dx >= -r && dx - this.size[0] <= r && dy >= -r && dy - this.size[1] <= r; 153 } 154 155 // foreignObject is transformed 156 c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board); 157 // v is the vector from anchor point to the drag point 158 c = c.usrCoords; 159 v = [c[0] - this.span[0][0], c[1] - this.span[0][1], 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 = [ 209 Math.abs(this.usrSize[0] * this.board.unitX), 210 Math.abs(this.usrSize[1] * this.board.unitY) 211 ]; 212 } else { 213 if (this.rendNode.hasChildNodes()) { 214 bb = this.rendNode.childNodes[0].getBoundingClientRect(); 215 this.size = [bb.width, bb.height]; 216 } 217 } 218 219 return this; 220 }, 221 222 /** 223 * Update the anchor point of the foreignObject, i.e. the lower left corner 224 * and the two vectors which span the rectangle. 225 * @returns {JXG.ForeignObject} A reference to the element 226 * @private 227 * 228 */ 229 updateSpan: function () { 230 var i, 231 j, 232 len = this.transformations.length, 233 v = []; 234 235 if (len === 0) { 236 this.span = [ 237 [this.Z(), this.X(), this.Y()], 238 [this.Z(), this.W(), 0], 239 [this.Z(), 0, this.H()] 240 ]; 241 } else { 242 // v contains the three defining corners of the rectangle/image 243 v[0] = [this.Z(), this.X(), this.Y()]; 244 v[1] = [this.Z(), this.X() + this.W(), this.Y()]; 245 v[2] = [this.Z(), this.X(), this.Y() + this.H()]; 246 247 // Transform the three corners 248 for (i = 0; i < len; i++) { 249 for (j = 0; j < 3; j++) { 250 v[j] = Mat.matVecMult(this.transformations[i].matrix, v[j]); 251 } 252 } 253 // Normalize the vectors 254 for (j = 0; j < 3; j++) { 255 v[j][1] /= v[j][0]; 256 v[j][2] /= v[j][0]; 257 v[j][0] /= v[j][0]; 258 } 259 // Compute the two vectors spanning the rectangle 260 // by subtracting the anchor point. 261 for (j = 1; j < 3; j++) { 262 v[j][0] -= v[0][0]; 263 v[j][1] -= v[0][1]; 264 v[j][2] -= v[0][2]; 265 } 266 this.span = v; 267 } 268 269 return this; 270 }, 271 272 addTransform: function (transform) { 273 var i, 274 list = Type.isArray(transform) ? transform : [transform], 275 len = list.length; 276 277 for (i = 0; i < len; i++) { 278 this.transformations.push(list[i]); 279 } 280 281 return this; 282 }, 283 284 removeTransform: function (transform) { 285 var i, 286 list = Type.isArray(transform) ? transform : [transform], 287 len = list.length; 288 289 for (i = 0; i < len; i++) { 290 Type.removeElementFromArray(this.transformations, list[i]); 291 } 292 293 return this; 294 }, 295 296 clearTransforms: function () { 297 this.transformations = []; 298 299 return this; 300 }, 301 302 // Documented in element.js 303 getParents: function () { 304 var p = [this.url, [this.Z(), this.X(), this.Y()], this.usrSize]; 305 306 if (this.parents.length !== 0) { 307 p = this.parents; 308 } 309 310 return p; 311 }, 312 313 /** 314 * Set the width and height of the foreignObject. After setting a new size, 315 * board.update() or foreignobject.fullUpdate() 316 * has to be called to make the change visible. 317 * @param {numbe|function|string} width Number, function or string 318 * that determines the new width of the foreignObject 319 * @param {number|function|string} height Number, function or string 320 * that determines the new height of the foreignObject 321 * @returns {JXG.ForeignObject} A reference to the element 322 * 323 */ 324 setSize: function (width, height) { 325 this.W = Type.createFunction(width, this.board, ""); 326 this.H = Type.createFunction(height, this.board, ""); 327 this._useUserSize = true; 328 this.addParentsFromJCFunctions([this.W, this.H]); 329 330 return this; 331 }, 332 333 /** 334 * Returns the width of the foreignObject in user coordinates. 335 * @returns {number} width of the image in user coordinates 336 */ 337 W: function () {}, // Needed for docs, defined in constructor 338 339 /** 340 * Returns the height of the foreignObject in user coordinates. 341 * @returns {number} height of the image in user coordinates 342 */ 343 H: function () {} // Needed for docs, defined in constructor 344 } 345 ); 346 347 /** 348 * @class Display any HTML content in an SVG foreignObject container - even below other elements. 349 * <p> 350 * Instead of board.create('foreignobject') the shortcut board.create('fo') may be used. 351 * 352 * <p style="background-color:#dddddd; padding:10px"><b>NOTE:</b> In Safari up to version 15, a foreignObject does not obey the layer structure 353 * if it contains <video> or <iframe> tags, as well as elements which are 354 * positioned with <tt>position:absolute|relative|fixed</tt>. In this case, the foreignobject will be 355 * "above" the JSXGraph construction. 356 * </p> 357 * 358 * @pseudo 359 * @name ForeignObject 360 * @augments JXG.ForeignObject 361 * @constructor 362 * @type JXG.ForeignObject 363 * 364 * @param {String} content HTML content of the foreignObject. May also be <video> or <iframe> 365 * @param {Array} position Position of the foreignObject given by [x, y] in user coordinates. Same as for images. 366 * @param {Array} [size] (Optional) argument size of the foreignObject in user coordinates. If not given, size is specified by the HTML attributes 367 * or CSS properties of the content. 368 * 369 * @see Image 370 * 371 * @example 372 * var p = board.create('point', [1, 7], {size: 16}); 373 * var fo = board.create('foreignobject', [ 374 * '<video width="300" height="200" src="https://eucbeniki.sio.si/vega2/278/Video_metanje_oge_.mp4" type="html5video" controls>', 375 * [0, -3], [9, 6]], 376 * {layer: 8, fixed: true} 377 * ); 378 * 379 * </pre><div id="JXG0c122f2c-3671-4a28-80a9-f4c523eeda89" class="jxgbox" style="width: 500px; height: 500px;"></div> 380 * <script type="text/javascript"> 381 * (function() { 382 * var board = JXG.JSXGraph.initBoard('JXG0c122f2c-3671-4a28-80a9-f4c523eeda89', 383 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 384 * var p = board.create('point', [1, 7], {size: 16}); 385 * var fo = board.create('foreignobject', [ 386 * '<video width="300" height="200" src="https://eucbeniki.sio.si/vega2/278/Video_metanje_oge_.mp4" type="html5video" controls>', 387 * [0, -3], [9, 6]], 388 * {layer: 8, fixed: true} 389 * ); 390 * 391 * })(); 392 * 393 * </script><pre> 394 * 395 * @example 396 * var p = board.create('point', [1, 7], {size: 16}); 397 * var fo = board.create('fo', [ 398 * '<div style="background-color:blue; color: yellow; padding:20px; width:200px; height:50px; ">Hello</div>', 399 * [-7, -6]], 400 * {layer: 1, fixed: false} 401 * ); 402 * 403 * </pre><div id="JXG1759c868-1a4a-4767-802c-91f84902e3ec" class="jxgbox" style="width: 500px; height: 500px;"></div> 404 * <script type="text/javascript"> 405 * (function() { 406 * var board = JXG.JSXGraph.initBoard('JXG1759c868-1a4a-4767-802c-91f84902e3ec', 407 * {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false}); 408 * var p = board.create('point', [1, 7], {size: 16}); 409 * var fo = board.create('foreignobject', [ 410 * '<div style="background-color:blue; color: yellow; padding:20px; width:200px; height:50px; ">Hello</div>', 411 * [-7, -6]], 412 * {layer: 1, fixed: false} 413 * ); 414 * 415 * })(); 416 * 417 * </script><pre> 418 * 419 * @example 420 * board.renderer.container.style.backgroundColor = 'lightblue'; 421 * var points = []; 422 * points.push( board.create('point', [-2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 am'}) ); 423 * points.push( board.create('point', [0, 3.5], {fixed:false,color: 'yellow', size: 6,name:'12 pm'}) ); 424 * points.push( board.create('point', [2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 pm'}) ); 425 * 426 * var fo = board.create('fo', [ 427 * '<video width="100%" height="100%" src="https://benedu.net/moodle/aaimg/ajx_img/astro/tr/1vd.mp4" type="html5video" controls>', 428 * [-6, -4], [12, 8]], 429 * {layer: 0, fixed: true} 430 * ); 431 * 432 * var f = JXG.Math.Numerics.lagrangePolynomial(points); 433 * var graph = board.create('functiongraph', [f, -10, 10], {fixed:true,strokeWidth:3, layer: 8}); 434 * 435 * </pre><div id="JXGc3fc5520-13aa-4f66-abaa-42e9dc3fbf3f" class="jxgbox" style="width: 500px; height: 500px;"></div> 436 * <script type="text/javascript"> 437 * (function() { 438 * var board = JXG.JSXGraph.initBoard('JXGc3fc5520-13aa-4f66-abaa-42e9dc3fbf3f', 439 * {boundingbox: [-6,4,6,-4], axis: true, showcopyright: false, shownavigation: false}); 440 * board.renderer.container.style.backgroundColor = 'lightblue'; 441 * var points = []; 442 * points.push( board.create('point', [-2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 am'}) ); 443 * points.push( board.create('point', [0, 3.5], {fixed:false,color: 'yellow', size: 6,name:'12 pm'}) ); 444 * points.push( board.create('point', [2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 pm'}) ); 445 * 446 * var fo = board.create('fo', [ 447 * '<video width="100%" height="100%" src="https://benedu.net/moodle/aaimg/ajx_img/astro/tr/1vd.mp4" type="html5video" controls>', 448 * [-6, -4], [12, 8]], 449 * {layer: 0, fixed: true} 450 * ); 451 * 452 * var f = JXG.Math.Numerics.lagrangePolynomial(points); 453 * var graph = board.create('functiongraph', [f, -10, 10], {fixed:true,strokeWidth:3, layer: 8}); 454 * 455 * })(); 456 * 457 * </script><pre> 458 * 459 * Video "24-hour time-lapse in Cascais, Portugal. Produced by Nuno Miguel Duarte" adapted from 460 * <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>, 461 * ©2016 Nuno Miguel Duarte. 462 * 463 */ 464 JXG.createForeignObject = function (board, parents, attributes) { 465 var attr, 466 fo, 467 content = parents[0], 468 coords = parents[1], 469 size = []; 470 471 if (parents.length >= 2) { 472 size = parents[2]; 473 } 474 475 attr = Type.copyAttributes(attributes, board.options, 'foreignobject'); 476 fo = CoordsElement.create(JXG.ForeignObject, board, coords, attr, content, size); 477 if (!fo) { 478 throw new Error( 479 "JSXGraph: Can't create foreignObject with parent types '" + 480 typeof parents[0] + 481 "' and '" + 482 typeof parents[1] + 483 "'." + 484 "\nPossible parent types: [string, [x, y], [w, h]], [string, [x, y]], [element,transformation]" 485 ); 486 } 487 488 return fo; 489 }; 490 491 JXG.registerElement("foreignobject", JXG.createForeignObject); 492 JXG.registerElement("fo", JXG.createForeignObject); 493 494 export default JXG.ForeignObject; 495 // export default { 496 // ForeignObject: JXG.ForeignObject, 497 // createForeignobject: JXG.createForeignObject 498 // }; 499