/*
Copyright 2008-2026
Matthias Ehmann,
Michael Gerhaeuser,
Carsten Miller,
Bianca Valentin,
Andreas Walter,
Alfred Wassermann,
Peter Wilfahrt
This file is part of JSXGraph and JSXCompressor.
JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
JSXCompressor is free software dual licensed under the GNU LGPL or Apache License.
You can redistribute it and/or modify it under the terms of the
* GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version
OR
* MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
OR
* Apache License Version 2.0
JSXGraph is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License, Apache
License, and the MIT License along with JSXGraph. If not, see
<https://www.gnu.org/licenses/>, <https://www.apache.org/licenses/LICENSE-2.0.html>,
and <https://opensource.org/licenses/MIT/>.
*/
/*global JXG: true, define: true, jQuery: true, window: true, document: true, navigator: true, require: true, module: true, console: true */
/*jslint nomen:true, plusplus:true, forin:true*/
/**
* @fileoverview The JSXGraph object is defined in this file. JXG.JSXGraph controls all boards.
* It has methods to create, save, load and free boards. Additionally some helper functions are
* defined in this file directly in the JXG namespace.
*/
/**
* JXG is the top object of JSXGraph and defines the namespace of all classes.
*
* See {@link JXG.board} and {@link JXG.appBox}.
*
* @name JXG
* @exports jxg as JXG
* @namespace
*/
var jxg = {};
// Make sure JXG.extend is not defined.
// If JSXGraph is compiled as an amd module, it is possible that another JSXGraph version is already loaded and we
// therefore must not re-use the global JXG variable. But in this case JXG.extend will already be defined.
// This is the reason for this check.
// The try-statement is necessary, otherwise an error is thrown in certain imports, e.g. in deno.
try {
if (typeof JXG === "object" && !JXG.extend) {
jxg = JXG;
}
} catch (e) {}
// We need the following two methods "extend" and "shortcut" to create the JXG object via JXG.extend.
/**
* Copy all properties of the `extension` object to `object`.
* @param {Object} object
* @param {Object} extension
* @param {Boolean} [onlyOwn=false] Only consider properties that belong to extension itself, not any inherited properties.
* @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see {@link JXG#copyAttributes}
*/
jxg.extend = function (object, extension, onlyOwn, toLower) {
var e, e2;
onlyOwn = onlyOwn || false;
toLower = toLower || false;
// the purpose of this for...in loop is indeed to use hasOwnProperty only if the caller
// explicitly wishes so.
for (e in extension) {
if (!onlyOwn || (onlyOwn && extension.hasOwnProperty(e))) {
if (toLower) {
e2 = e.toLowerCase();
} else {
e2 = e;
}
object[e2] = extension[e];
}
}
};
// ----------------------------------------------------------------------
//
// typedef declarations for jsdoc
//
/**
* Function returning a point like object.
*
* This could be
* - a {@link Point}
* - coordinate array `[x, y]`
* - coordinate array `[z, x, y]` with homogeneous coordinates.
* In this case, `z` is 0 for infinite points, non-zero otherwise.
*
* @callback PointFunction
* @returns PointLike
*/
/**
* An array of length 2 or 3.
*
* - coordinate array `[x, y]`: *affine coordinates*
* - coordinate array `[z, x, y]`: *homogeneous coordinates*.
*
* In most cases, both types (affine or homogeneous) coordinates can be used.
*
* @typedef {array} Coordinates2D
*/
/**
* A point, coordinates array, or a function returning point or coordinates.
*
* @typedef {(Point | Coordinates2D | PointFunction)} PointLike
*/
/**
* A number or a function returning a number.
*
* @typedef {Number | Function} NumberLike
*/
/**
* An array of length 3 or 4.
*
* - coordinate array `[x, y, z]`: *affine coordinates*
* - coordinate array `[w, x, y, z]`: *homogeneous coordinates*.
*
* In most cases, both types (affine or homogeneous) coordinates can be used.
*
* @typedef {array} Coordinates3D
*/
/**
* A point, coordinates, or a function returning point or coordinates.
*
* @typedef {(Point3D | Coordinates3D | Point3DFunction)} Point3DLike
*/
/**
* Function returning a 3D point like object.
*
* This could be
* - a {@link Point3D}
* - coordinate array `[x, y, z]`
* - coordinate array `[w, x, y, z]` with homogeneous coordinates.
* In this case, `w` is 0 for infinite points, non-zero otherwise.
*
* @callback Point3DFunction
* @returns Point3DLike
*/
/**
* A line or an array of size 3 with homogenous coordinates defining the line.
*
* @typedef {(Line|number[])} LineType
* @memberof Line
*/
// ----------------------------------------------------------------------
/**
* Set a constant `name` in `object` to `value`. The value can't be changed after declaration.
* @param {Object} object
* @param {String} name
* @param {Number|String|Boolean} value
* @param {Boolean} ignoreRedefine This should be left at its default: false.
*/
jxg.defineConstant = function (object, name, value, ignoreRedefine) {
ignoreRedefine = ignoreRedefine || false;
if (ignoreRedefine && jxg.exists(object[name])) {
return;
}
Object.defineProperty(object, name, {
value: value,
writable: false,
enumerable: true,
configurable: false
});
};
/**
* Copy all properties of the `constants` object in `object` as a constant.
* @param {Object} object
* @param {Object} constants
* @param {Boolean} [onlyOwn=false] Only consider properties that belong to extension itself, not any inherited properties.
* @param {Boolean} [toUpper=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
*/
jxg.extendConstants = function (object, constants, onlyOwn, toUpper) {
var e, e2;
onlyOwn = onlyOwn || false;
toUpper = toUpper || false;
// The purpose of this for...in loop is indeed to use hasOwnProperty only if the caller explicitly wishes so.
for (e in constants) {
if (!onlyOwn || (onlyOwn && constants.hasOwnProperty(e))) {
if (toUpper) {
e2 = e.toUpperCase();
} else {
e2 = e;
}
this.defineConstant(object, e2, constants[e]);
}
}
};
jxg.extend(
jxg,
/** @lends JXG */ {
/**
* Store a reference to every board in this central list. This will at some point
* replace JXG.JSXGraph.boards.
* @type Object
*/
boards: {},
/**
* Store the available file readers in this structure.
* @type Object
*/
readers: {},
/**
* Associative array that keeps track of all constructable elements registered
* via {@link JXG.registerElement}.
* @type Object
*/
elements: {},
/**
* This registers a new construction element to JSXGraph for the construction via the {@link JXG.Board.create}
* interface.
* @param {String} element The elements name. This is case-insensitive, existing elements with the same name
* will be overwritten.
* @param {Function} creator A reference to a function taking three parameters: First the board, the element is
* to be created on, a parent element array, and an attributes object. See {@link JXG.createPoint} or any other
* `JXG.create...` function for an example.
*/
registerElement: function (element, creator) {
element = element.toLowerCase();
this.elements[element] = creator;
},
/**
* Register a file reader.
* @param {function} reader A file reader. This object has to provide two methods: `prepareString()`
* and `read()`.
* @param {Array} ext
*/
registerReader: function (reader, ext) {
var i, e;
for (i = 0; i < ext.length; i++) {
e = ext[i].toLowerCase();
if (typeof this.readers[e] !== 'function') {
this.readers[e] = reader;
}
}
},
/**
* Creates a shortcut to a method, e.g. {@link JXG.Board#createElement} is a shortcut to {@link JXG.Board#create}.
* Sometimes the target is undefined by the time you want to define the shortcut so we need this little helper.
* @param {Object} object The object the method we want to create a shortcut for belongs to.
* @param {String} fun The method we want to create a shortcut for.
* @returns {Function} A function that calls the given method.
*/
shortcut: function (object, fun) {
return function () {
return object[fun].apply(this, arguments);
};
},
/**
* s may be a string containing the name or id of an element or even a reference
* to the element itself. This function returns a reference to the element. Search order: id, name.
* @param {JXG.Board} board Reference to the board the element belongs to.
* @param {String} s String or reference to a JSXGraph element.
* @returns {Object} Reference to the object given in parameter object
* @deprecated Use {@link JXG.Board#select}
*/
getRef: function (board, s) {
jxg.deprecated("JXG.getRef()", "Board.select()");
return board.select(s);
},
/**
* This is just a shortcut to {@link JXG.getRef}.
* @deprecated Use {@link JXG.Board#select}.
*/
getReference: function (board, s) {
jxg.deprecated("JXG.getReference()", "Board.select()");
return board.select(s);
},
/**
* s may be the string containing the id of an HTML tag that hosts a JSXGraph board.
* This function returns the reference to the board.
* @param {String} s String of an HTML tag that hosts a JSXGraph board
* @returns {Object} Reference to the board or null.
*/
getBoardByContainerId: function (s) {
var b;
for (b in JXG.boards) {
if (JXG.boards.hasOwnProperty(b) && JXG.boards[b].container === s) {
return JXG.boards[b];
}
}
return null;
},
/**
* This method issues a warning to the developer that the given function is deprecated
* and, if available, offers an alternative to the deprecated function.
* @param {String} what Describes the function that is deprecated
* @param {String} [replacement] The replacement that should be used instead.
*/
deprecated: function (what, replacement) {
var warning = what + " is deprecated.";
if (replacement) {
warning += " Please use " + replacement + " instead.";
}
jxg.warn(warning);
},
/**
* Outputs a warning via console.warn(), if available. If console.warn() is
* unavailable this function will look for an HTML element with the id 'warning'
* and append the warning to this element's innerText.
* @param {String} warning The warning text
*/
warn: function (warning) {
if (typeof window === "object" && window.console && console.warn) {
console.warn("WARNING:", warning);
} else if (typeof document === "object" && document.getElementById('warning')) {
document.getElementById('debug').innerText += "WARNING: " + warning + '\n';
}
},
/**
* Add something to the debug log. If available a JavaScript debug console is used. Otherwise
* we're looking for a HTML div with id "debug". If this doesn't exist, too, the output is omitted.
* @param s An arbitrary number of parameters.
* @see JXG.debugWST
*/
debugInt: function (s) {
var i, p;
for (i = 0; i < arguments.length; i++) {
p = arguments[i];
if (typeof window === "object" && window.console && console.log) {
console.log(p);
} else if (typeof document === "object" && document.getElementById('debug')) {
document.getElementById('debug').innerText += p + '\n';
}
}
},
/**
* Add something to the debug log. If available a JavaScript debug console is used. Otherwise
* we're looking for a HTML div with id "debug". If this doesn't exist, too, the output is omitted.
* This method adds a stack trace (if available).
* @param s An arbitrary number of parameters.
* @see JXG.debug
*/
debugWST: function (s) {
var e = new Error();
jxg.debugInt.apply(this, arguments);
if (e && e.stack) {
jxg.debugInt('stacktrace');
jxg.debugInt(e.stack.split("\n").slice(1).join("\n"));
}
},
/**
* Add something to the debug log. If available a JavaScript debug console is used. Otherwise
* we're looking for a HTML div with id "debug". If this doesn't exist, too, the output is omitted.
* This method adds a line of the stack trace (if available).
*
* @param s An arbitrary number of parameters.
* @see JXG.debug
*/
debugLine: function (s) {
var e = new Error();
jxg.debugInt.apply(this, arguments);
if (e && e.stack) {
jxg.debugInt("Called from", e.stack.split("\n").slice(2, 3).join("\n"));
}
},
/**
* Add something to the debug log. If available a JavaScript debug console is used. Otherwise
* we're looking for a HTML div with id "debug". If this doesn't exist, too, the output is omitted.
* @param s An arbitrary number of parameters.
* @see JXG.debugWST
* @see JXG.debugLine
* @see JXG.debugInt
*/
debug: function (s) {
jxg.debugInt.apply(this, arguments);
},
/**
* @class
*
* @pseudo
* @name JXG.board
* @elementclass board
*/
/**
* Initialize a new board.
* Alias of {@link JXG.JSXGraph.initBoard}.
* @param {String|Object} box id of or reference to the HTML element in which the board is painted.
* @param {Object} attributes An object that sets some of the board attributes.
* See {@link JXG.Board} for a list of available attributes of the board.
* Most of these attributes can also be set globally via {@link JXG.Options}.
*
* @returns {JXG.Board} Reference to the created board.
*
* @see JXG.AbstractRenderer#drawNavigationBar
* @example
* var board = JXG.board('jxgbox', {
* boundingbox: [-10, 5, 10, -5],
* keepaspectratio: false,
* axis: true
* });
*
* </pre><div id="JXG79b42d26-b664-451d-96b4-08bc25dd87d3" class="jxgbox" style="width: 600px; height: 300px;"></div>
* <script type="text/javascript">
* (function() {
* var board = JXG.board('JXG79b42d26-b664-451d-96b4-08bc25dd87d3', {
* boundingbox: [-10, 5, 10, -5],
* keepaspectratio: false,
* axis: true
* });
*
* })();
*
* </script><pre>
*
* @example
* const board = JXG.board('jxgbox', {
* boundingbox: [-10, 10, 10, -10],
* axis: true,
* showCopyright: true,
* showFullscreen: true,
* showScreenshot: false,
* showClearTraces: false,
* showInfobox: false,
* showNavigation: true,
* grid: false,
* defaultAxes: {
* x: {
* withLabel: true,
* label: {
* position: '95% left',
* offset: [-10, 10]
* },
* lastArrow: {
* type: 4,
* size: 10
* }
* },
* y: {
* withLabel: true,
* label: {
* position: '0.90fr right',
* offset: [6, -6]
* },
* lastArrow: {
* type: 4,
* size: 10
* }
* }
* }
* });
*
* </pre><div id="JXGd7a7705b-35bb-4193-bbd4-3e3fd92eb92c" class="jxgbox" style="width: 300px; height: 300px;"></div>
* <script type="text/javascript">
* (function() {
* var board = JXG.board('JXGd7a7705b-35bb-4193-bbd4-3e3fd92eb92c', {
* boundingbox: [-10, 10, 10, -10],
* axis: true,
* showCopyright: true,
* showFullscreen: true,
* showScreenshot: false,
* showClearTraces: false,
* showInfobox: false,
* showNavigation: true,
* grid: false,
* defaultAxes: {
* x: {
* withLabel: true,
* label: {
* position: '95% left',
* offset: [0, 0]
* },
* lastArrow: {
* type: 4,
* size: 10
* }
* },
* y: {
* withLabel: true,
* label: {
* position: '0.90fr right',
* offset: [0, 0]
* },
* lastArrow: {
* type: 4,
* size: 10
* }
* }
* }
* });
*
* })();
*
* </script><pre>
*
* @example
* const board = JXG.board('jxgbox', {
* boundingbox: [-5, 5, 5, -5],
* intl: {
* enabled: false,
* locale: 'en-EN'
* },
* keepaspectratio: true,
* axis: true,
* defaultAxes: {
* x: {
* ticks: {
* intl: {
* enabled: true,
* options: {
* style: 'unit',
* unit: 'kilometer-per-hour',
* unitDisplay: 'narrow'
* }
* }
* }
* },
* y: {
* ticks: {
* }
* }
* },
* infobox: {
* fontSize: 20,
* intl: {
* enabled: true,
* options: {
* minimumFractionDigits: 4,
* maximumFractionDigits: 5
* }
* }
* }
* });
*
* </pre><div id="JXGd84f4c84-f900-4d33-b001-e5f5f3ab0dd2" class="jxgbox" style="width: 600px; height: 600px;"></div>
* <script type="text/javascript">
* (function() {
* var board = JXG.board('JXGd84f4c84-f900-4d33-b001-e5f5f3ab0dd2', {
* boundingbox: [-5, 5, 5, -5],
* intl: {
* enabled: false,
* locale: 'en-EN'
* },
* keepaspectratio: true,
* axis: true,
* defaultAxes: {
* x: {
* ticks: {
* intl: {
* enabled: true,
* options: {
* style: 'unit',
* unit: 'kilometer-per-hour',
* unitDisplay: 'narrow'
* }
* }
* }
* },
* y: {
* ticks: {
* }
* }
* },
* infobox: {
* fontSize: 20,
* intl: {
* enabled: true,
* options: {
* minimumFractionDigits: 4,
* maximumFractionDigits: 5
* }
* }
* }
* });
*
* })();
*
* </script><pre>
*
*/
board: function (box, attributes) {
return this.JSXGraph.initBoard(box, attributes);
},
/**
* @class
*
* @pseudo
* @name JXG.appBox
* @elementclass board
*/
/**
* Create a JSXGraph div element containing a JSXGraph board inside of a user supplied div.
*
* The styling of the supplied div is up to the user, see the style-tag in the example below for
* one possibility. The CSS for the inner div, hosting the JSXGraph board, is supplied by the attributes
*
* ```
* jxgbox: {
* cssStyle: 'width:640px; aspect-ratio:2/1; background-color: white',
* cssClass: '',
* id: 'jxgbox'
* }
* ```
*
* i.e. the div's style-attribute and a list of classes (separated by blanks) can be given.
*
* By setting the attribute "clip" to false for selected
* elements (like sliders and texts), these elements can be positioned outside of the JSXGraph board. For those elements,
* the setting of the attributes "frozen:true, fixed:true" is recommended to make their position independent from zooming
* or panning the board coordinates.
*
* However, not all elements will look good if displayed outside of the JSXGraph board - be careful.
*
* @param {String|Object} box id of or reference to the HTML element in which the board is painted into a sub-element of type div.
* @param {Object} attributes An object that sets some of the board attributes and attributes of the sub-element containing the board.
* See {@link JXG.Board} for a list of available attributes of the board.
* Most of these attributes can also be set globally via {@link JXG.Options}.
*
* @returns {JXG.Board} Reference to the created board.
*
* @example
*
* // Styling of the outer div
* <style>
* .container {
* display: flex;
* align-items: center;
* justify-content: center;
* width: 800px;
* height: 500px;
* overflow: hidden;
* border: 1px black solid;
* border-radius: 10px;
* background-color: #eee;
* }
* </style>
* <div id="container" class="container"></div>
* <script type="text/javascript">
* const board = JXG.appBox('container', {
* jxgbox: {
* // Styling of the inner div
* cssStyle: 'width:640px; aspect-ratio:2/1; background-color: white',
* cssClass: '',
* id: 'jxgbox'
* },
* boundingbox: [-5, 5, 5, -5],
* axis: true,
* showFullScreen: true
* });
*
* const point1 = board.create("point", [4, 1], { name: 'A' });
* const point2 = board.create("point", [3, -1], {name: 'B'});
* const point3 = board.create("point", [6, -1], { clip: false });
*
* var sl = board.create('slider', [[-3, -6], [-1, -6], [-5, 1, 5]], {
* clip: false,
* frozen: true,
* size: 16,
* face: '[]',
* name: 's'
* });
*
* var graph = board.create("functiongraph", ['s.Value() * x^3'], { clip: true });
*
* </script>
* </pre><div id="JXGd1c7bf6a-a571-4392-a289-e4ef44d57c88" class="container"></div>
* <style>
* .container {
* display: flex;
* align-items: center;
* justify-content: center;
* width: 800px;
* height: 500px;
* overflow: hidden;
* border: 1px black solid;
* border-radius: 10px;
* background-color: #eee;
* }
* </style>
*
* <script type="text/javascript">
* (function() {
* const board = JXG.appBox('JXGd1c7bf6a-a571-4392-a289-e4ef44d57c88', {
* jxgbox: {
* cssStyle: "width:640px; aspect-ratio:2/1; background-color: white",
* cssClass: "",
* id: 'xxx'
* },
* boundingbox: [-5, 5, 5, -5],
* axis: true,
* showFullScreen: true
* });
*
* const point1 = board.create("point", [4, 1], { name: 'A' });
* const point2 = board.create("point", [3, -1], {name: 'B'});
* const point3 = board.create("point", [6, -1], { clip: false });
*
* var sl = board.create('slider', [[-3, -6], [-1, -6], [-5, 1, 5]], {
* clip: false,
* size: 16,
* face: '[]',
* name: 's',
* frozen: true
* });
*
* var graph = board.create("functiongraph", ['s.Value() * x^3'], { clip: true });
*
* })();
*
* </script><pre>
*
*/
appBox: function (box, attributes) {
var node, id, jxg_id, innerdiv,
obb, bb, w, h,
attr, board;
if (!JXG.isBrowser) {
throw new Error("JSXGraph: JXG.appBox needs a browser");
}
if (JXG.isString(box)) {
// Hosting div is given as string
node = document.getElementById(box);
id = box;
} else {
// Hosting div is given as object pointer
node = box;
id = box.getAttribute('id');
}
innerdiv = document.createElement("div");
attr = JXG.copyAttributes(attributes, JXG.Options, 'board').jxgbox;
jxg_id = ((id !== null) ? id + '_' : '') + attr.id;
innerdiv.setAttribute('id', jxg_id);
innerdiv.className += 'jxgbox ';
innerdiv.className += attr.cssclass;
innerdiv.style = attr.cssstyle;
obb = attr.outerbox;
if (obb !== null && JXG.isArray(obb)) {
bb = JXG.copyAttributes(attributes, JXG.Options, 'board').boundingbox;
node.style.position = 'relative';
w = obb[2] - obb[0];
h = obb[1] - obb[3];
innerdiv.style.position = 'absolute';
innerdiv.style.left = (100 * (bb[0] - obb[0]) / w) + '%';
innerdiv.style.top = (100 * (obb[1] - bb[1]) / h) + '%';
innerdiv.style.width = (100 * (bb[2] - bb[0]) / w) + '%';
innerdiv.style.height = (100 * (bb[1] - bb[3]) / h) + '%';
// rect = node.getBoundingClientRect();
// innerdiv.style.left = ((bb[0] - obb[0]) / w * rect.width) + 'px';
// innerdiv.style.top = ((obb[1] - bb[1]) / h * rect.height) + 'px';
// innerdiv.style.width = ((bb[2] - bb[0]) / w * rect.width) + 'px';
// innerdiv.style.height = ((bb[1] - bb[3]) / h * rect.height) + 'px';
}
node.appendChild(innerdiv);
attributes.moveTarget = node;
board = this.board(innerdiv, attributes);
innerdiv.style.overflow = 'visible';
if (board.renderer.type === 'svg') {
board.renderer.svgRoot.style.overflow = 'visible';
} else {
throw new Error("JSXGraph: JXG.appBox needs SVG renderer");
}
return board;
},
/**
* @class Collection of themes
*
* @name JXG.themes
* @elementclass themes
* @type Object
*
*/
themes: {}
}
);
export default jxg;