(function (root, factory) {
 if (typeof define === 'function' && define.amd) {
 define('jsxgraphcore', [], factory);
 } else if (typeof module === 'object' && module.exports) {
 module.exports = factory();
 } else {
 root.returnExports = factory();
 }
}(this, function () {
/**
 * @license almond 0.3.3 Copyright jQuery Foundation and other contributors.
 * Released under MIT license, http://github.com/requirejs/almond/LICENSE
 */
//Going sloppy to avoid 'use strict' string cost, but strict practices should
//be followed.
/*global setTimeout: false */

var requirejs, require, define;
(function (undef) {
    var main, req, makeMap, handlers,
        defined = {},
        waiting = {},
        config = {},
        defining = {},
        hasOwn = Object.prototype.hasOwnProperty,
        aps = [].slice,
        jsSuffixRegExp = /\.js$/;

    function hasProp(obj, prop) {
        return hasOwn.call(obj, prop);
    }

    /**
     * Given a relative module name, like ./something, normalize it to
     * a real name that can be mapped to a path.
     * @param {String} name the relative name
     * @param {String} baseName a real name that the name arg is relative
     * to.
     * @returns {String} normalized name
     */
    function normalize(name, baseName) {
        var nameParts, nameSegment, mapValue, foundMap, lastIndex,
            foundI, foundStarMap, starI, i, j, part, normalizedBaseParts,
            baseParts = baseName && baseName.split("/"),
            map = config.map,
            starMap = (map && map['*']) || {};

        //Adjust any relative paths.
        if (name) {
            name = name.split('/');
            lastIndex = name.length - 1;

            // If wanting node ID compatibility, strip .js from end
            // of IDs. Have to do this here, and not in nameToUrl
            // because node allows either .js or non .js to map
            // to same file.
            if (config.nodeIdCompat && jsSuffixRegExp.test(name[lastIndex])) {
                name[lastIndex] = name[lastIndex].replace(jsSuffixRegExp, '');
            }

            // Starts with a '.' so need the baseName
            if (name[0].charAt(0) === '.' && baseParts) {
                //Convert baseName to array, and lop off the last part,
                //so that . matches that 'directory' and not name of the baseName's
                //module. For instance, baseName of 'one/two/three', maps to
                //'one/two/three.js', but we want the directory, 'one/two' for
                //this normalization.
                normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
                name = normalizedBaseParts.concat(name);
            }

            //start trimDots
            for (i = 0; i < name.length; i++) {
                part = name[i];
                if (part === '.') {
                    name.splice(i, 1);
                    i -= 1;
                } else if (part === '..') {
                    // If at the start, or previous value is still ..,
                    // keep them so that when converted to a path it may
                    // still work when converted to a path, even though
                    // as an ID it is less than ideal. In larger point
                    // releases, may be better to just kick out an error.
                    if (i === 0 || (i === 1 && name[2] === '..') || name[i - 1] === '..') {
                        continue;
                    } else if (i > 0) {
                        name.splice(i - 1, 2);
                        i -= 2;
                    }
                }
            }
            //end trimDots

            name = name.join('/');
        }

        //Apply map config if available.
        if ((baseParts || starMap) && map) {
            nameParts = name.split('/');

            for (i = nameParts.length; i > 0; i -= 1) {
                nameSegment = nameParts.slice(0, i).join("/");

                if (baseParts) {
                    //Find the longest baseName segment match in the config.
                    //So, do joins on the biggest to smallest lengths of baseParts.
                    for (j = baseParts.length; j > 0; j -= 1) {
                        mapValue = map[baseParts.slice(0, j).join('/')];

                        //baseName segment has  config, find if it has one for
                        //this name.
                        if (mapValue) {
                            mapValue = mapValue[nameSegment];
                            if (mapValue) {
                                //Match, update name to the new value.
                                foundMap = mapValue;
                                foundI = i;
                                break;
                            }
                        }
                    }
                }

                if (foundMap) {
                    break;
                }

                //Check for a star map match, but just hold on to it,
                //if there is a shorter segment match later in a matching
                //config, then favor over this star map.
                if (!foundStarMap && starMap && starMap[nameSegment]) {
                    foundStarMap = starMap[nameSegment];
                    starI = i;
                }
            }

            if (!foundMap && foundStarMap) {
                foundMap = foundStarMap;
                foundI = starI;
            }

            if (foundMap) {
                nameParts.splice(0, foundI, foundMap);
                name = nameParts.join('/');
            }
        }

        return name;
    }

    function makeRequire(relName, forceSync) {
        return function () {
            //A version of a require function that passes a moduleName
            //value for items that may need to
            //look up paths relative to the moduleName
            var args = aps.call(arguments, 0);

            //If first arg is not require('string'), and there is only
            //one arg, it is the array form without a callback. Insert
            //a null so that the following concat is correct.
            if (typeof args[0] !== 'string' && args.length === 1) {
                args.push(null);
            }
            return req.apply(undef, args.concat([relName, forceSync]));
        };
    }

    function makeNormalize(relName) {
        return function (name) {
            return normalize(name, relName);
        };
    }

    function makeLoad(depName) {
        return function (value) {
            defined[depName] = value;
        };
    }

    function callDep(name) {
        if (hasProp(waiting, name)) {
            var args = waiting[name];
            delete waiting[name];
            defining[name] = true;
            main.apply(undef, args);
        }

        if (!hasProp(defined, name) && !hasProp(defining, name)) {
            throw new Error('No ' + name);
        }
        return defined[name];
    }

    //Turns a plugin!resource to [plugin, resource]
    //with the plugin being undefined if the name
    //did not have a plugin prefix.
    function splitPrefix(name) {
        var prefix,
            index = name ? name.indexOf('!') : -1;
        if (index > -1) {
            prefix = name.substring(0, index);
            name = name.substring(index + 1, name.length);
        }
        return [prefix, name];
    }

    //Creates a parts array for a relName where first part is plugin ID,
    //second part is resource ID. Assumes relName has already been normalized.
    function makeRelParts(relName) {
        return relName ? splitPrefix(relName) : [];
    }

    /**
     * Makes a name map, normalizing the name, and using a plugin
     * for normalization if necessary. Grabs a ref to plugin
     * too, as an optimization.
     */
    makeMap = function (name, relParts) {
        var plugin,
            parts = splitPrefix(name),
            prefix = parts[0],
            relResourceName = relParts[1];

        name = parts[1];

        if (prefix) {
            prefix = normalize(prefix, relResourceName);
            plugin = callDep(prefix);
        }

        //Normalize according
        if (prefix) {
            if (plugin && plugin.normalize) {
                name = plugin.normalize(name, makeNormalize(relResourceName));
            } else {
                name = normalize(name, relResourceName);
            }
        } else {
            name = normalize(name, relResourceName);
            parts = splitPrefix(name);
            prefix = parts[0];
            name = parts[1];
            if (prefix) {
                plugin = callDep(prefix);
            }
        }

        //Using ridiculous property names for space reasons
        return {
            f: prefix ? prefix + '!' + name : name, //fullName
            n: name,
            pr: prefix,
            p: plugin
        };
    };

    function makeConfig(name) {
        return function () {
            return (config && config.config && config.config[name]) || {};
        };
    }

    handlers = {
        require: function (name) {
            return makeRequire(name);
        },
        exports: function (name) {
            var e = defined[name];
            if (typeof e !== 'undefined') {
                return e;
            } else {
                return (defined[name] = {});
            }
        },
        module: function (name) {
            return {
                id: name,
                uri: '',
                exports: defined[name],
                config: makeConfig(name)
            };
        }
    };

    main = function (name, deps, callback, relName) {
        var cjsModule, depName, ret, map, i, relParts,
            args = [],
            callbackType = typeof callback,
            usingExports;

        //Use name if no relName
        relName = relName || name;
        relParts = makeRelParts(relName);

        //Call the callback to define the module, if necessary.
        if (callbackType === 'undefined' || callbackType === 'function') {
            //Pull out the defined dependencies and pass the ordered
            //values to the callback.
            //Default to [require, exports, module] if no deps
            deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
            for (i = 0; i < deps.length; i += 1) {
                map = makeMap(deps[i], relParts);
                depName = map.f;

                //Fast path CommonJS standard dependencies.
                if (depName === "require") {
                    args[i] = handlers.require(name);
                } else if (depName === "exports") {
                    //CommonJS module spec 1.1
                    args[i] = handlers.exports(name);
                    usingExports = true;
                } else if (depName === "module") {
                    //CommonJS module spec 1.1
                    cjsModule = args[i] = handlers.module(name);
                } else if (hasProp(defined, depName) ||
                           hasProp(waiting, depName) ||
                           hasProp(defining, depName)) {
                    args[i] = callDep(depName);
                } else if (map.p) {
                    map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
                    args[i] = defined[depName];
                } else {
                    throw new Error(name + ' missing ' + depName);
                }
            }

            ret = callback ? callback.apply(defined[name], args) : undefined;

            if (name) {
                //If setting exports via "module" is in play,
                //favor that over return value and exports. After that,
                //favor a non-undefined return value over exports use.
                if (cjsModule && cjsModule.exports !== undef &&
                        cjsModule.exports !== defined[name]) {
                    defined[name] = cjsModule.exports;
                } else if (ret !== undef || !usingExports) {
                    //Use the return value from the function.
                    defined[name] = ret;
                }
            }
        } else if (name) {
            //May just be an object definition for the module. Only
            //worry about defining if have a module name.
            defined[name] = callback;
        }
    };

    requirejs = require = req = function (deps, callback, relName, forceSync, alt) {
        if (typeof deps === "string") {
            if (handlers[deps]) {
                //callback in this case is really relName
                return handlers[deps](callback);
            }
            //Just return the module wanted. In this scenario, the
            //deps arg is the module name, and second arg (if passed)
            //is just the relName.
            //Normalize module name, if it contains . or ..
            return callDep(makeMap(deps, makeRelParts(callback)).f);
        } else if (!deps.splice) {
            //deps is a config object, not an array.
            config = deps;
            if (config.deps) {
                req(config.deps, config.callback);
            }
            if (!callback) {
                return;
            }

            if (callback.splice) {
                //callback is an array, which means it is a dependency list.
                //Adjust args if there are dependencies
                deps = callback;
                callback = relName;
                relName = null;
            } else {
                deps = undef;
            }
        }

        //Support require(['a'])
        callback = callback || function () {};

        //If relName is a function, it is an errback handler,
        //so remove it.
        if (typeof relName === 'function') {
            relName = forceSync;
            forceSync = alt;
        }

        //Simulate async callback;
        if (forceSync) {
            main(undef, deps, callback, relName);
        } else {
            //Using a non-zero value because of concern for what old browsers
            //do, and latest browsers "upgrade" to 4 if lower value is used:
            //http://www.whatwg.org/specs/web-apps/current-work/multipage/timers.html#dom-windowtimers-settimeout:
            //If want a value immediately, use require('id') instead -- something
            //that works in almond on the global level, but not guaranteed and
            //unlikely to work in other AMD implementations.
            setTimeout(function () {
                main(undef, deps, callback, relName);
            }, 4);
        }

        return req;
    };

    /**
     * Just drops the config on the floor, but returns req in case
     * the config return value is used.
     */
    req.config = function (cfg) {
        return req(cfg);
    };

    /**
     * Expose module registry for debugging and tooling
     */
    requirejs._defined = defined;

    define = function (name, deps, callback) {
        if (typeof name !== 'string') {
            throw new Error('See almond README: incorrect module build, no module name');
        }

        //This module may not have dependencies
        if (!deps.splice) {
            //deps is not an array, so probably means
            //an object literal or factory function for
            //the value. Adjust args.
            callback = deps;
            deps = [];
        }

        if (!hasProp(defined, name) && !hasProp(waiting, name)) {
            waiting[name] = [name, deps, callback];
        }
    };

    define.amd = {
        jQuery: true
    };
}());

define("../node_modules/almond/almond", function(){});

/*
    Copyright 2008-2022
        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
    <http://www.gnu.org/licenses/>, <https://www.apache.org/licenses/LICENSE-2.0.html>,
    and <http://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*/

/* depends:
 */

/**
 * @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.
 */

define('jxg',[], function () {

    'use strict';

    /**
     * JXG is the top object of JSXGraph and defines the namespace
     * @exports jxg as JXG
     */
    var jxg = {};

    // Make sure JXG.extend is not defined
    // If jsxgraph is loaded via loadjsxgraph.js, this is required, but JXG.extend will be undefined
    // 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.
    if (typeof JXG === 'object' && !JXG.extend) {
        jxg = JXG;
    }

    // We need the following two methods "extend" and "shortcut" to create the JXG object via JXG.extend.

    /**
     * Copy all properties of the <tt>extension</tt> object to <tt>object</tt>.
     * @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 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];
            }
        }
    };

    /**
     * Set a constant <tt>name</tt> in <tt>object</tt> to <tt>value</tt>. 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 <tt>constants</tt> object in <tt>object</tt> 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
         * <tt>JXG.create...</tt> 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: <tt>prepareString()</tt>
         * and <tt>read()</tt>.
         * @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 innerHTML.
         * @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').innerHTML += 'WARNING: ' + warning + '<br />';
            }
        },

        /**
         * 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').innerHTML += p + '<br/>';
                }
            }
        },

        /**
         * 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);
        }
    });

    return jxg;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Andreas Walter,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */

/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 */

define('base/constants',['jxg'], function (JXG) {
    'use strict';

    var major = 1,
        minor = 4,
        patch = 2,
        add = '', //'dev'
        version = major + '.' + minor + '.' + patch + (add ? '-' + add : ''),
        constants;

    constants = /** @lends JXG */ {
        /**
         * Constant: the currently used JSXGraph version.
         *
         * @name JXG.version
         * @type String
         */
        version: version,

        /**
         * Constant: the small gray version indicator in the top left corner of every JSXGraph board (if
         * showCopyright is not set to false on board creation).
         *
         * @name JXG.licenseText
         * @type String
         */
        licenseText: 'JSXGraph v' + version + ' Copyright (C) see https://jsxgraph.org',

        /**
         *  Constant: user coordinates relative to the coordinates system defined by the bounding box.
         *  @name JXG.COORDS_BY_USER
         *  @type Number
         */
        COORDS_BY_USER: 0x0001,

        /**
         *  Constant: screen coordinates in pixel relative to the upper left corner of the div element.
         *  @name JXG.COORDS_BY_SCREEN
         *  @type Number
         */
        COORDS_BY_SCREEN: 0x0002,

        // object types
        OBJECT_TYPE_ARC: 1,
        OBJECT_TYPE_ARROW: 2,
        OBJECT_TYPE_AXIS: 3,
        OBJECT_TYPE_AXISPOINT: 4,
        OBJECT_TYPE_TICKS: 5,
        OBJECT_TYPE_CIRCLE: 6,
        OBJECT_TYPE_CONIC: 7,
        OBJECT_TYPE_CURVE: 8,
        OBJECT_TYPE_GLIDER: 9,
        OBJECT_TYPE_IMAGE: 10,
        OBJECT_TYPE_LINE: 11,
        OBJECT_TYPE_POINT: 12,
        OBJECT_TYPE_SLIDER: 13,
        OBJECT_TYPE_CAS: 14,
        OBJECT_TYPE_GXTCAS: 15,
        OBJECT_TYPE_POLYGON: 16,
        OBJECT_TYPE_SECTOR: 17,
        OBJECT_TYPE_TEXT: 18,
        OBJECT_TYPE_ANGLE: 19,
        OBJECT_TYPE_INTERSECTION: 20,
        OBJECT_TYPE_TURTLE: 21,
        OBJECT_TYPE_VECTOR: 22,
        OBJECT_TYPE_OPROJECT: 23,
        OBJECT_TYPE_GRID: 24,
        OBJECT_TYPE_TANGENT: 25,
        OBJECT_TYPE_HTMLSLIDER: 26,
        OBJECT_TYPE_CHECKBOX: 27,
        OBJECT_TYPE_INPUT: 28,
        OBJECT_TYPE_BUTTON: 29,
        OBJECT_TYPE_TRANSFORMATION: 30,
        OBJECT_TYPE_FOREIGNOBJECT: 31,
        OBJECT_TYPE_VIEW3D: 32,

        // IMPORTANT:
        // ----------
        // For being able to differentiate between the (sketchometry specific) SPECIAL_OBJECT_TYPEs and
        // (core specific) OBJECT_TYPEs, the non-sketchometry types MUST NOT be changed
        // to values > 100.

        // object classes
        OBJECT_CLASS_POINT: 1,
        OBJECT_CLASS_LINE: 2,
        OBJECT_CLASS_CIRCLE: 3,
        OBJECT_CLASS_CURVE: 4,
        OBJECT_CLASS_AREA: 5,
        OBJECT_CLASS_OTHER: 6,
        OBJECT_CLASS_TEXT: 7,

        // SketchReader constants
        GENTYPE_ABC: 1, // unused
        GENTYPE_AXIS: 2,
        GENTYPE_MID: 3,

        /**
         * @ignore
         * @deprecated, now use {@link JXG.GENTYPE_REFLECTION_ON_LINE}
         *
         */
        GENTYPE_REFLECTION: 4,
        /**
         * @ignore
         * @deprecated, now use {@link JXG.GENTYPE_REFLECTION_ON_POINT}
         */
        GENTYPE_MIRRORELEMENT: 5,

        GENTYPE_REFLECTION_ON_LINE: 4,
        GENTYPE_REFLECTION_ON_POINT: 5,
        GENTYPE_TANGENT: 6,
        GENTYPE_PARALLEL: 7,
        GENTYPE_BISECTORLINES: 8,
        GENTYPE_BOARDIMG: 9,
        GENTYPE_BISECTOR: 10,
        GENTYPE_NORMAL: 11,
        GENTYPE_POINT: 12,
        GENTYPE_GLIDER: 13,
        GENTYPE_INTERSECTION: 14,
        GENTYPE_CIRCLE: 15,
        /**
         * @ignore @deprecated NOT USED ANY MORE SINCE SKETCHOMETRY 2.0 (only for old constructions needed)
         */
        GENTYPE_CIRCLE2POINTS: 16,

        GENTYPE_LINE: 17,
        GENTYPE_TRIANGLE: 18,
        GENTYPE_QUADRILATERAL: 19,
        GENTYPE_TEXT: 20,
        GENTYPE_POLYGON: 21,
        GENTYPE_REGULARPOLYGON: 22,
        GENTYPE_SECTOR: 23,
        GENTYPE_ANGLE: 24,
        GENTYPE_PLOT: 25,
        GENTYPE_SLIDER: 26,
        GENTYPE_TRUNCATE: 27,
        GENTYPE_JCODE: 28,
        GENTYPE_MOVEMENT: 29,
        GENTYPE_COMBINED: 30,
        GENTYPE_RULER: 31,
        GENTYPE_SLOPETRIANGLE: 32,
        GENTYPE_PERPSEGMENT: 33,
        GENTYPE_LABELMOVEMENT: 34,
        GENTYPE_VECTOR: 35,
        GENTYPE_NONREFLEXANGLE: 36,
        GENTYPE_REFLEXANGLE: 37,
        GENTYPE_PATH: 38,
        GENTYPE_DERIVATIVE: 39,
        // 40 // unused ...
        GENTYPE_DELETE: 41,
        GENTYPE_COPY: 42,
        GENTYPE_MIRROR: 43,
        GENTYPE_ROTATE: 44,
        GENTYPE_ABLATION: 45,
        GENTYPE_MIGRATE: 46,
        GENTYPE_VECTORCOPY: 47,
        GENTYPE_POLYGONCOPY: 48,    /**
        * Constants
        * @name Constants
        * @namespace
        */

        //        GENTYPE_TRANSFORM: 48, // unused
        // 49 ... 50 // unused ...

        // IMPORTANT:
        // ----------
        // For being able to differentiate between the (GUI-specific) CTX and
        // (CORE-specific) non-CTX steps, the non-CTX steps MUST NOT be changed
        // to values > 50.

        GENTYPE_CTX_TYPE_G: 51,
        GENTYPE_CTX_TYPE_P: 52,
        GENTYPE_CTX_TRACE: 53,
        GENTYPE_CTX_VISIBILITY: 54,
        GENTYPE_CTX_CCVISIBILITY: 55, // unused
        GENTYPE_CTX_MPVISIBILITY: 56,
        GENTYPE_CTX_WITHLABEL: 57,
        GENTYPE_CTX_LABEL: 58,
        GENTYPE_CTX_FIXED: 59,
        GENTYPE_CTX_STROKEWIDTH: 60,
        GENTYPE_CTX_LABELSIZE: 61,
        GENTYPE_CTX_SIZE: 62,
        GENTYPE_CTX_FACE: 63,
        GENTYPE_CTX_STRAIGHT: 64,
        GENTYPE_CTX_ARROW: 65,
        GENTYPE_CTX_COLOR: 66,
        GENTYPE_CTX_RADIUS: 67,
        GENTYPE_CTX_COORDS: 68,
        GENTYPE_CTX_TEXT: 69,
        GENTYPE_CTX_ANGLERADIUS: 70,
        GENTYPE_CTX_DOTVISIBILITY: 71,
        GENTYPE_CTX_FILLOPACITY: 72,
        GENTYPE_CTX_PLOT: 73,
        GENTYPE_CTX_SCALE: 74,
        GENTYPE_CTX_INTVAL: 75,
        GENTYPE_CTX_POINT1: 76,
        GENTYPE_CTX_POINT2: 77,
        GENTYPE_CTX_LABELSTICKY: 78,
        GENTYPE_CTX_TYPE_I: 79,
        GENTYPE_CTX_HASINNERPOINTS: 80,
        GENTYPE_CTX_SNAPWIDTH: 81,
        GENTYPE_CTX_SNAPTOGRID: 82
    };

    JXG.extendConstants(JXG, constants);

    return constants;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Andreas Walter,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */

/*global JXG: true, define: true, html_sanitize: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 */

/**
 * @fileoverview type.js contains several functions to help deal with javascript's weak types.
 * This file mainly consists of detector functions which verify if a variable is or is not of
 * a specific type and converter functions that convert variables to another type or normalize
 * the type of a variable.
 */

define('utils/type',[
    'jxg', 'base/constants'
], function (JXG, Const) {

    'use strict';

    JXG.extend(JXG, /** @lends JXG */ {
        /**
         * Checks if the given string is an id within the given board.
         * @param {JXG.Board} board
         * @param {String} s
         * @returns {Boolean}
         */
        isId: function (board, s) {
            return (typeof s === 'string') && !!board.objects[s];
        },

        /**
         * Checks if the given string is a name within the given board.
         * @param {JXG.Board} board
         * @param {String} s
         * @returns {Boolean}
         */
        isName: function (board, s) {
            return typeof s === 'string' && !!board.elementsByName[s];
        },

        /**
         * Checks if the given string is a group id within the given board.
         * @param {JXG.Board} board
         * @param {String} s
         * @returns {Boolean}
         */
        isGroup: function (board, s) {
            return typeof s === 'string' && !!board.groups[s];
        },

        /**
         * Checks if the value of a given variable is of type string.
         * @param v A variable of any type.
         * @returns {Boolean} True, if v is of type string.
         */
        isString: function (v) {
            return typeof v === 'string';
        },

        /**
         * Checks if the value of a given variable is of type number.
         * @param v A variable of any type.
         * @returns {Boolean} True, if v is of type number.
         */
        isNumber: function (v) {
            return typeof v === 'number' || Object.prototype.toString.call(v) === '[Object Number]';
        },

        /**
         * Checks if a given variable references a function.
         * @param v A variable of any type.
         * @returns {Boolean} True, if v is a function.
         */
        isFunction: function (v) {
            return typeof v === 'function';
        },

        /**
         * Checks if a given variable references an array.
         * @param v A variable of any type.
         * @returns {Boolean} True, if v is of type array.
         */
        isArray: function (v) {
            var r;

            // use the ES5 isArray() method and if that doesn't exist use a fallback.
            if (Array.isArray) {
                r = Array.isArray(v);
            } else {
                r = (v !== null && typeof v === 'object' && typeof v.splice === 'function' && typeof v.join === 'function');
            }

            return r;
        },

        /**
         * Tests if the input variable is an Object
         * @param v
         */
        isObject: function (v) {
            return typeof v === 'object' && !this.isArray(v);
        },

        /**
         * Checks if a given variable is a reference of a JSXGraph Point element.
         * @param v A variable of any type.
         * @returns {Boolean} True, if v is of type JXG.Point.
         */
        isPoint: function (v) {
            if (v !== null && typeof v === 'object') {
                return (v.elementClass === Const.OBJECT_CLASS_POINT);
            }

            return false;
        },

        /**
         * Checks if a given variable is a reference of a JSXGraph Point element or an array of length at least two or
         * a function returning an array of length two or three.
         * @param {JXG.Board} board
         * @param v A variable of any type.
         * @returns {Boolean} True, if v is of type JXG.Point.
         */
        isPointType: function (board, v) {
            var val, p;

            if (this.isArray(v)) {
                return true;
            }
            if (this.isFunction(v)) {
                val = v();
                if (this.isArray(val) && val.length > 1) {
                    return true;
                }
            }
            p = board.select(v);
            return this.isPoint(p);
        },

        /**
         * Checks if a given variable is a reference of a JSXGraph transformation element or an array
         * of JSXGraph transformation elements.
         * @param v A variable of any type.
         * @returns {Boolean} True, if v is of type JXG.Transformation.
         */
        isTransformationOrArray: function (v) {
            if (v !== null) {
                if (this.isArray(v) && v.length > 0) {
                    return this.isTransformationOrArray(v[0]);
                }
                if (typeof v === 'object') {
                    return (v.type === Const.OBJECT_TYPE_TRANSFORMATION);
                }
            }
            return false;
        },

        /**
         * Checks if a given variable is neither undefined nor null. You should not use this together with global
         * variables!
         * @param v A variable of any type.
         * @param {Boolean} [checkEmptyString=false] If set to true, it is also checked whether v is not equal to ''.
         * @returns {Boolean} True, if v is neither undefined nor null.
         */
        exists: function (v, checkEmptyString) {
            /* eslint-disable eqeqeq */
            var result = !(v == undefined || v === null);
            /* eslint-enable eqeqeq */
            checkEmptyString = checkEmptyString || false;

            if (checkEmptyString) {
                return result && v !== '';
            }
            return result;
        },
        // exists: (function (undef) {
        //     return function (v, checkEmptyString) {
        //         var result = !(v === undef || v === null);

        //         checkEmptyString = checkEmptyString || false;

        //         if (checkEmptyString) {
        //             return result && v !== '';
        //         }
        //         return result;
        //     };
        // }()),

        /**
         * Checks if v is an empty object or empty.
         * @param v {Object|Array}
         * @returns {boolean} True, if v is an empty object or array.
         */
        isEmpty: function(v) {
            return Object.keys(v).length === 0;
        },

        /**
         * Handle default parameters.
         * @param v Given value
         * @param d Default value
         * @returns <tt>d</tt>, if <tt>v</tt> is undefined or null.
         */
        def: function (v, d) {
            if (this.exists(v)) {
                return v;
            }

            return d;
        },

        /**
         * Converts a string containing either <strong>true</strong> or <strong>false</strong> into a boolean value.
         * @param {String} s String containing either <strong>true</strong> or <strong>false</strong>.
         * @returns {Boolean} String typed boolean value converted to boolean.
         */
        str2Bool: function (s) {
            if (!this.exists(s)) {
                return true;
            }

            if (typeof s === 'boolean') {
                return s;
            }

            if (this.isString(s)) {
                return (s.toLowerCase() === 'true');
            }

            return false;
        },

        /**
         * Convert a String, a number or a function into a function. This method is used in Transformation.js
         * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
         * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
         * values is of type string.
         * @param {Array} param An array containing strings, numbers, or functions.
         * @param {Number} n Length of <tt>param</tt>.
         * @returns {Function} A function taking one parameter k which specifies the index of the param element
         * to evaluate.
         */
        createEvalFunction: function (board, param, n) {
            var f = [], i;

            for (i = 0; i < n; i++) {
                f[i] = JXG.createFunction(param[i], board, '', true);
            }

            return function (k) {
                return f[k]();
            };
        },

        /**
         * Convert a String, number or function into a function.
         * @param {String|Number|Function} term A variable of type string, function or number.
         * @param {JXG.Board} board Reference to a JSXGraph board. It is required to resolve dependencies given
         * by a GEONE<sub>X</sub>T string, thus it must be a valid reference only in case one of the param
         * values is of type string.
         * @param {String} variableName Only required if evalGeonext is set to true. Describes the variable name
         * of the variable in a GEONE<sub>X</sub>T string given as term.
         * @param {Boolean} [evalGeonext=true] Set this true, if term should be treated as a GEONE<sub>X</sub>T string.
         * @returns {Function} A function evaluation the value given by term or null if term is not of type string,
         * function or number.
         */
        createFunction: function (term, board, variableName, evalGeonext) {
            var f = null;

            if ((!this.exists(evalGeonext) || evalGeonext) && this.isString(term)) {
                // Convert GEONExT syntax into  JavaScript syntax
                //newTerm = JXG.GeonextParser.geonext2JS(term, board);
                //return new Function(variableName,'return ' + newTerm + ';');

                //term = JXG.GeonextParser.replaceNameById(term, board);
                //term = JXG.GeonextParser.geonext2JS(term, board);
                f = board.jc.snippet(term, true, variableName, true);
            } else if (this.isFunction(term)) {
                f = term;
            } else if (this.isNumber(term)) {
                /** @ignore */
                f = function () {
                    return term;
                };
            } else if (this.isString(term)) {
                // In case of string function like fontsize
                /** @ignore */
                f = function () {
                    return term;
                };
            }

            if (f !== null) {
                f.origin = term;
            }

            return f;
        },

        /**
         *  Test if the parents array contains existing points. If instead parents contains coordinate arrays or
         *  function returning coordinate arrays
         *  free points with these coordinates are created.
         *
         * @param {JXG.Board} board Board object
         * @param {Array} parents Array containing parent elements for a new object. This array may contain
         *    <ul>
         *      <li> {@link JXG.Point} objects
         *      <li> {@link JXG.GeometryElement#name} of {@link JXG.Point} objects
         *      <li> {@link JXG.GeometryElement#id} of {@link JXG.Point} objects
         *      <li> Coordinates of points given as array of numbers of length two or three, e.g. [2, 3].
         *      <li> Coordinates of points given as array of functions of length two or three. Each function returns one coordinate, e.g.
         *           [function(){ return 2; }, function(){ return 3; }]
         *      <li> Function returning coordinates, e.g. function() { return [2, 3]; }
         *    </ul>
         *  In the last three cases a new point will be created.
         * @param {String} attrClass Main attribute class of newly created points, see {@link JXG#copyAttributes}
         * @param {Array} attrArray List of subtype attributes for the newly created points. The list of subtypes is mapped to the list of new points.
         * @returns {Array} List of newly created {@link JXG.Point} elements or false if not all returned elements are points.
         */
        providePoints: function (board, parents, attributes, attrClass, attrArray) {
            var i, j,
                len,
                lenAttr = 0,
                points = [], attr, val;

            if (!this.isArray(parents)) {
                parents = [parents];
            }
            len = parents.length;
            if (this.exists(attrArray)) {
                lenAttr = attrArray.length;
            }
            if (lenAttr === 0) {
                attr = this.copyAttributes(attributes, board.options, attrClass);
            }

            for (i = 0; i < len; ++i) {
                if (lenAttr > 0) {
                    j = Math.min(i, lenAttr - 1);
                    attr = this.copyAttributes(attributes, board.options, attrClass, attrArray[j]);
                }
                if (this.isArray(parents[i]) && parents[i].length > 1) {
                    points.push(board.create('point', parents[i], attr));
                    points[points.length - 1]._is_new = true;
                } else if (this.isFunction(parents[i])) {
                    val = parents[i]();
                    if (this.isArray(val) && (val.length > 1)) {
                        points.push(board.create('point', [parents[i]], attr));
                        points[points.length - 1]._is_new = true;
                    }
                } else {
                    points.push(board.select(parents[i]));
                }

                if (!this.isPoint(points[i])) {
                    return false;
                }
            }

            return points;
        },

        /**
         * Generates a function which calls the function fn in the scope of owner.
         * @param {Function} fn Function to call.
         * @param {Object} owner Scope in which fn is executed.
         * @returns {Function} A function with the same signature as fn.
         */
        bind: function (fn, owner) {
            return function () {
                return fn.apply(owner, arguments);
            };
        },

        /**
         * If <tt>val</tt> is a function, it will be evaluated without giving any parameters, else the input value
         * is just returned.
         * @param val Could be anything. Preferably a number or a function.
         * @returns If <tt>val</tt> is a function, it is evaluated and the result is returned. Otherwise <tt>val</tt> is returned.
         */
        evaluate: function (val) {
            if (this.isFunction(val)) {
                return val();
            }

            return val;
        },

        /**
         * Search an array for a given value.
         * @param {Array} array
         * @param value
         * @param {String} [sub] Use this property if the elements of the array are objects.
         * @returns {Number} The index of the first appearance of the given value, or
         * <tt>-1</tt> if the value was not found.
         */
        indexOf: function (array, value, sub) {
            var i, s = this.exists(sub);

            if (Array.indexOf && !s) {
                return array.indexOf(value);
            }

            for (i = 0; i < array.length; i++) {
                if ((s && array[i][sub] === value) || (!s && array[i] === value)) {
                    return i;
                }
            }

            return -1;
        },

        /**
         * Eliminates duplicate entries in an array consisting of numbers and strings.
         * @param {Array} a An array of numbers and/or strings.
         * @returns {Array} The array with duplicate entries eliminated.
         */
        eliminateDuplicates: function (a) {
            var i,
                len = a.length,
                result = [],
                obj = {};

            for (i = 0; i < len; i++) {
                obj[a[i]] = 0;
            }

            for (i in obj) {
                if (obj.hasOwnProperty(i)) {
                    result.push(i);
                }
            }

            return result;
        },

        /**
         * Swaps to array elements.
         * @param {Array} arr
         * @param {Number} i
         * @param {Number} j
         * @returns {Array} Reference to the given array.
         */
        swap: function (arr, i, j) {
            var tmp;

            tmp = arr[i];
            arr[i] = arr[j];
            arr[j] = tmp;

            return arr;
        },

        /**
         * Generates a copy of an array and removes the duplicate entries. The original
         * Array will be altered.
         * @param {Array} arr
         * @returns {Array}
         */
        uniqueArray: function (arr) {
            var i, j, isArray, ret = [];

            if (arr.length === 0) {
                return [];
            }

            for (i = 0; i < arr.length; i++) {
                isArray = this.isArray(arr[i]);

                if (!this.exists(arr[i])) {
                    arr[i] = '';
                    continue;
                }
                for (j = i + 1; j < arr.length; j++) {
                    if (isArray && JXG.cmpArrays(arr[i], arr[j])) {
                        arr[i] = [];
                    } else if (!isArray && arr[i] === arr[j]) {
                        arr[i] = '';
                    }
                }
            }

            j = 0;

            for (i = 0; i < arr.length; i++) {
                isArray = this.isArray(arr[i]);

                if (!isArray && arr[i] !== '') {
                    ret[j] = arr[i];
                    j++;
                } else if (isArray && arr[i].length !== 0) {
                    ret[j] = (arr[i].slice(0));
                    j++;
                }
            }

            arr = ret;
            return ret;
        },

        /**
         * Checks if an array contains an element equal to <tt>val</tt> but does not check the type!
         * @param {Array} arr
         * @param val
         * @returns {Boolean}
         */
        isInArray: function (arr, val) {
            return JXG.indexOf(arr, val) > -1;
        },

        /**
         * Converts an array of {@link JXG.Coords} objects into a coordinate matrix.
         * @param {Array} coords
         * @param {Boolean} split
         * @returns {Array}
         */
        coordsArrayToMatrix: function (coords, split) {
            var i,
                x = [],
                m = [];

            for (i = 0; i < coords.length; i++) {
                if (split) {
                    x.push(coords[i].usrCoords[1]);
                    m.push(coords[i].usrCoords[2]);
                } else {
                    m.push([coords[i].usrCoords[1], coords[i].usrCoords[2]]);
                }
            }

            if (split) {
                m = [x, m];
            }

            return m;
        },

        /**
         * Compare two arrays.
         * @param {Array} a1
         * @param {Array} a2
         * @returns {Boolean} <tt>true</tt>, if the arrays coefficients are of same type and value.
         */
        cmpArrays: function (a1, a2) {
            var i;

            // trivial cases
            if (a1 === a2) {
                return true;
            }

            if (a1.length !== a2.length) {
                return false;
            }

            for (i = 0; i < a1.length; i++) {
                if (this.isArray(a1[i]) && this.isArray(a2[i])) {
                    if (!this.cmpArrays(a1[i], a2[i])) {
                        return false;
                    }
                } else if (a1[i] !== a2[i]) {
                    return false;
                }
            }

            return true;
        },

        /**
         * Removes an element from the given array
         * @param {Array} ar
         * @param el
         * @returns {Array}
         */
        removeElementFromArray: function (ar, el) {
            var i;

            for (i = 0; i < ar.length; i++) {
                if (ar[i] === el) {
                    ar.splice(i, 1);
                    return ar;
                }
            }

            return ar;
        },

        /**
         * Truncate a number <tt>n</tt> after <tt>p</tt> decimals.
         * @param {Number} n
         * @param {Number} p
         * @returns {Number}
         */
        trunc: function (n, p) {
            p = JXG.def(p, 0);

            return this.toFixed(n, p);
        },

        /**
         * Decimal adjustment of a number.
         * From https://developer.mozilla.org/de/docs/Web/JavaScript/Reference/Global_Objects/Math/round
         *
         * @param    {String}    type    The type of adjustment.
         * @param    {Number}    value    The number.
         * @param    {Number}    exp        The exponent (the 10 logarithm of the adjustment base).
         * @returns    {Number}            The adjusted value.
         *
         * @private
         */
        _decimalAdjust: function (type, value, exp) {
            // If the exp is undefined or zero...
            if (exp === undefined || +exp === 0) {
                return Math[type](value);
            }

            value = +value;
            exp = +exp;
            // If the value is not a number or the exp is not an integer...
            if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0)) {
                return NaN;
            }

            // Shift
            value = value.toString().split('e');
            value = Math[type](+(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp)));

            // Shift back
            value = value.toString().split('e');
            return +(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp));
        },

        /**
         * Round a number to given number of decimal digits.
         *
         * Example: JXG._toFixed(3.14159, -2) gives 3.14
         * @param  {Number} value Number to be rounded
         * @param  {Number} exp   Number of decimal digits given as negative exponent
         * @return {Number}       Rounded number.
         *
         * @private
         */
        _round10: function (value, exp) {
            return this._decimalAdjust('round', value, exp);
        },

        /**
         * "Floor" a number to given number of decimal digits.
         *
         * Example: JXG._toFixed(3.14159, -2) gives 3.14
         * @param  {Number} value Number to be floored
         * @param  {Number} exp   Number of decimal digits given as negative exponent
         * @return {Number}       "Floored" number.
         *
         * @private
         */
        _floor10: function (value, exp) {
            return this._decimalAdjust('floor', value, exp);
        },

        /**
         * "Ceil" a number to given number of decimal digits.
         *
         * Example: JXG._toFixed(3.14159, -2) gives 3.15
         * @param  {Number} value Number to be ceiled
         * @param  {Number} exp   Number of decimal digits given as negative exponent
         * @return {Number}       "Ceiled" number.
         *
         * @private
         */
        _ceil10: function (value, exp) {
            return this._decimalAdjust('ceil', value, exp);
        },

        /**
         * Replacement of the default toFixed() method.
         * It does a correct rounding (independent of the browser) and
         * returns "0.00" for toFixed(-0.000001, 2) instead of "-0.00" which
         * is returned by JavaScript's toFixed()
         *
         * @memberOf JXG
         * @param  {Number} num    Number tp be rounded
         * @param  {Number} digits Decimal digits
         * @return {String}        Rounded number is returned as string
         */
        toFixed: function (num, digits) {
            return this._round10(num, -digits).toFixed(digits);
        },

        /**
         * Truncate a number <tt>val</tt> automatically.
         * @memberOf JXG
         * @param val
         * @returns {Number}
         */
        autoDigits: function (val) {
            var x = Math.abs(val),
                str;

            if (x > 0.1) {
                str = this.toFixed(val, 2);
            } else if (x >= 0.01) {
                str = this.toFixed(val, 4);
            } else if (x >= 0.0001) {
                str = this.toFixed(val, 6);
            } else {
                str = val;
            }
            return str;
        },

        /**
         * Extracts the keys of a given object.
         * @param object The object the keys are to be extracted
         * @param onlyOwn If true, hasOwnProperty() is used to verify that only keys are collected
         * the object owns itself and not some other object in the prototype chain.
         * @returns {Array} All keys of the given object.
         */
        keys: function (object, onlyOwn) {
            var keys = [], property;

            // the caller decides if we use hasOwnProperty
            /*jslint forin:true*/
            for (property in object) {
                if (onlyOwn) {
                    if (object.hasOwnProperty(property)) {
                        keys.push(property);
                    }
                } else {
                    keys.push(property);
                }
            }
            /*jslint forin:false*/

            return keys;
        },

        /**
         * This outputs an object with a base class reference to the given object. This is useful if
         * you need a copy of an e.g. attributes object and want to overwrite some of the attributes
         * without changing the original object.
         * @param {Object} obj Object to be embedded.
         * @returns {Object} An object with a base class reference to <tt>obj</tt>.
         */
        clone: function (obj) {
            var cObj = {};

            cObj.prototype = obj;

            return cObj;
        },

        /**
         * Embeds an existing object into another one just like {@link #clone} and copies the contents of the second object
         * to the new one. Warning: The copied properties of obj2 are just flat copies.
         * @param {Object} obj Object to be copied.
         * @param {Object} obj2 Object with data that is to be copied to the new one as well.
         * @returns {Object} Copy of given object including some new/overwritten data from obj2.
         */
        cloneAndCopy: function (obj, obj2) {
            var r,
                cObj = function () { return undefined; };

            cObj.prototype = obj;

            // no hasOwnProperty on purpose
            /*jslint forin:true*/
            /*jshint forin:true*/

            for (r in obj2) {
                cObj[r] = obj2[r];
            }

            /*jslint forin:false*/
            /*jshint forin:false*/

            return cObj;
        },

        /**
         * Recursively merges obj2 into obj1. Contrary to {@link JXG#deepCopy} this won't create a new object
         * but instead will overwrite obj1.
         * @param {Object} obj1
         * @param {Object} obj2
         * @returns {Object}
         */
        merge: function (obj1, obj2) {
            var i, j;

            for (i in obj2) {
                if (obj2.hasOwnProperty(i)) {
                    if (this.isArray(obj2[i])) {
                        if (!obj1[i]) {
                            obj1[i] = [];
                        }

                        for (j = 0; j < obj2[i].length; j++) {
                            if (typeof obj2[i][j] === 'object') {
                                obj1[i][j] = this.merge(obj1[i][j], obj2[i][j]);
                            } else {
                                obj1[i][j] = obj2[i][j];
                            }
                        }
                    } else if (typeof obj2[i] === 'object') {
                        if (!obj1[i]) {
                            obj1[i] = {};
                        }

                        obj1[i] = this.merge(obj1[i], obj2[i]);
                    } else {
                        obj1[i] = obj2[i];
                    }
                }
            }

            return obj1;
        },

        /**
         * Creates a deep copy of an existing object, i.e. arrays or sub-objects are copied component resp.
         * element-wise instead of just copying the reference. If a second object is supplied, the two objects
         * are merged into one object. The properties of the second object have priority.
         * @param {Object} obj This object will be copied.
         * @param {Object} obj2 This object will merged into the newly created object
         * @param {Boolean} [toLower=false] If true the keys are convert to lower case. This is needed for visProp, see JXG#copyAttributes
         * @returns {Object} copy of obj or merge of obj and obj2.
         */
        deepCopy: function (obj, obj2, toLower) {
            var c, i, prop, i2;

            toLower = toLower || false;

            if (typeof obj !== 'object' || obj === null) {
                return obj;
            }

            // missing hasOwnProperty is on purpose in this function
            if (this.isArray(obj)) {
                c = [];
                for (i = 0; i < obj.length; i++) {
                    prop = obj[i];
                    if (typeof prop === 'object') {
                        // We certainly do not want to recurse into a JSXGraph object.
                        // This would for sure result in an infinite recursion.
                        // As alternative we copy the id of the object.
                        if (this.exists(prop.board)) {
                            c[i] = prop.id;
                        } else {
                            c[i] = this.deepCopy(prop);
                        }
                    } else {
                        c[i] = prop;
                    }
                }
            } else {
                c = {};
                for (i in obj) {
                    if (obj.hasOwnProperty(i)) {
                        i2 = toLower ? i.toLowerCase() : i;
                        prop = obj[i];
                        if (prop !== null && typeof prop === 'object') {
                            if (this.exists(prop.board)) {
                                c[i2] = prop.id;
                            } else {
                                c[i2] = this.deepCopy(prop);
                            }
                        } else {
                            c[i2] = prop;
                        }
                    }
                }

                for (i in obj2) {
                    if (obj2.hasOwnProperty(i)) {
                        i2 = toLower ? i.toLowerCase() : i;

                        prop = obj2[i];
                        if (typeof prop === 'object') {
                            if (this.isArray(prop) || !this.exists(c[i2])) {
                                c[i2] = this.deepCopy(prop);
                            } else {
                                c[i2] = this.deepCopy(c[i2], prop, toLower);
                            }
                        } else {
                            c[i2] = prop;
                        }
                    }
                }
            }

            return c;
        },

        /**
         * Generates an attributes object that is filled with default values from the Options object
         * and overwritten by the user specified attributes.
         * @param {Object} attributes user specified attributes
         * @param {Object} options defaults options
         * @param {String} s variable number of strings, e.g. 'slider', subtype 'point1'.
         * @returns {Object} The resulting attributes object
         */
        copyAttributes: function (attributes, options, s) {
            var a, i, len, o, isAvail,
                primitives = {
                    'circle': 1,
                    'curve': 1,
                    'image': 1,
                    'line': 1,
                    'point': 1,
                    'polygon': 1,
                    'text': 1,
                    'ticks': 1,
                    'integral': 1
                };

            len = arguments.length;
            if (len < 3 || primitives[s]) {
                // default options from Options.elements
                a = JXG.deepCopy(options.elements, null, true);
            } else {
                a = {};
            }

            // Only the layer of the main element is set.
            if (len < 4 && this.exists(s) && this.exists(options.layer[s])) {
                a.layer = options.layer[s];
            }

            // default options from specific elements
            o = options;
            isAvail = true;
            for (i = 2; i < len; i++) {
                if (this.exists(o[arguments[i]])) {
                    o = o[arguments[i]];
                } else {
                    isAvail = false;
                    break;
                }
            }
            if (isAvail) {
                a = JXG.deepCopy(a, o, true);
            }

            // options from attributes
            o = attributes;
            isAvail = true;
            for (i = 3; i < len; i++) {
                if (this.exists(o[arguments[i]])) {
                    o = o[arguments[i]];
                } else {
                    isAvail = false;
                    break;
                }
            }
            if (isAvail) {
                this.extend(a, o, null, true);
            }

            if (arguments[2] === 'board') {
                // For board attributes we are done now.
                return a;
            }

            // Special treatment of labels
            o = options;
            isAvail = true;
            for (i = 2; i < len; i++) {
                if (this.exists(o[arguments[i]])) {
                    o = o[arguments[i]];
                } else {
                    isAvail = false;
                    break;
                }
            }
            if (isAvail && this.exists(o.label)) {
                a.label = JXG.deepCopy(o.label, a.label);
            }
            a.label = JXG.deepCopy(options.label, a.label);

            return a;
        },

        /**
         * Copy all prototype methods from object "superObject" to object
         * "subObject". The constructor of superObject will be available
         * in subObject as subObject.constructor[constructorName].
         * @param {Object} subObj A JavaScript object which receives new methods.
         * @param {Object} superObj A JavaScript object which lends its prototype methods to subObject
         * @returns {String} constructorName Under this name the constructor of superObj will be available
         * in subObject.
         * @private
         */
        copyPrototypeMethods: function (subObject, superObject, constructorName) {
            var key;

            subObject.prototype[constructorName] = superObject.prototype.constructor;
            for (key in superObject.prototype) {
                if (superObject.prototype.hasOwnProperty(key)) {
                    subObject.prototype[key] = superObject.prototype[key];
                }
            }
        },

        /**
         * Converts a JavaScript object into a JSON string.
         * @param {Object} obj A JavaScript object, functions will be ignored.
         * @param {Boolean} [noquote=false] No quotes around the name of a property.
         * @returns {String} The given object stored in a JSON string.
         */
        toJSON: function (obj, noquote) {
            var list, prop, i, s, val;

            noquote = JXG.def(noquote, false);

            // check for native JSON support:
            if (typeof JSON && JSON.stringify && !noquote) {
                try {
                    s = JSON.stringify(obj);
                    return s;
                } catch (e) {
                    // if something goes wrong, e.g. if obj contains functions we won't return
                    // and use our own implementation as a fallback
                }
            }

            switch (typeof obj) {
                case 'object':
                    if (obj) {
                        list = [];

                        if (this.isArray(obj)) {
                            for (i = 0; i < obj.length; i++) {
                                list.push(JXG.toJSON(obj[i], noquote));
                            }

                            return '[' + list.join(',') + ']';
                        }

                        for (prop in obj) {
                            if (obj.hasOwnProperty(prop)) {
                                try {
                                    val = JXG.toJSON(obj[prop], noquote);
                                } catch (e2) {
                                    val = '';
                                }

                                if (noquote) {
                                    list.push(prop + ':' + val);
                                } else {
                                    list.push('"' + prop + '":' + val);
                                }
                            }
                        }

                        return '{' + list.join(',') + '} ';
                    }
                    return 'null';
                case 'string':
                    return '\'' + obj.replace(/(["'])/g, '\\$1') + '\'';
                case 'number':
                case 'boolean':
                    return obj.toString();
            }

            return '0';
        },

        /**
         * Resets visPropOld.
         * @param {JXG.GeometryElement} el
         * @returns {GeometryElement}
         */
        clearVisPropOld: function (el) {
            el.visPropOld = {
                cssclass: '',
                cssdefaultstyle: '',
                cssstyle: '',
                fillcolor: '',
                fillopacity: '',
                firstarrow: false,
                fontsize: -1,
                lastarrow: false,
                left: -100000,
                linecap: '',
                shadow: false,
                strokecolor: '',
                strokeopacity: '',
                strokewidth: '',
                tabindex: -100000,
                transitionduration: 0,
                top: -100000,
                visible: null
            };

            return el;
        },

        /**
         * Checks if an object contains a key, whose value equals to val.
         * @param {Object} obj
         * @param val
         * @returns {Boolean}
         */
        isInObject: function (obj, val) {
            var el;

            for (el in obj) {
                if (obj.hasOwnProperty(el)) {
                    if (obj[el] === val) {
                        return true;
                    }
                }
            }

            return false;
        },

        /**
         * Replaces all occurences of &amp; by &amp;amp;, &gt; by &amp;gt;, and &lt; by &amp;lt;.
         * @param {String} str
         * @returns {String}
         */
        escapeHTML: function (str) {
            return str.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
        },

        /**
         * Eliminates all substrings enclosed by &lt; and &gt; and replaces all occurences of
         * &amp;amp; by &amp;, &amp;gt; by &gt;, and &amp;lt; by &lt;.
         * @param {String} str
         * @returns {String}
         */
        unescapeHTML: function (str) {
            // This regex is NOT insecure. We are replacing everything found with ''
            /*jslint regexp:true*/
            return str.replace(/<\/?[^>]+>/gi, '').replace(/&amp;/g, '&').replace(/&lt;/g, '<').replace(/&gt;/g, '>');
        },

        /**
         * Makes a string lower case except for the first character which will be upper case.
         * @param {String} str Arbitrary string
         * @returns {String} The capitalized string.
         */
        capitalize: function (str) {
            return str.charAt(0).toUpperCase() + str.substring(1).toLowerCase();
        },

        /**
         * Make numbers given as strings nicer by removing all unnecessary leading and trailing zeroes.
         * @param {String} str
         * @returns {String}
         */
        trimNumber: function (str) {
            str = str.replace(/^0+/, '');
            str = str.replace(/0+$/, '');

            if (str[str.length - 1] === '.' || str[str.length - 1] === ',') {
                str = str.slice(0, -1);
            }

            if (str[0] === '.' || str[0] === ',') {
                str = '0' + str;
            }

            return str;
        },

        /**
         * Filter an array of elements.
         * @param {Array} list
         * @param {Object|function} filter
         * @returns {Array}
         */
        filterElements: function (list, filter) {
            var i, f, item, flower, value, visPropValue, pass,
                l = list.length,
                result = [];

            if (typeof filter !== 'function' && typeof filter !== 'object') {
                return result;
            }

            for (i = 0; i < l; i++) {
                pass = true;
                item = list[i];

                if (typeof filter === 'object') {
                    for (f in filter) {
                        if (filter.hasOwnProperty(f)) {
                            flower = f.toLowerCase();

                            if (typeof item[f] === 'function') {
                                value = item[f]();
                            } else {
                                value = item[f];
                            }

                            if (item.visProp && typeof item.visProp[flower] === 'function') {
                                visPropValue = item.visProp[flower]();
                            } else {
                                visPropValue = item.visProp && item.visProp[flower];
                            }

                            if (typeof filter[f] === 'function') {
                                pass = filter[f](value) || filter[f](visPropValue);
                            } else {
                                pass = (value === filter[f] || visPropValue === filter[f]);
                            }

                            if (!pass) {
                                break;
                            }
                        }
                    }
                } else if (typeof filter === 'function') {
                    pass = filter(item);
                }

                if (pass) {
                    result.push(item);
                }
            }

            return result;
        },

        /**
         * Remove all leading and trailing whitespaces from a given string.
         * @param {String} str
         * @returns {String}
         */
        trim: function (str) {
            // str = str.replace(/^\s+/, '');
            // str = str.replace(/\s+$/, '');
            //
            // return str;
            return str.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
        },

        /**
         * Convert HTML tags to entities or use html_sanitize if the google caja html sanitizer is available.
         * @param {String} str
         * @param {Boolean} caja
         * @returns {String} Sanitized string
         */
        sanitizeHTML: function (str, caja) {
            if (typeof html_sanitize === 'function' && caja) {
                return html_sanitize(str, function () { return undefined; }, function (id) { return id; });
            }

            if (str) {
                str = str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
            }

            return str;
        },

        /**
         * If <tt>s</tt> is a slider, it returns the sliders value, otherwise it just returns the given value.
         * @param {*} s
         * @returns {*} s.Value() if s is an element of type slider, s otherwise
         */
        evalSlider: function (s) {
            if (s && s.type === Const.OBJECT_TYPE_GLIDER && typeof s.Value === 'function') {
                return s.Value();
            }

            return s;
        }
    });

    return JXG;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Andreas Walter,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */

/*global JXG: true, define: true, window: true, document: true, navigator: true, module: true, global: true, self: true, require: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/type
 */

/**
 * @fileoverview The functions in this file help with the detection of the environment JSXGraph runs in. We can distinguish
 * between node.js, windows 8 app and browser, what rendering techniques are supported and (most of the time) if the device
 * the browser runs on is a tablet/cell or a desktop computer.
 */

define('utils/env',['jxg', 'utils/type'], function (JXG, Type) {

    'use strict';

    JXG.extendConstants(JXG, /** @lends JXG */{
        /**
         * Determines the property that stores the relevant information in the event object.
         * @type String
         * @default 'touches'
         * @private
         */
        touchProperty: 'touches',
    });

    JXG.extend(JXG, /** @lends JXG */ {
        /**
         * Determines whether evt is a touch event.
         * @param evt {Event}
         * @returns {Boolean}
         */
        isTouchEvent: function (evt) {
            return JXG.exists(evt[JXG.touchProperty]);
        },

        /**
         * Determines whether evt is a pointer event.
         * @param evt {Event}
         * @returns {Boolean}
         */
        isPointerEvent: function (evt) {
            return JXG.exists(evt.pointerId);
        },

        /**
         * Determines whether evt is neither a touch event nor a pointer event.
         * @param evt {Event}
         * @returns {Boolean}
         */
        isMouseEvent: function (evt) {
            return !JXG.isTouchEvent(evt)&&!JXG.isPointerEvent(evt);
        },

        /**
         * Determines the number of touch points in a touch event.
         * For other events, -1 is returned.
         * @param evt {Event}
         * @returns {Number}
         */
        getNumberOfTouchPoints: function (evt) {
            var n = -1;

            if (JXG.isTouchEvent(evt)) {
                n = evt[JXG.touchProperty].length;
            }

            return n;
        },

        /**
         * Checks whether an mouse, pointer or touch event evt is the first event of a multitouch event.
         * Attention: When two or more pointer device types are being used concurrently,
         *            it is only checked whether the passed event is the first one of its type!
         * @param evt {Event}
         * @returns {boolean}
         */
        isFirstTouch: function (evt) {
            var touchPoints = JXG.getNumberOfTouchPoints(evt);

            if (JXG.isPointerEvent(evt)) {
                return evt.isPrimary;
            }

            return (touchPoints === 1);
        },

        /**
         * A document/window environment is available.
         * @type Boolean
         * @default false
         */
        isBrowser: typeof window === 'object' && typeof document === 'object',

        /**
         * Features of ECMAScript 6+ are available.
         * @type Boolean
         * @default false
         */
        supportsES6: function () {
            var testMap;
            /* jshint ignore:start */
            try {
                // This would kill the old uglifyjs: testMap = (a = 0) => a;
                new Function('(a = 0) => a');
                return true;
            } catch (err) {
                return false;
            }
            /* jshint ignore:end */
        },

        /**
         * Detect browser support for VML.
         * @returns {Boolean} True, if the browser supports VML.
         */
        supportsVML: function () {
            // From stackoverflow.com
            return this.isBrowser && !!document.namespaces;
        },

        /**
         * Detect browser support for SVG.
         * @returns {Boolean} True, if the browser supports SVG.
         */
        supportsSVG: function () {
            return this.isBrowser && document.implementation.hasFeature('http://www.w3.org/TR/SVG11/feature#BasicStructure', '1.1');
        },

        /**
         * Detect browser support for Canvas.
         * @returns {Boolean} True, if the browser supports HTML canvas.
         */
        supportsCanvas: function () {
            var c, hasCanvas = false;

            if (this.isNode()) {
                try {
                    c = (typeof module === 'object' ? module.require('canvas') : require('canvas'));
                    hasCanvas = !!c;
                } catch (err) {
                }
            }

            return hasCanvas || (this.isBrowser && !!document.createElement('canvas').getContext);
        },

        /**
         * True, if run inside a node.js environment.
         * @returns {Boolean}
         */
        isNode: function () {
            // this is not a 100% sure but should be valid in most cases

            // we are not inside a browser
            return !this.isBrowser && (
                // there is a module object (plain node, no requirejs)
                (typeof module === 'object' && !!module.exports) ||
                // there is a global object and requirejs is loaded
                (typeof global === 'object' && global.requirejsVars && !global.requirejsVars.isBrowser)
            );
        },

        /**
         * True if run inside a webworker environment.
         * @returns {Boolean}
         */
        isWebWorker: function () {
            return !this.isBrowser && (typeof self === 'object' && typeof self.postMessage === 'function');
        },

        /**
         * Checks if the environments supports the W3C Pointer Events API {@link http://www.w3.org/Submission/pointer-events/}
         * @returns {Boolean}
         */
        supportsPointerEvents: function () {
            return !!(this.isBrowser && window.navigator &&
                (window.PointerEvent ||             // Chrome/Edge/IE11+
                    window.navigator.pointerEnabled || // IE11+
                    window.navigator.msPointerEnabled  // IE10-
                )
            );
        },

        /**
         * Determine if the current browser supports touch events
         * @returns {Boolean} True, if the browser supports touch events.
         */
        isTouchDevice: function () {
            return this.isBrowser && window.ontouchstart !== undefined;
        },

        /**
         * Detects if the user is using an Android powered device.
         * @returns {Boolean}
         */
        isAndroid: function () {
            return Type.exists(navigator) && navigator.userAgent.toLowerCase().indexOf('android') > -1;
        },

        /**
         * Detects if the user is using the default Webkit browser on an Android powered device.
         * @returns {Boolean}
         */
        isWebkitAndroid: function () {
            return this.isAndroid() && navigator.userAgent.indexOf(' AppleWebKit/') > -1;
        },

        /**
         * Detects if the user is using a Apple iPad / iPhone.
         * @returns {Boolean}
         */
        isApple: function () {
            return Type.exists(navigator) && (navigator.userAgent.indexOf('iPad') > -1 || navigator.userAgent.indexOf('iPhone') > -1);
        },

        /**
         * Detects if the user is using Safari on an Apple device.
         * @returns {Boolean}
         */
        isWebkitApple: function () {
            return this.isApple() && (navigator.userAgent.search(/Mobile\/[0-9A-Za-z.]*Safari/) > -1);
        },

        /**
         * Returns true if the run inside a Windows 8 "Metro" App.
         * @returns {Boolean}
         */
        isMetroApp: function () {
            return typeof window === 'object' && window.clientInformation && window.clientInformation.appVersion && window.clientInformation.appVersion.indexOf('MSAppHost') > -1;
        },

        /**
         * Detects if the user is using a Mozilla browser
         * @returns {Boolean}
         */
        isMozilla: function () {
            return Type.exists(navigator) &&
                navigator.userAgent.toLowerCase().indexOf('mozilla') > -1 &&
                navigator.userAgent.toLowerCase().indexOf('apple') === -1;
        },

        /**
         * Detects if the user is using a firefoxOS powered device.
         * @returns {Boolean}
         */
        isFirefoxOS: function () {
            return Type.exists(navigator) &&
                navigator.userAgent.toLowerCase().indexOf('android') === -1 &&
                navigator.userAgent.toLowerCase().indexOf('apple') === -1 &&
                navigator.userAgent.toLowerCase().indexOf('mobile') > -1 &&
                navigator.userAgent.toLowerCase().indexOf('mozilla') > -1;
        },

        /**
         * Internet Explorer version. Works only for IE > 4.
         * @type Number
         */
        ieVersion: (function () {
            var div, all,
                v = 3;

            if (typeof document !== 'object') {
                return 0;
            }

            div = document.createElement('div');
            all = div.getElementsByTagName('i');

            do {
                div.innerHTML = '<!--[if gt IE ' + (++v) + ']><' + 'i><' + '/i><![endif]-->';
            } while (all[0]);

            return v > 4 ? v : undefined;

        }()),

        /**
         * Reads the width and height of an HTML element.
         * @param {String} elementId The HTML id of an HTML DOM node.
         * @returns {Object} An object with the two properties width and height.
         */
        getDimensions: function (elementId, doc) {
            var element, display, els, originalVisibility, originalPosition,
                originalDisplay, originalWidth, originalHeight, style,
                pixelDimRegExp = /\d+(\.\d*)?px/;

            if (!this.isBrowser || elementId === null) {
                return {
                    width: 500,
                    height: 500
                };
            }

            doc = doc || document;
            // Borrowed from prototype.js
            element = doc.getElementById(elementId);
            if (!Type.exists(element)) {
                throw new Error('\nJSXGraph: HTML container element \'' + elementId + '\' not found.');
            }

            display = element.style.display;

            // Work around a bug in Safari
            if (display !== 'none' && display !== null) {
                if (element.clientWidth > 0 && element.clientHeight > 0) {
                    return {width: element.clientWidth, height: element.clientHeight};
                }

                // a parent might be set to display:none; try reading them from styles
                style = window.getComputedStyle ? window.getComputedStyle(element) : element.style;
                return {
                    width: pixelDimRegExp.test(style.width) ? parseFloat(style.width) : 0,
                    height: pixelDimRegExp.test(style.height) ? parseFloat(style.height) : 0
                };
            }

            // All *Width and *Height properties give 0 on elements with display set to none,
            // hence we show the element temporarily
            els = element.style;

            // save style
            originalVisibility = els.visibility;
            originalPosition = els.position;
            originalDisplay = els.display;

            // show element
            els.visibility = 'hidden';
            els.position = 'absolute';
            els.display = 'block';

            // read the dimension
            originalWidth = element.clientWidth;
            originalHeight = element.clientHeight;

            // restore original css values
            els.display = originalDisplay;
            els.position = originalPosition;
            els.visibility = originalVisibility;

            return {
                width: originalWidth,
                height: originalHeight
            };
        },

        /**
         * Adds an event listener to a DOM element.
         * @param {Object} obj Reference to a DOM node.
         * @param {String} type The event to catch, without leading 'on', e.g. 'mousemove' instead of 'onmousemove'.
         * @param {Function} fn The function to call when the event is triggered.
         * @param {Object} owner The scope in which the event trigger is called.
         */
        addEvent: function (obj, type, fn, owner) {
            var el = function () {
                return fn.apply(owner, arguments);
            };

            el.origin = fn;
            owner['x_internal' + type] = owner['x_internal' + type] || [];
            owner['x_internal' + type].push(el);

            // Non-IE browser
            if (Type.exists(obj) && Type.exists(obj.addEventListener)) {
                obj.addEventListener(type, el, false);
            }

            // IE
            if (Type.exists(obj) && Type.exists(obj.attachEvent)) {
                obj.attachEvent('on' + type, el);
            }
        },

        /**
         * Removes an event listener from a DOM element.
         * @param {Object} obj Reference to a DOM node.
         * @param {String} type The event to catch, without leading 'on', e.g. 'mousemove' instead of 'onmousemove'.
         * @param {Function} fn The function to call when the event is triggered.
         * @param {Object} owner The scope in which the event trigger is called.
         */
        removeEvent: function (obj, type, fn, owner) {
            var i;

            if (!Type.exists(owner)) {
                JXG.debug('no such owner');
                return;
            }

            if (!Type.exists(owner['x_internal' + type])) {
                JXG.debug('no such type: ' + type);
                return;
            }

            if (!Type.isArray(owner['x_internal' + type])) {
                JXG.debug('owner[x_internal + ' + type + '] is not an array');
                return;
            }

            i = Type.indexOf(owner['x_internal' + type], fn, 'origin');

            if (i === -1) {
                JXG.debug('removeEvent: no such event function in internal list: ' + fn);
                return;
            }

            try {
                // Non-IE browser
                if (Type.exists(obj) && Type.exists(obj.removeEventListener)) {
                    obj.removeEventListener(type, owner['x_internal' + type][i], false);
                }

                // IE
                if (Type.exists(obj) && Type.exists(obj.detachEvent)) {
                    obj.detachEvent('on' + type, owner['x_internal' + type][i]);
                }
            } catch (e) {
                JXG.debug('event not registered in browser: (' + type + ' -- ' + fn + ')');
            }

            owner['x_internal' + type].splice(i, 1);
        },

        /**
         * Removes all events of the given type from a given DOM node; Use with caution and do not use it on a container div
         * of a {@link JXG.Board} because this might corrupt the event handling system.
         * @param {Object} obj Reference to a DOM node.
         * @param {String} type The event to catch, without leading 'on', e.g. 'mousemove' instead of 'onmousemove'.
         * @param {Object} owner The scope in which the event trigger is called.
         */
        removeAllEvents: function (obj, type, owner) {
            var i, len;
            if (owner['x_internal' + type]) {
                len = owner['x_internal' + type].length;

                for (i = len - 1; i >= 0; i--) {
                    JXG.removeEvent(obj, type, owner['x_internal' + type][i].origin, owner);
                }

                if (owner['x_internal' + type].length > 0) {
                    JXG.debug('removeAllEvents: Not all events could be removed.');
                }
            }
        },

        /**
         * Cross browser mouse / touch coordinates retrieval relative to the board's top left corner.
         * @param {Object} [e] The browsers event object. If omitted, <tt>window.event</tt> will be used.
         * @param {Number} [index] If <tt>e</tt> is a touch event, this provides the index of the touch coordinates, i.e. it determines which finger.
         * @param {Object} [doc] The document object.
         * @returns {Array} Contains the position as x,y-coordinates in the first resp. second component.
         */
        getPosition: function (e, index, doc) {
            var i, len, evtTouches,
                posx = 0,
                posy = 0;

            if (!e) {
                e = window.event;
            }

            doc = doc || document;
            evtTouches = e[JXG.touchProperty];

            // touchend events have their position in "changedTouches"
            if (Type.exists(evtTouches) && evtTouches.length === 0) {
                evtTouches = e.changedTouches;
            }

            if (Type.exists(index) && Type.exists(evtTouches)) {
                if (index === -1) {
                    len = evtTouches.length;

                    for (i = 0; i < len; i++) {
                        if (evtTouches[i]) {
                            e = evtTouches[i];
                            break;
                        }
                    }

                } else {
                    e = evtTouches[index];
                }
            }

            // Scrolling is ignored.
            // e.clientX is supported since IE6
            if (e.clientX) {
                posx = e.clientX;
                posy = e.clientY;
            }

            return [posx, posy];
        },

        /**
         * Calculates recursively the offset of the DOM element in which the board is stored.
         * @param {Object} obj A DOM element
         * @returns {Array} An array with the elements left and top offset.
         */
        getOffset: function (obj) {
            var cPos,
                o = obj,
                o2 = obj,
                l = o.offsetLeft - o.scrollLeft,
                t = o.offsetTop - o.scrollTop;

            cPos = this.getCSSTransform([l, t], o);
            l = cPos[0];
            t = cPos[1];

            /*
             * In Mozilla and Webkit: offsetParent seems to jump at least to the next iframe,
             * if not to the body. In IE and if we are in an position:absolute environment
             * offsetParent walks up the DOM hierarchy.
             * In order to walk up the DOM hierarchy also in Mozilla and Webkit
             * we need the parentNode steps.
             */
            o = o.offsetParent;
            while (o) {
                l += o.offsetLeft;
                t += o.offsetTop;

                if (o.offsetParent) {
                    l += o.clientLeft - o.scrollLeft;
                    t += o.clientTop - o.scrollTop;
                }

                cPos = this.getCSSTransform([l, t], o);
                l = cPos[0];
                t = cPos[1];

                o2 = o2.parentNode;

                while (o2 !== o) {
                    l += o2.clientLeft - o2.scrollLeft;
                    t += o2.clientTop - o2.scrollTop;

                    cPos = this.getCSSTransform([l, t], o2);
                    l = cPos[0];
                    t = cPos[1];

                    o2 = o2.parentNode;
                }
                o = o.offsetParent;
            }

            return [l, t];
        },

        /**
         * Access CSS style sheets.
         * @param {Object} obj A DOM element
         * @param {String} stylename The CSS property to read.
         * @returns The value of the CSS property and <tt>undefined</tt> if it is not set.
         */
        getStyle: function (obj, stylename) {
            var r,
                doc = obj.ownerDocument;

            // Non-IE
            if (doc.defaultView && doc.defaultView.getComputedStyle) {
                r = doc.defaultView.getComputedStyle(obj, null).getPropertyValue(stylename);
                // IE
            } else if (obj.currentStyle && JXG.ieVersion >= 9) {
                r = obj.currentStyle[stylename];
            } else {
                if (obj.style) {
                    // make stylename lower camelcase
                    stylename = stylename.replace(/-([a-z]|[0-9])/ig, function (all, letter) {
                        return letter.toUpperCase();
                    });
                    r = obj.style[stylename];
                }
            }

            return r;
        },

        /**
         * Reads css style sheets of a given element. This method is a getStyle wrapper and
         * defaults the read value to <tt>0</tt> if it can't be parsed as an integer value.
         * @param {DOMElement} el
         * @param {string} css
         * @returns {number}
         */
        getProp: function (el, css) {
            var n = parseInt(this.getStyle(el, css), 10);
            return isNaN(n) ? 0 : n;
        },

        /**
         * Correct position of upper left corner in case of
         * a CSS transformation. Here, only translations are
         * extracted. All scaling transformations are corrected
         * in {@link JXG.Board#getMousePosition}.
         * @param {Array} cPos Previously determined position
         * @param {Object} obj A DOM element
         * @returns {Array} The corrected position.
         */
        getCSSTransform: function (cPos, obj) {
            var i, j, str, arrStr, start, len, len2, arr,
                t = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'oTransform'];

            // Take the first transformation matrix
            len = t.length;

            for (i = 0, str = ''; i < len; i++) {
                if (Type.exists(obj.style[t[i]])) {
                    str = obj.style[t[i]];
                    break;
                }
            }

            /**
             * Extract the coordinates and apply the transformation
             * to cPos
             */
            if (str !== '') {
                start = str.indexOf('(');

                if (start > 0) {
                    len = str.length;
                    arrStr = str.substring(start + 1, len - 1);
                    arr = arrStr.split(',');

                    for (j = 0, len2 = arr.length; j < len2; j++) {
                        arr[j] = parseFloat(arr[j]);
                    }

                    if (str.indexOf('matrix') === 0) {
                        cPos[0] += arr[4];
                        cPos[1] += arr[5];
                    } else if (str.indexOf('translateX') === 0) {
                        cPos[0] += arr[0];
                    } else if (str.indexOf('translateY') === 0) {
                        cPos[1] += arr[0];
                    } else if (str.indexOf('translate') === 0) {
                        cPos[0] += arr[0];
                        cPos[1] += arr[1];
                    }
                }
            }

            // Zoom is used by reveal.js
            if (Type.exists(obj.style.zoom)) {
                str = obj.style.zoom;
                if (str !== '') {
                    cPos[0] *= parseFloat(str);
                    cPos[1] *= parseFloat(str);
                }
            }

            return cPos;
        },

        /**
         * Scaling CSS transformations applied to the div element containing the JSXGraph constructions
         * are determined. In IE prior to 9, 'rotate', 'skew', 'skewX', 'skewY' are not supported.
         * @returns {Array} 3x3 transformation matrix without translation part. See {@link JXG.Board#updateCSSTransforms}.
         */
        getCSSTransformMatrix: function (obj) {
            var i, j, str, arrstr, start, len, len2, arr,
                st,
                doc = obj.ownerDocument,
                t = ['transform', 'webkitTransform', 'MozTransform', 'msTransform', 'oTransform'],
                mat = [[1, 0, 0],
                    [0, 1, 0],
                    [0, 0, 1]];

            // This should work on all browsers except IE 6-8
            if (doc.defaultView && doc.defaultView.getComputedStyle) {
                st = doc.defaultView.getComputedStyle(obj, null);
                str = st.getPropertyValue('-webkit-transform') ||
                    st.getPropertyValue('-moz-transform') ||
                    st.getPropertyValue('-ms-transform') ||
                    st.getPropertyValue('-o-transform') ||
                    st.getPropertyValue('transform');
            } else {
                // Take the first transformation matrix
                len = t.length;
                for (i = 0, str = ''; i < len; i++) {
                    if (Type.exists(obj.style[t[i]])) {
                        str = obj.style[t[i]];
                        break;
                    }
                }
            }

            if (str !== '') {
                start = str.indexOf('(');

                if (start > 0) {
                    len = str.length;
                    arrstr = str.substring(start + 1, len - 1);
                    arr = arrstr.split(',');

                    for (j = 0, len2 = arr.length; j < len2; j++) {
                        arr[j] = parseFloat(arr[j]);
                    }

                    if (str.indexOf('matrix') === 0) {
                        mat = [[1, 0, 0],
                            [0, arr[0], arr[1]],
                            [0, arr[2], arr[3]]];
                    } else if (str.indexOf('scaleX') === 0) {
                        mat[1][1] = arr[0];
                    } else if (str.indexOf('scaleY') === 0) {
                        mat[2][2] = arr[0];
                    } else if (str.indexOf('scale') === 0) {
                        mat[1][1] = arr[0];
                        mat[2][2] = arr[1];
                    }
                }
            }

            // CSS style zoom is used by reveal.js
            // Recursively search for zoom style entries.
            // This is necessary for reveal.js on webkit.
            // It fails if the user does zooming
            if (Type.exists(obj.style.zoom)) {
                str = obj.style.zoom;
                if (str !== '') {
                    mat[1][1] *= parseFloat(str);
                    mat[2][2] *= parseFloat(str);
                }
            }

            return mat;
        },

        /**
         * Process data in timed chunks. Data which takes long to process, either because it is such
         * a huge amount of data or the processing takes some time, causes warnings in browsers about
         * irresponsive scripts. To prevent these warnings, the processing is split into smaller pieces
         * called chunks which will be processed in serial order.
         * Copyright 2009 Nicholas C. Zakas. All rights reserved. MIT Licensed
         * @param {Array} items to do
         * @param {Function} process Function that is applied for every array item
         * @param {Object} context The scope of function process
         * @param {Function} callback This function is called after the last array element has been processed.
         */
        timedChunk: function (items, process, context, callback) {
            //create a clone of the original
            var todo = items.concat(),
                timerFun = function () {
                    var start = +new Date();

                    do {
                        process.call(context, todo.shift());
                    } while (todo.length > 0 && (+new Date() - start < 300));

                    if (todo.length > 0) {
                        window.setTimeout(timerFun, 1);
                    } else {
                        callback(items);
                    }
                };

            window.setTimeout(timerFun, 1);
        },

        /**
         * Calculate the scale factor and vertical shift for the JSXGraph div
         * in full screen mode.
         *
         * @param {Object} obj Reference to a DOM node.
         * @returns Object {scale: number, vshift: number}
         * @see JXG.Board#fullscreenListener
         * @private
         */
        _getScaleFactors: function (node) {
            var width = node.getBoundingClientRect().width,
                height = node.getBoundingClientRect().height,

                // Determine the maximum scale factor.
                r_w = window.screen.width / width,
                r_h = window.screen.height / height,

                // Determine the vertical shift to place the div in the center of the screen
                vshift = (window.screen.height - height) * 0.5,

                // Scaling factor: if not supplied, it's taken as large as possible
                scale = Math.min(r_w, r_h);

            // Adapt vshift and scale for landscape on tablets
            if (window.matchMedia && window.matchMedia('(orientation:landscape)').matches &&
                window.screen.width < window.screen.height) {
                // Landscape on iOS: it returns 'landscape', but still width < height.
                r_w = window.screen.height / width;
                r_h = window.screen.width / height;
                scale = Math.min(r_w, r_h);
                vshift = (window.screen.width - height) * 0.5;
            }
            scale *= 0.85;

            return { scale: scale, vshift: vshift, width: width };
        },

        /**
         * Scale and vertically shift a DOM element (usually a JSXGraph div)
         * inside of a parent DOM
         * element which is set to fullscreen.
         * This is realized with a CSS transformation.
         *          *
         * @param  {String} wrap_id  id of the parent DOM element which is in fullscreen mode
         * @param  {String} inner_id id of the DOM element which is scaled and shifted
         * @param  {Number} scale    Scaling factor
         * @param  {Number} vshift   Vertical shift (in pixel)
         *
         * @private
         * @see JXG.Board#toFullscreen
         * @see JXG.Board#fullscreenListener
         *
         */
        scaleJSXGraphDiv: function (wrap_id, inner_id, scale, vshift) {
            var len = document.styleSheets.length, style,

                pseudo_keys = [':fullscreen', ':-webkit-full-screen', ':-moz-full-screen', ':-ms-fullscreen'],
                len_pseudo = pseudo_keys.length, i,

                // CSS rules to center the inner div horizontally and vertically.
                rule_inner = '{margin:0 auto;transform:matrix(' + scale + ',0,0,' + scale + ',0,' + vshift + ');}',

                // A previously installed CSS rule to center the JSXGraph div has to
                // be searched and removed again.
                regex = new RegExp('.*#' + wrap_id + ':.*full.*screen.*#' + inner_id + '.*auto;.*transform:.*matrix');

            if (len === 0) {
                // In case there is not a single CSS rule defined at all.
                style = document.createElement('style');
                // WebKit hack :(
                style.appendChild(document.createTextNode(''));
                // Add the <style> element to the page
                document.body.appendChild(style);
                len = document.styleSheets.length;
            }

            // Remove a previously installed CSS rule.
            if (document.styleSheets[len - 1].cssRules.length > 0 &&
                regex.test(document.styleSheets[len - 1].cssRules[0].cssText) &&
                document.styleSheets[len - 1].deleteRule) {

                document.styleSheets[len - 1].deleteRule(0);
            }

            // Install a CSS rule to center the JSXGraph div at the first position of the list.
            for (i = 0; i < len_pseudo; i++) {
                try {
                    document.styleSheets[len - 1].insertRule('#' + wrap_id + pseudo_keys[i] + ' #' + inner_id + rule_inner, 0);
                    break;
                } catch (err) {
                    // console.log('JXG.scaleJSXGraphDiv: Could not add CSS rule "' + pseudo_keys[i] + '".');
                    // console.log('One possible reason could be that the id of the JSXGraph container does not start with a letter.');
                }
            }
            if (i === len_pseudo) {
                console.log('JXG.scaleJSXGraphDiv: Could not add any CSS rule.');
                console.log('One possible reason could be that the id of the JSXGraph container does not start with a letter.');
            }
        }
    });

    return JXG;
});

/*
 Copyright 2008-2022
 Matthias Ehmann,
 Michael Gerhaeuser,
 Carsten Miller,
 Bianca Valentin,
 Alfred Wassermann,
 Peter Wilfahrt

 This file is part of JSXGraph.

 JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

 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 and
 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, DOMParser: true, ActiveXObject: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/type
 */

define('utils/xml',['jxg', 'utils/type'], function (JXG, Type) {

    "use strict";

    /**
     * Holds browser independent xml parsing routines. Won't work in environments other than browsers.
     * @namespace
     */
    JXG.XML = {
        /**
         * Cleans out unneccessary whitespaces in a chunk of xml.
         * @param {Object} el
         */
        cleanWhitespace: function (el) {
            var cur = el.firstChild;

            while (Type.exists(cur)) {
                if (cur.nodeType === 3 && !/\S/.test(cur.nodeValue)) {
                    el.removeChild(cur);
                } else if (cur.nodeType === 1) {
                    this.cleanWhitespace(cur);
                }
                cur = cur.nextSibling;
            }
        },

        /**
         * Converts a given string into a XML tree.
         * @param {String} str
         * @returns {Object} The xml tree represented by the root node.
         */
        parse: function (str) {
            var parser, tree, DP;

            // DOMParser is a function in all browsers, except older IE and Safari.
            // In IE it does not exists (workaround in else branch), in Safari it's an object.
            if (typeof DOMParser === 'function' || typeof DOMParser === 'object') {
                DP = DOMParser;
            } else {
                // IE workaround, since there is no DOMParser
                DP = function () {
                    this.parseFromString = function (str) {
                        var d;

                        if (typeof ActiveXObject === 'function') {
                            d = new ActiveXObject('MSXML.DomDocument');
                            d.loadXML(str);
                        }

                        return d;
                    };
                };
            }

            parser = new DP();
            tree = parser.parseFromString(str, 'text/xml');
            this.cleanWhitespace(tree);

            return tree;
        }
    };

    return JXG.XML;
});
/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/type
 */

/**
 * @fileoverview In this file the EventEmitter interface is defined.
 */

define('utils/event',['jxg', 'utils/type'], function (JXG, Type) {

    "use strict";

    /**
     * Event namespace
     * @namespace
     */
    JXG.EventEmitter = {
        /**
         * Holds the registered event handlers.
         * @type Object
         */
        eventHandlers: {},

        /**
         * Events can be suspended to prevent endless loops.
         * @type Object
         */
        suspended: {},

        /**
         * Triggers all event handlers of this element for a given event.
         * @param {Array} event
         * @param {Array} args The arguments passed onto the event handler
         * @returns Reference to the object.
         */
        trigger: function (event, args) {
            var i, j, h, evt, len1, len2;

            len1 = event.length;
            for (j = 0; j < len1; j++) {
                evt = this.eventHandlers[event[j]];

                if (!this.suspended[event[j]]) {
                    this.suspended[event[j]] = true;

                    if (evt) {
                        len2 = evt.length;

                        for (i = 0; i < len2; i++) {
                            h = evt[i];
                            h.handler.apply(h.context, args);
                        }
                    }

                    this.suspended[event[j]] = false;
                }
            }

            return this;
        },

        /**
         * Register a new event handler. For a list of possible events see documentation
         * of the elements and objects implementing
         * the {@link EventEmitter} interface.
         * @param {String} event
         * @param {Function} handler
         * @param {Object} [context] The context the handler will be called in, default is the element itself.
         * @returns Reference to the object.
         */
        on: function (event, handler, context) {
            if (!Type.isArray(this.eventHandlers[event])) {
                this.eventHandlers[event] = [];
            }

            context = Type.def(context, this);

            this.eventHandlers[event].push({
                handler: handler,
                context: context
            });

            return this;
        },

        /**
         * Unregister an event handler.
         * @param {String} event
         * @param {Function} [handler]
         * @returns Reference to the object.
         */
        off: function (event, handler) {
            var i;

            if (!event || !Type.isArray(this.eventHandlers[event])) {
                return this;
            }

            if (handler) {
                i = Type.indexOf(this.eventHandlers[event], handler, 'handler');
                if (i > -1) {
                    this.eventHandlers[event].splice(i, 1);
                }

                if (this.eventHandlers[event].length === 0) {
                    delete this.eventHandlers[event];
                }
            } else {
                delete this.eventHandlers[event];
            }

            return this;
        },

        /**
         * @description Implements the functionality from this interface in the given object.
         * All objects getting their event handling
         * capabilities from this method should document it by adding
         * the <tt>on, off, triggerEventHandlers</tt> via the
         * borrows tag as methods to their documentation:
         * <pre>@borrows JXG.EventEmitter#on as this.on</pre>
         * @param {Object} o
         */
        eventify: function (o) {
            o.eventHandlers = {};
            o.on = this.on;
            o.off = this.off;
            o.triggerEventHandlers = this.trigger;
            o.trigger = this.trigger;
            o.suspended = {};
        }
    };

    return JXG.EventEmitter;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, Float32Array: true */
/*jslint nomen: true, plusplus: true, bitwise: true*/

/* depends:
 jxg
 */

/**
 * @fileoverview In this file the namespace JXG.Math is defined, which is the base namespace
 * for namespaces like Math.Numerics, Math.Algebra, Math.Statistics etc.
 */

define('math/math',['jxg', 'utils/type'], function (JXG, Type) {

    "use strict";

    var undef,

        /*
         * Dynamic programming approach for recursive functions.
         * From "Speed up your JavaScript, Part 3" by Nicholas C. Zakas.
         * @see JXG.Math.factorial
         * @see JXG.Math.binomial
         * http://blog.thejit.org/2008/09/05/memoization-in-javascript/
         *
         * This method is hidden, because it is only used in JXG.Math. If someone wants
         * to use it in JSXGraph outside of JXG.Math, it should be moved to jsxgraph.js
         */
        memoizer = function (f) {
            var cache, join;

            if (f.memo) {
                return f.memo;
            }

            cache = {};
            join = Array.prototype.join;

            f.memo = function () {
                var key = join.call(arguments);

                // Seems to be a bit faster than "if (a in b)"
                return (cache[key] !== undef) ?
                        cache[key] :
                        cache[key] = f.apply(this, arguments);
            };

            return f.memo;
        };

    /**
     * Math namespace.
     * @namespace
     */
    JXG.Math = {
        /**
         * eps defines the closeness to zero. If the absolute value of a given number is smaller
         * than eps, it is considered to be equal to zero.
         * @type Number
         */
        eps: 0.000001,

        /**
         * Determine the relative difference between two numbers.
         * @param  {Number} a First number
         * @param  {Number} b Second number
         * @returns {Number}  Relative difference between a and b: |a-b| / max(|a|, |b|)
         */
        relDif: function(a, b) {
            var c = Math.abs(a),
                d = Math.abs(b);

            d = Math.max(c, d);

            return (d === 0.0) ? 0.0 : Math.abs(a - b) / d;
        },

        /**
         * The JavaScript implementation of the % operator returns the symmetric modulo.
         * mod and "%" are both identical if a >= 0 and m >= 0 but the results differ if a or m < 0.
         * @param {Number} a
         * @param {Number} m
         * @returns {Number} Mathematical modulo <tt>a mod m</tt>
         */
        mod: function (a, m) {
            return a - Math.floor(a / m) * m;
        },

        /**
         * Initializes a vector as an array with the coefficients set to the given value resp. zero.
         * @param {Number} n Length of the vector
         * @param {Number} [init=0] Initial value for each coefficient
         * @returns {Array} A <tt>n</tt> times <tt>m</tt>-matrix represented by a
         * two-dimensional array. The inner arrays hold the columns, the outer array holds the rows.
         */
        vector: function (n, init) {
            var r, i;

            init = init || 0;
            r = [];

            for (i = 0; i < n; i++) {
                r[i] = init;
            }

            return r;
        },

        /**
         * Initializes a matrix as an array of rows with the given value.
         * @param {Number} n Number of rows
         * @param {Number} [m=n] Number of columns
         * @param {Number} [init=0] Initial value for each coefficient
         * @returns {Array} A <tt>n</tt> times <tt>m</tt>-matrix represented by a
         * two-dimensional array. The inner arrays hold the columns, the outer array holds the rows.
         */
        matrix: function (n, m, init) {
            var r, i, j;

            init = init || 0;
            m = m || n;
            r = [];

            for (i = 0; i < n; i++) {
                r[i] = [];

                for (j = 0; j < m; j++) {
                    r[i][j] = init;
                }
            }

            return r;
        },

        /**
         * Generates an identity matrix. If n is a number and m is undefined or not a number, a square matrix is generated,
         * if n and m are both numbers, an nxm matrix is generated.
         * @param {Number} n Number of rows
         * @param {Number} [m=n] Number of columns
         * @returns {Array} A square matrix of length <tt>n</tt> with all coefficients equal to 0 except a_(i,i), i out of (1, ..., n), if <tt>m</tt> is undefined or not a number
         * or a <tt>n</tt> times <tt>m</tt>-matrix with a_(i,j) = 0 and a_(i,i) = 1 if m is a number.
         */
        identity: function (n, m) {
            var r, i;

            if ((m === undef) && (typeof m !== 'number')) {
                m = n;
            }

            r = this.matrix(n, m);

            for (i = 0; i < Math.min(n, m); i++) {
                r[i][i] = 1;
            }

            return r;
        },

        /**
         * Generates a 4x4 matrix for 3D to 2D projections.
         * @param {Number} l Left
         * @param {Number} r Right
         * @param {Number} t Top
         * @param {Number} b Bottom
         * @param {Number} n Near
         * @param {Number} f Far
         * @returns {Array} 4x4 Matrix
         */
        frustum: function (l, r, b, t, n, f) {
            var ret = this.matrix(4, 4);

            ret[0][0] = (n * 2) / (r - l);
            ret[0][1] = 0;
            ret[0][2] = (r + l) / (r - l);
            ret[0][3] = 0;

            ret[1][0] = 0;
            ret[1][1] = (n * 2) / (t - b);
            ret[1][2] = (t + b) / (t - b);
            ret[1][3] = 0;

            ret[2][0] = 0;
            ret[2][1] = 0;
            ret[2][2] = -(f + n) / (f - n);
            ret[2][3] = -(f * n * 2) / (f - n);

            ret[3][0] = 0;
            ret[3][1] = 0;
            ret[3][2] = -1;
            ret[3][3] = 0;

            return ret;
        },

        /**
         * Generates a 4x4 matrix for 3D to 2D projections.
         * @param {Number} fov Field of view in vertical direction, given in rad.
         * @param {Number} ratio Aspect ratio of the projection plane.
         * @param {Number} n Near
         * @param {Number} f Far
         * @returns {Array} 4x4 Projection Matrix
         */
        projection: function (fov, ratio, n, f) {
            var t = n * Math.tan(fov / 2),
                r = t * ratio;

            return this.frustum(-r, r, -t, t, n, f);
        },

        /**
         * Multiplies a vector vec to a matrix mat: mat * vec. The matrix is interpreted by this function as an array of rows. Please note: This
         * function does not check if the dimensions match.
         * @param {Array} mat Two dimensional array of numbers. The inner arrays describe the columns, the outer ones the matrix' rows.
         * @param {Array} vec Array of numbers
         * @returns {Array} Array of numbers containing the result
         * @example
         * var A = [[2, 1],
         *          [1, 3]],
         *     b = [4, 5],
         *     c;
         * c = JXG.Math.matVecMult(A, b)
         * // c === [13, 19];
         */
        matVecMult: function (mat, vec) {
            var i, s, k,
                m = mat.length,
                n = vec.length,
                res = [];

            if (n === 3) {
                for (i = 0; i < m; i++) {
                    res[i] = mat[i][0] * vec[0] + mat[i][1] * vec[1] + mat[i][2] * vec[2];
                }
            } else {
                for (i = 0; i < m; i++) {
                    s = 0;
                    for (k = 0; k < n; k++) {
                        s += mat[i][k] * vec[k];
                    }
                    res[i] = s;
                }
            }
            return res;
        },

        /**
         * Computes the product of the two matrices mat1*mat2.
         * @param {Array} mat1 Two dimensional array of numbers
         * @param {Array} mat2 Two dimensional array of numbers
         * @returns {Array} Two dimensional Array of numbers containing result
         */
        matMatMult: function (mat1, mat2) {
            var i, j, s, k,
                m = mat1.length,
                n = m > 0 ? mat2[0].length : 0,
                m2 = mat2.length,
                res = this.matrix(m, n);

            for (i = 0; i < m; i++) {
                for (j = 0; j < n; j++) {
                    s = 0;
                    for (k = 0; k < m2; k++) {
                        s += mat1[i][k] * mat2[k][j];
                    }
                    res[i][j] = s;
                }
            }
            return res;
        },

        /**
         * Transposes a matrix given as a two dimensional array.
         * @param {Array} M The matrix to be transposed
         * @returns {Array} The transpose of M
         */
        transpose: function (M) {
            var MT, i, j,
                m, n;

            // number of rows of M
            m = M.length;
            // number of columns of M
            n = M.length > 0 ? M[0].length : 0;
            MT = this.matrix(n, m);

            for (i = 0; i < n; i++) {
                for (j = 0; j < m; j++) {
                    MT[i][j] = M[j][i];
                }
            }

            return MT;
        },

        /**
         * Compute the inverse of an nxn matrix with Gauss elimination.
         * @param {Array} Ain
         * @returns {Array} Inverse matrix of Ain
         */
        inverse: function (Ain) {
            var i, j, k, s, ma, r, swp,
                n = Ain.length,
                A = [],
                p = [],
                hv = [];

            for (i = 0; i < n; i++) {
                A[i] = [];
                for (j = 0; j < n; j++) {
                    A[i][j] = Ain[i][j];
                }
                p[i] = i;
            }

            for (j = 0; j < n; j++) {
                // pivot search:
                ma = Math.abs(A[j][j]);
                r = j;

                for (i = j + 1; i < n; i++) {
                    if (Math.abs(A[i][j]) > ma) {
                        ma = Math.abs(A[i][j]);
                        r = i;
                    }
                }

                // Singular matrix
                if (ma <= this.eps) {
                    return [];
                }

                // swap rows:
                if (r > j) {
                    for (k = 0; k < n; k++) {
                        swp = A[j][k];
                        A[j][k] = A[r][k];
                        A[r][k] = swp;
                    }

                    swp = p[j];
                    p[j] = p[r];
                    p[r] = swp;
                }

                // transformation:
                s = 1.0 / A[j][j];
                for (i = 0; i < n; i++) {
                    A[i][j] *= s;
                }
                A[j][j] = s;

                for (k = 0; k < n; k++) {
                    if (k !== j) {
                        for (i = 0; i < n; i++) {
                            if (i !== j) {
                                A[i][k] -= A[i][j] * A[j][k];
                            }
                        }
                        A[j][k] = -s * A[j][k];
                    }
                }
            }

            // swap columns:
            for (i = 0; i < n; i++) {
                for (k = 0; k < n; k++) {
                    hv[p[k]] = A[i][k];
                }
                for (k = 0; k < n; k++) {
                    A[i][k] = hv[k];
                }
            }

            return A;
        },

        /**
         * Inner product of two vectors a and b. n is the length of the vectors.
         * @param {Array} a Vector
         * @param {Array} b Vector
         * @param {Number} [n] Length of the Vectors. If not given the length of the first vector is taken.
         * @returns {Number} The inner product of a and b.
         */
        innerProduct: function (a, b, n) {
            var i,
                s = 0;

            if (n === undef || !Type.isNumber(n)) {
                n = a.length;
            }

            for (i = 0; i < n; i++) {
                s += a[i] * b[i];
            }

            return s;
        },

        /**
         * Calculates the cross product of two vectors both of length three.
         * In case of homogeneous coordinates this is either
         * <ul>
         * <li>the intersection of two lines</li>
         * <li>the line through two points</li>
         * </ul>
         * @param {Array} c1 Homogeneous coordinates of line or point 1
         * @param {Array} c2 Homogeneous coordinates of line or point 2
         * @returns {Array} vector of length 3: homogeneous coordinates of the resulting point / line.
         */
        crossProduct: function (c1, c2) {
            return [c1[1] * c2[2] - c1[2] * c2[1],
                c1[2] * c2[0] - c1[0] * c2[2],
                c1[0] * c2[1] - c1[1] * c2[0]];
        },

        /**
         * Euclidean norm of a vector.
         *
         * @param {Array} a Array containing a vector.
         * @param {Number} n (Optional) length of the array.
         * @returns {Number} Euclidean norm of the vector.
         */
        norm: function(a, n) {
            var i, sum = 0.0;

            if (n === undef || !Type.isNumber(n)) {
                n = a.length;
            }

            for (i = 0; i < n; i++) {
                sum += a[i] * a[i];
            }

            return Math.sqrt(sum);
        },

        /**
         * Compute the factorial of a positive integer. If a non-integer value
         * is given, the fraction will be ignored.
         * @function
         * @param {Number} n
         * @returns {Number} n! = n*(n-1)*...*2*1
         */
        factorial: memoizer(function (n) {
            if (n < 0) {
                return NaN;
            }

            n = Math.floor(n);

            if (n === 0 || n === 1) {
                return 1;
            }

            return n * this.factorial(n - 1);
        }),

        /**
         * Computes the binomial coefficient n over k.
         * @function
         * @param {Number} n Fraction will be ignored
         * @param {Number} k Fraction will be ignored
         * @returns {Number} The binomial coefficient n over k
         */
        binomial: memoizer(function (n, k) {
            var b, i;

            if (k > n || k < 0) {
                return NaN;
            }

            k = Math.round(k);
            n = Math.round(n);

            if (k === 0 || k === n) {
                return 1;
            }

            b = 1;

            for (i = 0; i < k; i++) {
                b *= (n - i);
                b /= (i + 1);
            }

            return b;
        }),

        /**
         * Calculates the cosine hyperbolicus of x.
         * @function
         * @param {Number} x The number the cosine hyperbolicus will be calculated of.
         * @returns {Number} Cosine hyperbolicus of the given value.
         */
        cosh: Math.cosh || function (x) {
            return (Math.exp(x) + Math.exp(-x)) * 0.5;
        },

        /**
         * Sine hyperbolicus of x.
         * @function
         * @param {Number} x The number the sine hyperbolicus will be calculated of.
         * @returns {Number} Sine hyperbolicus of the given value.
         */
        sinh: Math.sinh || function (x) {
            return (Math.exp(x) - Math.exp(-x)) * 0.5;
        },

        /**
         * Hyperbolic arc-cosine of a number.
         *
         * @param {Number} x
         * @returns {Number}
         */
        acosh: Math.acosh || function(x) {
            return Math.log(x + Math.sqrt(x * x - 1));
        },

        /**
         * Hyperbolic arcsine of a number
         * @param {Number} x
         * @returns {Number}
         */
        asinh: Math.asinh || function(x) {
            if (x === -Infinity) {
                return x;
            }
            return Math.log(x + Math.sqrt(x * x + 1));
        },

        /**
         * Computes the cotangent of x.
         * @function
         * @param {Number} x The number the cotangent will be calculated of.
         * @returns {Number} Cotangent of the given value.
         */
        cot: function (x) {
            return 1 / Math.tan(x);
        },

        /**
         * Computes the inverse cotangent of x.
         * @param {Number} x The number the inverse cotangent will be calculated of.
         * @returns {Number} Inverse cotangent of the given value.
         */
        acot: function (x) {
            return ((x >= 0) ? (0.5) : (-0.5)) * Math.PI - Math.atan(x);
        },

        /**
         * Compute n-th real root of a real number. n must be strictly positive integer.
         * If n is odd, the real n-th root exists and is negative.
         * For n even, for negative valuees of x NaN is returned
         * @param  {Number} x radicand. Must be non-negative, if n even.
         * @param  {Number} n index of the root. must be strictly positive integer.
         * @returns {Number} returns real root or NaN
         *
         * @example
         * nthroot(16, 4): 2
         * nthroot(-27, 3): -3
         * nthroot(-4, 2): NaN
         */
        nthroot: function(x, n) {
            var inv = 1 / n;

            if (n <= 0 || Math.floor(n) !== n) {
                return NaN;
            }

            if (x === 0.0) {
                return 0.0;
            }

            if (x > 0) {
                return Math.exp(inv * Math.log(x));
            }

            // From here on, x is negative
            if (n % 2 === 1) {
                return -Math.exp(inv * Math.log(-x));
            }

            // x negative, even root
            return NaN;
        },

        /**
         * Computes cube root of real number
         * Polyfill for Math.cbrt().
         *
         * @function
         * @param  {Number} x Radicand
         * @returns {Number} Cube root of x.
         */
        cbrt: Math.cbrt || function(x) {
            return this.nthroot(x, 3);
        },

        /**
         * Compute base to the power of exponent.
         * @param {Number} base
         * @param {Number} exponent
         * @returns {Number} base to the power of exponent.
         */
        pow: function (base, exponent) {
            if (base === 0) {
                if (exponent === 0) {
                    return 1;
                }
                return 0;
            }

            // exponent is an integer
            if (Math.floor(exponent) === exponent) {
                return Math.pow(base, exponent);
            }

            // exponent is not an integer
            if (base > 0) {
                return Math.exp(exponent * Math.log(base));
            }

            return NaN;
        },

        /**
         * Compute base to the power of the rational exponent m / n.
         * This function first reduces the fraction m/n and then computes
         * JXG.Math.pow(base, m/n).
         *
         * This function is necessary to have the same results for e.g.
         * (-8)^(1/3) = (-8)^(2/6) = -2
         * @param {Number} base
         * @param {Number} m numerator of exponent
         * @param {Number} n denominator of exponent
         * @returns {Number} base to the power of exponent.
         */
        ratpow: function(base, m, n) {
            var g;
            if (m === 0) {
                return 1;
            }
            if (n === 0) {
                return NaN;
            }

            g = this.gcd(m, n);
            return this.nthroot(this.pow(base, m / g), n / g);
        },

        /**
         * Logarithm to base 10.
         * @param {Number} x
         * @returns {Number} log10(x) Logarithm of x to base 10.
         */
        log10: function (x) {
            return Math.log(x) / Math.log(10.0);
        },

        /**
         * Logarithm to base 2.
         * @param {Number} x
         * @returns {Number} log2(x) Logarithm of x to base 2.
         */
        log2: function (x) {
            return Math.log(x) / Math.log(2.0);
        },

        /**
         * Logarithm to arbitrary base b. If b is not given, natural log is taken, i.e. b = e.
         * @param {Number} x
         * @param {Number} b base
         * @returns {Number} log(x, b) Logarithm of x to base b, that is log(x)/log(b).
         */
        log: function (x, b) {
            if (b !== undefined && Type.isNumber(b)) {
                return Math.log(x) / Math.log(b);
            }

            return Math.log(x);
        },

        /**
         * The sign() function returns the sign of a number, indicating whether the number is positive, negative or zero.
         *
         * @function
         * @param  {Number} x A Number
         * @returns {[type]}  This function has 5 kinds of return values,
         *    1, -1, 0, -0, NaN, which represent "positive number", "negative number", "positive zero", "negative zero"
         *    and NaN respectively.
         */
        sign: Math.sign || function(x) {
            x = +x; // convert to a number
            if (x === 0 || isNaN(x)) {
                return x;
            }
            return x > 0 ? 1 : -1;
        },

        /**
         * A square & multiply algorithm to compute base to the power of exponent.
         * Implementated by Wolfgang Riedl.
         *
         * @param {Number} base
         * @param {Number} exponent
         * @returns {Number} Base to the power of exponent
         */
        squampow: function (base, exponent) {
            var result;

            if (Math.floor(exponent) === exponent) {
                // exponent is integer (could be zero)
                result = 1;

                if (exponent < 0) {
                    // invert: base
                    base = 1.0 / base;
                    exponent *= -1;
                }

                while (exponent !== 0) {
                    if (exponent & 1) {
                        result *= base;
                    }

                    exponent >>= 1;
                    base *= base;
                }
                return result;
            }

            return this.pow(base, exponent);
        },

        /**
         * Greatest common divisor (gcd) of two numbers.
         * @see <a href="http://rosettacode.org/wiki/Greatest_common_divisor#JavaScript">rosettacode.org</a>
         *
         * @param  {Number} a First number
         * @param  {Number} b Second number
         * @returns {Number}   gcd(a, b) if a and b are numbers, NaN else.
         */
        gcd: function (a, b) {
            a = Math.abs(a);
            b = Math.abs(b);

            if (!(Type.isNumber(a) && Type.isNumber(b))) {
                return NaN;
            }
            if (b > a) {
                var temp = a;
                a = b;
                b = temp;
            }

            while (true) {
                a %= b;
                if (a === 0) { return b; }
                b %= a;
                if (b === 0) { return a; }
            }
        },

        /**
         * Least common multiple (lcm) of two numbers.
         *
         * @param  {Number} a First number
         * @param  {Number} b Second number
         * @returns {Number}   lcm(a, b) if a and b are numbers, NaN else.
         */
        lcm: function (a, b) {
            var ret;

            if (!(Type.isNumber(a) && Type.isNumber(b))) {
                return NaN;
            }

            ret = a * b;
            if (ret !== 0) {
                return ret / this.gcd(a, b);
            }

            return 0;
        },

        /**
         *  Error function, see {@link https://en.wikipedia.org/wiki/Error_function}.
         *
         * @see JXG.Math.PropFunc.erf
         * @param  {Number} x
         * @returns {Number}
         */
        erf: function(x) {
            return this.ProbFuncs.erf(x);
        },

        /**
         * Complementary error function, i.e. 1 - erf(x).
         *
         * @see JXG.Math.erf
         * @see JXG.Math.PropFunc.erfc
         * @param  {Number} x
         * @returns {Number}
         */
         erfc: function(x) {
            return this.ProbFuncs.erfc(x);
        },

        /**
         * Inverse of error function
         *
         * @see JXG.Math.erf
         * @see JXG.Math.PropFunc.erfi
         * @param  {Number} x
         * @returns {Number}
         */
         erfi: function(x) {
            return this.ProbFuncs.erfi(x);
        },

        /**
         * Normal distribution function
         *
         * @see JXG.Math.PropFunc.ndtr
         * @param  {Number} x
         * @returns {Number}
         */
         ndtr: function(x) {
            return this.ProbFuncs.ndtr(x);
        },

        /**
         * Inverse of normal distribution function
         *
         * @see JXG.Math.ndtr
         * @see JXG.Math.PropFunc.ndtri
         * @param  {Number} x
         * @returns {Number}
         */
         ndtri: function(x) {
            return this.ProbFuncs.ndtri(x);
        },

        /* ********************  Comparisons and logical operators ************** */

        /**
         * Logical test: a < b?
         *
         * @param {Number} a
         * @param {Number} b
         * @returns {Boolean}
         */
        lt: function(a, b) {
            return a < b;
        },

        /**
         * Logical test: a <= b?
         *
         * @param {Number} a
         * @param {Number} b
         * @returns {Boolean}
         */
        leq: function(a, b) {
            return a <= b;
        },

        /**
         * Logical test: a > b?
         *
         * @param {Number} a
         * @param {Number} b
         * @returns {Boolean}
         */
        gt: function(a, b) {
            return a > b;
        },

        /**
         * Logical test: a >= b?
         *
         * @param {Number} a
         * @param {Number} b
         * @returns {Boolean}
         */
        geq: function(a, b) {
            return a >= b;
        },

        /**
         * Logical test: a === b?
         *
         * @param {Number} a
         * @param {Number} b
         * @returns {Boolean}
         */
        eq: function(a, b) {
            return a === b;
        },

        /**
         * Logical test: a !== b?
         *
         * @param {Number} a
         * @param {Number} b
         * @returns {Boolean}
         */
        neq: function(a, b) {
            return a !== b;
        },

        /**
         * Logical operator: a && b?
         *
         * @param {Boolean} a
         * @param {Boolean} b
         * @returns {Boolean}
         */
        and: function(a, b) {
            return a && b;
        },

        /**
         * Logical operator: !a?
         *
         * @param {Boolean} a
         * @returns {Boolean}
         */
        not: function(a) {
            return !a;
        },

        /**
         * Logical operator: a || b?
         *
         * @param {Boolean} a
         * @param {Boolean} b
         * @returns {Boolean}
         */
        or: function(a, b) {
            return a || b;
        },

        /**
         * Logical operator: either a or b?
         *
         * @param {Boolean} a
         * @param {Boolean} b
         * @returns {Boolean}
         */
        xor: function(a, b) {
            return (a || b) && !(a && b);
        },

        /* *************************** Normalize *************************** */

        /**
         * Normalize the standard form [c, b0, b1, a, k, r, q0, q1].
         * @private
         * @param {Array} stdform The standard form to be normalized.
         * @returns {Array} The normalized standard form.
         */
        normalize: function (stdform) {
            var n, signr,
                a2 = 2 * stdform[3],
                r = stdform[4] / a2;

            stdform[5] = r;
            stdform[6] = -stdform[1] / a2;
            stdform[7] = -stdform[2] / a2;

            if (!isFinite(r)) {
                n = Math.sqrt(stdform[1] * stdform[1] + stdform[2] * stdform[2]);

                stdform[0] /= n;
                stdform[1] /= n;
                stdform[2] /= n;
                stdform[3] = 0;
                stdform[4] = 1;
            } else if (Math.abs(r) >= 1) {
                stdform[0] = (stdform[6] * stdform[6] + stdform[7] * stdform[7] - r * r) / (2 * r);
                stdform[1] = -stdform[6] / r;
                stdform[2] = -stdform[7] / r;
                stdform[3] = 1 / (2 * r);
                stdform[4] = 1;
            } else {
                signr = (r <= 0 ? -1 : 1);
                stdform[0] = signr * (stdform[6] * stdform[6] + stdform[7] * stdform[7] - r * r) * 0.5;
                stdform[1] = -signr * stdform[6];
                stdform[2] = -signr * stdform[7];
                stdform[3] = signr / 2;
                stdform[4] = signr * r;
            }

            return stdform;
        },

        /**
         * Converts a two dimensional array to a one dimensional Float32Array that can be processed by WebGL.
         * @param {Array} m A matrix in a two dimensional array.
         * @returns {Float32Array} A one dimensional array containing the matrix in column wise notation. Provides a fall
         * back to the default JavaScript Array if Float32Array is not available.
         */
        toGL: function (m) {
            var v, i, j;

            if (typeof Float32Array === 'function') {
                v = new Float32Array(16);
            } else {
                v = new Array(16);
            }

            if (m.length !== 4 && m[0].length !== 4) {
                return v;
            }

            for (i = 0; i < 4; i++) {
                for (j = 0; j < 4; j++) {
                    v[i + 4 * j] = m[i][j];
                }
            }

            return v;
        },

        /**
         * Theorem of Vieta: Given a set of simple zeroes x_0, ..., x_n
         * of a polynomial f, compute the coefficients s_k, (k=0,...,n-1)
         * of the polynomial of the form. See {@link https://de.wikipedia.org/wiki/Elementarsymmetrisches_Polynom}.
         * <p>
         *  f(x) = (x-x_0)*...*(x-x_n) =
         *  x^n + sum_{k=1}^{n} (-1)^(k) s_{k-1} x^(n-k)
         * </p>
         * @param {Array} x Simple zeroes of the polynomial.
         * @returns {Array} Coefficients of the polynomial.
         *
         */
        Vieta: function(x) {
            var n = x.length,
                s = [],
                m, k, y;

            s = x.slice();
            for (m = 1; m < n; ++m) {
                y = s[m];
                s[m] *= s[m - 1];
                for (k = m - 1; k >= 1; --k) {
                    s[k] += s[k - 1] * y;
                }
                s[0] += y;
            }
            return s;
        }
    };

    return JXG.Math;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 utils/event
 math/math
 */

define('base/coords',[
    'jxg', 'base/constants', 'utils/event', 'utils/type', 'math/math'
], function (JXG, Const, EventEmitter, Type, Mat) {

    "use strict";

    /**
     * @fileoverview In this file the Coords object is defined, a class to manage all
     * properties and methods coordinates usually have.
     */

    /**
     * Constructs a new Coordinates object.
     * @class This is the Coordinates class.
     * All members a coordinate has to provide
     * are defined here.
     * @param {Number} method The type of coordinates given by the user. Accepted values are <b>COORDS_BY_SCREEN</b> and <b>COORDS_BY_USER</b>.
     * @param {Array} coordinates An array of affine coordinates.
     * @param {JXG.Board} board A reference to a board.
     * @oaram {Boolean} [emitter=true]
     * @borrows JXG.EventEmitter#on as this.on
     * @borrows JXG.EventEmitter#off as this.off
     * @borrows JXG.EventEmitter#triggerEventHandlers as this.triggerEventHandlers
     * @borrows JXG.EventEmitter#eventHandlers as this.eventHandlers
     * @constructor
     */
    JXG.Coords = function (method, coordinates, board, emitter) {
        /**
         * Stores the board the object is used on.
         * @type JXG.Board
         */
        this.board = board;

        /**
         * Stores coordinates for user view as homogeneous coordinates.
         * @type Array
         */
        this.usrCoords = [];
        //this.usrCoords = new Float64Array(3);

        /**
         * Stores coordinates for screen view as homogeneous coordinates.
         * @type Array
         */
        this.scrCoords = [];
        //this.scrCoords = new Float64Array(3);

        /**
         * If true, this coordinates object will emit update events every time
         * the coordinates are set.
         * @type boolean
         * @default true
         */
        this.emitter = !Type.exists(emitter) || emitter;

        if (this.emitter) {
            EventEmitter.eventify(this);
        }
        this.setCoordinates(method, coordinates, false, true);
    };

    JXG.extend(JXG.Coords.prototype, /** @lends JXG.Coords.prototype */ {
        /**
         * Normalize homogeneous coordinates
         * @private
         */
        normalizeUsrCoords: function () {
            if (Math.abs(this.usrCoords[0]) > Mat.eps) {
                this.usrCoords[1] /= this.usrCoords[0];
                this.usrCoords[2] /= this.usrCoords[0];
                this.usrCoords[0] = 1.0;
            }
        },

        /**
         * Compute screen coordinates out of given user coordinates.
         * @private
         */
        usr2screen: function (doRound) {
            var mround = Math.round,  // Is faster on IE, maybe slower with JIT compilers
                b = this.board,
                uc = this.usrCoords,
                oc = b.origin.scrCoords;

            if (doRound === true) {
                this.scrCoords[0] = mround(uc[0]);
                this.scrCoords[1] = mround(uc[0] * oc[1] + uc[1] * b.unitX);
                this.scrCoords[2] = mround(uc[0] * oc[2] - uc[2] * b.unitY);
            } else {
                this.scrCoords[0] = uc[0];
                this.scrCoords[1] = uc[0] * oc[1] + uc[1] * b.unitX;
                this.scrCoords[2] = uc[0] * oc[2] - uc[2] * b.unitY;
            }
        },

        /**
         * Compute user coordinates out of given screen coordinates.
         * @private
         */
        screen2usr: function () {
            var o = this.board.origin.scrCoords,
                sc = this.scrCoords,
                b = this.board;

            this.usrCoords[0] =  1.0;
            this.usrCoords[1] = (sc[1] - o[1]) / b.unitX;
            this.usrCoords[2] = (o[2] - sc[2]) / b.unitY;
        },

        /**
         * Calculate distance of one point to another.
         * @param {Number} coord_type The type of coordinates used here. Possible values are <b>JXG.COORDS_BY_USER</b> and <b>JXG.COORDS_BY_SCREEN</b>.
         * @param {JXG.Coords} coordinates The Coords object to which the distance is calculated.
         * @returns {Number} The distance
         */
        distance: function (coord_type, coordinates) {
            var sum = 0,
                c,
                ucr = this.usrCoords,
                scr = this.scrCoords,
                f;

            if (coord_type === Const.COORDS_BY_USER) {
                c = coordinates.usrCoords;
                f = ucr[0] - c[0];
                sum = f * f;

                if (sum > Mat.eps * Mat.eps) {
                    return Number.POSITIVE_INFINITY;
                }
                f = ucr[1] - c[1];
                sum += f * f;
                f = ucr[2] - c[2];
                sum += f * f;
            } else {
                c = coordinates.scrCoords;
                //f = scr[0]-c[0];
                //sum = f*f;
                f = scr[1] - c[1];
                sum += f * f;
                f = scr[2] - c[2];
                sum += f * f;
            }

            return Math.sqrt(sum);
        },

        /**
         * Set coordinates by either user coordinates or screen coordinates and recalculate the other one.
         * @param {Number} coord_type The type of coordinates used here. Possible values are <b>COORDS_BY_USER</b> and <b>COORDS_BY_SCREEN</b>.
         * @param {Array} coordinates An array of affine coordinates the Coords object is set to.
         * @param {Boolean} [doRound=true] flag If true or null round the coordinates in usr2screen. This is used in smooth curve plotting.
         * The IE needs rounded coordinates. Id doRound==false we have to round in updatePathString.
         * @param {Boolean} [noevent=false]
         * @returns {JXG.Coords} Reference to the coords object.
         */
        setCoordinates: function (coord_type, coordinates, doRound, noevent) {
            var uc = this.usrCoords,
                sc = this.scrCoords,
                // Original values
                ou = [uc[0], uc[1], uc[2]],
                os = [sc[0], sc[1], sc[2]];

            if (coord_type === Const.COORDS_BY_USER) {
                if (coordinates.length === 2) { // Euclidean coordinates
                    uc[0] = 1.0;
                    uc[1] = coordinates[0];
                    uc[2] = coordinates[1];
                } else { // Homogeneous coordinates (normalized)
                    uc[0] = coordinates[0];
                    uc[1] = coordinates[1];
                    uc[2] = coordinates[2];
                    this.normalizeUsrCoords();
                }
                this.usr2screen(doRound);
            } else {
                if (coordinates.length === 2) { // Euclidean coordinates
                    sc[1] = coordinates[0];
                    sc[2] = coordinates[1];
                } else { // Homogeneous coordinates (normalized)
                    sc[1] = coordinates[1];
                    sc[2] = coordinates[2];
                }
                this.screen2usr();
            }

            if (this.emitter && !noevent && (os[1] !== sc[1] || os[2] !== sc[2])) {
                this.triggerEventHandlers(['update'], [ou, os]);
            }

            return this;
        },

        /**
        * Copy array, either scrCoords or usrCoords
        * Uses slice() in case of standard arrays and set() in case of
        * typed arrays.
        * @private
        * @param {String} obj Either 'scrCoords' or 'usrCoords'
        * @param {Number} offset Offset, defaults to 0 if not given
        * @returns {Array} Returns copy of the coords array either as standard array or as
        *   typed array.
        */
        copy: function (obj, offset) {
            if (offset === undefined) {
                offset = 0;
            }

            return this[obj].slice(offset);
        },

        /**
         * Test if one of the usrCoords is NaN or the coordinates are infinite.
         * @returns {Boolean} true if the coordinates are finite, false otherwise.
         */
        isReal: function() {
            return (!isNaN(this.usrCoords[1] + this.usrCoords[2])) && (Math.abs(this.usrCoords[0]) > Mat.eps);
        },

        /**
         * Triggered whenever the coordinates change.
         * @name JXG.Coords#update
         * @param {Array} ou Old user coordinates
         * @param {Array} os Old screen coordinates
         * @event
         */
        __evt__update: function (ou, os) { },

        /**
         * @ignore
         */
        __evt: function () {}
    });

    return JXG.Coords;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, html_sanitize: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 */

/**
 * @fileoverview expect.js provides utilities for parameter magic by normalizing multi-type parameters.
 */

define('utils/expect',[
    'jxg', 'utils/type', 'base/constants', 'base/coords'
], function (JXG, Type, Const, Coords) {

    "use strict";

    var Expect = {
        /**
         * Apply an expect method on every element of an array.
         *
         * @param {Array} a
         * @param {function} format
         * @param {Boolean} [copy=false]
         *
         * @returns {Array}
         */
        each: function (a, format, copy) {
            var i, len,
                r = [];

            if (Type.exists(a.length)) {
                len = a.length;
                for (i = 0; i < len; i++) {
                    r.push(format.call(this, a[i], copy));
                }
            }

            return r;
        },

        /**
         * Normalize points and coord objects into a coord object.
         *
         * @param {JXG.Point|JXG.Coords} c
         * @param {Boolean} [copy=false] Return a copy, not a reference
         *
         * @returns {JXG.Coords}
         */
        coords: function (c, copy) {
            var coord = c;

            if (c && c.elementClass === Const.OBJECT_CLASS_POINT) {
                coord = c.coords;
            } else if (c.usrCoords && c.scrCoords && c.usr2screen) {
                coord = c;
            }

            if (copy) {
                coord = new Coords(Const.COORDS_BY_USER, coord.usrCoords, coord.board);
            }

            return coord;
        },

        /**
         * Normalize points, coordinate arrays and coord objects into a coordinate array.
         *
         * @param {JXG.Point|JXG.Coords|Array} c
         * @param {Boolean} [copy=false] Return a copy, not a reference
         *
         * @returns {Array} Homogeneous coordinates
         */
        coordsArray: function (c, copy) {
            var coord;

            if (!Type.isArray(c)) {
                coord = this.coords(c).usrCoords;
            } else {
                coord = c;
            }

            if (coord.length < 3) {
                coord.unshift(1);
            }

            if (copy) {
                coord = [coord[0], coord[1], coord[2]];
            }

            return coord;
        }
    };

    JXG.Expect = Expect;

    return Expect;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Carsten Miller,
        Andreas Walter,
        Alfred Wassermann

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/
/*eslint no-loss-of-precision: off */

/* depends:
 jxg
 math/math
 utils/type
 */

define('math/probfuncs',['math/math', 'utils/type'], function (Mat, Type) {

    "use strict";

    /**
     * Probability functions, e.g. error function,
     * see: https://en.wikipedia.org/wiki/Error_function
     * Ported from
     * by https://github.com/jeremybarnes/cephes/blob/master/cprob/ndtr.c,
     *
     * Cephes Math Library Release 2.9:  November, 2000
     * Copyright 1984, 1987, 1988, 1992, 2000 by Stephen L. Moshier
     *
     * @name JXG.Math.ProbFuncs
     * @exports Mat.ProbFuncs as JXG.Math.ProbFuncs
     * @namespace
     */
    Mat.ProbFuncs = {
        MAXNUM: 1.701411834604692317316873e38,  // 2**127
        SQRTH:  7.07106781186547524401E-1,      // sqrt(2)/2
        SQRT2:  1.41421356237309504880,         // sqrt(2)
        MAXLOG: 7.08396418532264106224E2,       // log 2**1022

        P: [
            2.46196981473530512524E-10,
            5.64189564831068821977E-1,
            7.46321056442269912687E0,
            4.86371970985681366614E1,
            1.96520832956077098242E2,
            5.26445194995477358631E2,
            9.34528527171957607540E2,
            1.02755188689515710272E3,
            5.57535335369399327526E2
        ],

        Q: [
            1.32281951154744992508E1,
            8.67072140885989742329E1,
            3.54937778887819891062E2,
            9.75708501743205489753E2,
            1.82390916687909736289E3,
            2.24633760818710981792E3,
            1.65666309194161350182E3,
            5.57535340817727675546E2
        ],

        R: [
            5.64189583547755073984E-1,
            1.27536670759978104416E0,
            5.01905042251180477414E0,
            6.16021097993053585195E0,
            7.40974269950448939160E0,
            2.97886665372100240670E0
        ],

        S: [
            2.26052863220117276590E0,
            9.39603524938001434673E0,
            1.20489539808096656605E1,
            1.70814450747565897222E1,
            9.60896809063285878198E0,
            3.36907645100081516050E0
        ],

        T: [
            9.60497373987051638749E0,
            9.00260197203842689217E1,
            2.23200534594684319226E3,
            7.00332514112805075473E3,
            5.55923013010394962768E4
        ],

        U: [
            3.35617141647503099647E1,
            5.21357949780152679795E2,
            4.59432382970980127987E3,
            2.26290000613890934246E4,
            4.92673942608635921086E4
        ],

        // UTHRESH: 37.519379347,
        M: 128.0,
        MINV: 0.0078125,

        /**
         *
         *	Exponential of squared argument
         *
         * SYNOPSIS:
         *
         * double x, y, expx2();
         * int sign;
         *
         * y = expx2( x, sign );
         *
         *
         *
         * DESCRIPTION:
         *
         * Computes y = exp(x*x) while suppressing error amplification
         * that would ordinarily arise from the inexactness of the
         * exponential argument x*x.
         *
         * If sign < 0, the result is inverted; i.e., y = exp(-x*x) .
         *
         *
         * ACCURACY:
         *
         *                      Relative error:
         * arithmetic    domain     # trials      peak         rms
         *   IEEE      -26.6, 26.6    10^7       3.9e-16     8.9e-17
         *
         * @private
         * @param  {Number} x
         * @param  {Number} sign (int)
         * @returns {Number}
         */
        expx2: function(x, sign) {
            // double x;
            // int sign;
            var u, u1, m, f;

            x = Math.abs(x);
            if (sign < 0) {
                x = -x;
            }

            // Represent x as an exact multiple of M plus a residual.
            //    M is a power of 2 chosen so that exp(m * m) does not overflow
            //    or underflow and so that |x - m| is small.
            m = this.MINV * Math.floor(this.M * x + 0.5);
            f = x - m;

            // x^2 = m^2 + 2mf + f^2
            u = m * m;
            u1 = 2 * m * f  +  f * f;

            if (sign < 0) {
                u = -u;
                u1 = -u1;
            }

            if ( u + u1 > this.MAXLOG) {
                return Infinity;
            }

            // u is exact, u1 is small.
            u = Math.exp(u) * Math.exp(u1);
            return u;
        },

        /**
         *
         *	Evaluate polynomial
         *
         * SYNOPSIS:
         *
         * int N;
         * double x, y, coef[N+1], polevl[];
         *
         * y = polevl( x, coef, N );
         *
         * DESCRIPTION:
         *
         * Evaluates polynomial of degree N:
         *
         *                     2          N
         * y  =  C  + C x + C x  +...+ C x
         *        0    1     2          N
         *
         * Coefficients are stored in reverse order:
         *
         * coef[0] = C  , ..., coef[N] = C  .
         *            N                   0
         *
         *  The function p1evl() assumes that coef[N] = 1.0 and is
         * omitted from the array.  Its calling arguments are
         * otherwise the same as polevl().
         *
         *
         * SPEED:
         *
         * In the interest of speed, there are no checks for out
         * of bounds arithmetic.  This routine is used by most of
         * the functions in the library.  Depending on available
         * equipment features, the user may wish to rewrite the
         * program in microcode or assembly language.
         *
         * @private
         * @param  {Number} x
         * @param  {Number} coef
         * @param  {Number} N
         * @returns {Number}
         */
        polevl: function(x, coef, N) {
            var ans, i;

            if (Type.exists(coef.reduce)) {
                return coef.reduce(function(acc, c) {
                    return acc * x + c;
                }, 0);
            }
            // Polyfill
            for (i = 0, ans = 0; i <= N; i++) {
                ans = ans * x + coef[i];
            }
            return ans;

        },

        /**
         * Evaluate polynomial when coefficient of x is 1.0.
         * Otherwise same as polevl.
         *
         * @private
         * @param  {Number} x
         * @param  {Number} coef
         * @param  {Number} N
         * @returns {Number}
         */
        p1evl: function(x, coef, N) {
            var ans, i;

            if (Type.exists(coef.reduce)) {
                return coef.reduce(function(acc, c) {
                    return acc * x + c;
                }, 1);
            }
            // Polyfill
            for (i = 0, ans = 1; i < N; i++) {
                ans = ans * x + coef[i];
            }
            return ans;
        },

        /**
         *
         *	Normal distribution function
         *
         * SYNOPSIS:
         *
         * y = ndtr( x );
         *
         * DESCRIPTION:
         *
         * Returns the area under the Gaussian probability density
         * function, integrated from minus infinity to x:
         *
         *                            x
         *                             -
         *                   1        | |          2
         *    ndtr(x)  = ---------    |    exp( - t /2 ) dt
         *               sqrt(2pi)  | |
         *                           -
         *                          -inf.
         *
         *             =  ( 1 + erf(z) ) / 2
         *             =  erfc(z) / 2
         *
         * where z = x/sqrt(2). Computation is via the functions
         * erf and erfc with care to avoid error amplification in computing exp(-x^2).
         *
         *
         * ACCURACY:
         *
         *                      Relative error:
         * arithmetic   domain     # trials      peak         rms
         *    IEEE     -13,0        30000       1.3e-15     2.2e-16
         *
         *
         * ERROR MESSAGES:
         *
         *   message         condition         value returned
         * erfc underflow    x > 37.519379347       0.0
         *
         * @param  {Number} a
         * @returns {Number}
         */
        ndtr: function(a) {
            // a: double, return double
            var x, y, z;

            x = a * this.SQRTH;
            z = Math.abs(x);

            if (z < 1.0) {
                y = 0.5 + 0.5 * this.erf(x);
            } else {
                y = 0.5 * this.erfce(z);
                /* Multiply by exp(-x^2 / 2)  */
                z = this.expx2(a, -1);
                y = y * Math.sqrt(z);
                if (x > 0) {
                    y = 1.0 - y;
                }
            }
            return y;
        },

        /**
         * @private
         * @param  {Number} a
         * @returns {Number}
         */
        _underflow: function(a) {
            console.log('erfc', 'UNDERFLOW');
            if (a < 0) {
                return 2.0;
            }
            return 0.0;
        },

        /**
         *
         *	Complementary error function
         *
         * SYNOPSIS:
         *
         * double x, y, erfc();
         *
         * y = erfc( x );
         *
         *
         *
         * DESCRIPTION:
         *
         *
         *  1 - erf(x) =
         *
         *                           inf.
         *                             -
         *                  2         | |          2
         *   erfc(x)  =  --------     |    exp( - t  ) dt
         *               sqrt(pi)   | |
         *                           -
         *                            x
         *
         *
         * For small x, erfc(x) = 1 - erf(x); otherwise rational
         * approximations are computed.
         *
         * A special function expx2.c is used to suppress error amplification
         * in computing exp(-x^2).
         *
         *
         * ACCURACY:
         *
         *                      Relative error:
         * arithmetic   domain     # trials      peak         rms
         *    IEEE      0,26.6417   30000       1.3e-15     2.2e-16
         *
         *
         * ERROR MESSAGES:
         *
         *   message         condition              value returned
         * erfc underflow    x > 9.231948545 (DEC)       0.0
         *
         * @param  {Number} a
         * @returns {Number}
         */
        erfc: function(a) {
            var p, q, x, y, z;

            if (a < 0.0) {
                x = -a;
            } else {
                x = a;
            }
            if (x < 1.0) {
                return 1.0 - this.erf(a);
            }

            z = -a * a;
            if (z < -this.MAXLOG) {
                return this._underflow(a);
            }

            z = this.expx2(a, -1);  // Compute z = exp(z).

            if (x < 8.0) {
                p = this.polevl(x, this.P, 8);
                q = this.p1evl(x, this.Q, 8);
            } else {
                p = this.polevl(x, this.R, 5);
                q = this.p1evl(x, this.S, 6);
            }

            y = (z * p) / q;

            if (a < 0) {
                y = 2.0 - y;
            }

            if (y === 0.0) {
                return this._underflow(a);
            }

            return y;
        },

        /**
         * Exponentially scaled erfc function
         *   exp(x^2) erfc(x)
         *   valid for x > 1.
         *   Use with ndtr and expx2.
         *
         * @private
         * @param {Number} x
         * @returns {Number}
         */
        erfce: function(x) {
            var p, q;

            if (x < 8.0) {
                p = this.polevl(x, this.P, 8);
                q = this.p1evl(x, this.Q, 8);
            } else {
                p = this.polevl( x, this.R, 5 );
                q = this.p1evl( x, this.S, 6 );
            }
            return p / q;
        },

        /**
         *	Error function
         *
         * SYNOPSIS:
         *
         * double x, y, erf();
         *
         * y = erf( x );
         *
         *
         *
         * DESCRIPTION:
         *
         * The integral is
         *
         *                           x
         *                            -
         *                 2         | |          2
         *   erf(x)  =  --------     |    exp( - t  ) dt.
         *              sqrt(pi)   | |
         *                          -
         *                           0
         *
         * For 0 <= |x| < 1, erf(x) = x * P4(x**2)/Q5(x**2); otherwise
         * erf(x) = 1 - erfc(x).
         *
         *
         * ACCURACY:
         *
         *                      Relative error:
         * arithmetic   domain     # trials      peak         rms
         *    DEC       0,1         14000       4.7e-17     1.5e-17
         *    IEEE      0,1         30000       3.7e-16     1.0e-16
         *
         * @param  {Number} x
         * @returns {Number}
         */
        erf: function(x) {
            var y, z;

            if (Math.abs(x) > 1.0) {
                return 1.0 - this.erfc(x);
            }
            z = x * x;
            y = x * this.polevl(z, this.T, 4) / this.p1evl(z, this.U, 5);
            return y;
        },

        s2pi: 2.50662827463100050242E0, // sqrt(2pi)

        // approximation for 0 <= |y - 0.5| <= 3/8 */
        P0: [
            -5.99633501014107895267E1,
             9.80010754185999661536E1,
            -5.66762857469070293439E1,
             1.39312609387279679503E1,
            -1.23916583867381258016E0
        ],

        Q0: [
             1.95448858338141759834E0,
             4.67627912898881538453E0,
             8.63602421390890590575E1,
            -2.25462687854119370527E2,
             2.00260212380060660359E2,
            -8.20372256168333339912E1,
             1.59056225126211695515E1,
            -1.18331621121330003142E0,
        ],

        //  Approximation for interval z = sqrt(-2 log y ) between 2 and 8
        //  i.e., y between exp(-2) = .135 and exp(-32) = 1.27e-14.
        P1: [
            4.05544892305962419923E0,
            3.15251094599893866154E1,
            5.71628192246421288162E1,
            4.40805073893200834700E1,
            1.46849561928858024014E1,
            2.18663306850790267539E0,
           -1.40256079171354495875E-1,
           -3.50424626827848203418E-2,
           -8.57456785154685413611E-4
        ],

        Q1: [
            1.57799883256466749731E1,
            4.53907635128879210584E1,
            4.13172038254672030440E1,
            1.50425385692907503408E1,
            2.50464946208309415979E0,
           -1.42182922854787788574E-1,
           -3.80806407691578277194E-2,
           -9.33259480895457427372E-4
        ],

        // Approximation for interval z = sqrt(-2 log y ) between 8 and 64
        // i.e., y between exp(-32) = 1.27e-14 and exp(-2048) = 3.67e-890.
        P2: [
            3.23774891776946035970E0,
            6.91522889068984211695E0,
            3.93881025292474443415E0,
            1.33303460815807542389E0,
            2.01485389549179081538E-1,
            1.23716634817820021358E-2,
            3.01581553508235416007E-4,
            2.65806974686737550832E-6,
            6.23974539184983293730E-9
        ],

        Q2: [
            6.02427039364742014255E0,
            3.67983563856160859403E0,
            1.37702099489081330271E0,
            2.16236993594496635890E-1,
            1.34204006088543189037E-2,
            3.28014464682127739104E-4,
            2.89247864745380683936E-6,
            6.79019408009981274425E-9
        ],

        /**
         *
         *	Inverse of Normal distribution function
         *
         * SYNOPSIS:
         *
         * double x, y, ndtri();
         *
         * x = ndtri( y );
         *
         * DESCRIPTION:
         *
         * Returns the argument, x, for which the area under the
         * Gaussian probability density function (integrated from
         * minus infinity to x) is equal to y.
         *
         *
         * For small arguments 0 < y < exp(-2), the program computes
         * z = sqrt( -2.0 * log(y) );  then the approximation is
         * x = z - log(z)/z  - (1/z) P(1/z) / Q(1/z).
         * There are two rational functions P/Q, one for 0 < y < exp(-32)
         * and the other for y up to exp(-2).  For larger arguments,
         * w = y - 0.5, and  x/sqrt(2pi) = w + w**3 R(w**2)/S(w**2)).
         *
         *
         * ACCURACY:
         *
         *                      Relative error:
         * arithmetic   domain        # trials      peak         rms
         *    DEC      0.125, 1         5500       9.5e-17     2.1e-17
         *    DEC      6e-39, 0.135     3500       5.7e-17     1.3e-17
         *    IEEE     0.125, 1        20000       7.2e-16     1.3e-16
         *    IEEE     3e-308, 0.135   50000       4.6e-16     9.8e-17
         *
         *
         * ERROR MESSAGES:
         *
         *   message         condition    value returned
         * ndtri domain       x <= 0        -MAXNUM
         * ndtri domain       x >= 1         MAXNUM
         *
         * @param  {Number} y0
         * @returns {Number}
         */
        ndtri: function(y0) {
            var x, y, z, y2, x0, x1, code;

            if (y0 <= 0.0) {
                //console.log("ndtri", "DOMAIN ");
                return -Infinity; // -this.MAXNUM;
            }
            if (y0 >= 1.0) {
                // console.log("ndtri", "DOMAIN");
                return Infinity; // this.MAXNUM;
            }

            code = 1;
            y = y0;
            if (y > (1.0 - 0.13533528323661269189))  {  // 0.135... = exp(-2)
                y = 1.0 - y;
                code = 0;
            }

            if (y > 0.13533528323661269189) {
                y = y - 0.5;
                y2 = y * y;
                x = y + y * (y2 * this.polevl(y2, this.P0, 4) / this.p1evl(y2, this.Q0, 8));
                x = x * this.s2pi;
                return x;
            }

            x = Math.sqrt( -2.0 * Math.log(y) );
            x0 = x - Math.log(x) / x;

            z = 1.0 / x;
            if (x < 8.0) {           // y > exp(-32) = 1.2664165549e-14
                x1 = z * this.polevl(z, this.P1, 8 ) / this.p1evl(z, this.Q1, 8);
            } else {
                x1 = z * this.polevl(z, this.P2, 8) / this.p1evl(z, this.Q2, 8);
            }
            x = x0 - x1;
            if (code !== 0) {
                x = -x;
            }
            return x;
        },

        /**
         * Inverse of error function erf.
         * 
         * @param  {Number} x
         * @returns {Number}
         */
        erfi: function(x) {
            return this.ndtri((x + 1) * 0.5) * this.SQRTH;
        }
    };

    return Mat.ProbFuncs;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Alfred Wassermann

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/math
 utils/type
 */

define('math/ia',['jxg', 'math/math', 'utils/type'], function (JXG, Mat, Type) {

    "use strict";

    JXG.Math.DoubleBits = function() {
        var hasTypedArrays = false,
            DOUBLE_VIEW = new Float64Array(1),
            UINT_VIEW   = new Uint32Array(DOUBLE_VIEW.buffer),
            doubleBitsLE, toDoubleLE, lowUintLE, highUintLE,
            doubleBitsBE, toDoubleBE, lowUintBE, highUintBE,
            doubleBits, toDouble, lowUint, highUint;

        if (Float64Array !== undefined) {

            DOUBLE_VIEW[0] = 1.0;
            hasTypedArrays = true;
            if (UINT_VIEW[1] === 0x3ff00000) {
                // Use little endian
                doubleBitsLE = function(n) {
                    DOUBLE_VIEW[0] = n;
                    return [UINT_VIEW[0], UINT_VIEW[1]];
                };
                toDoubleLE = function(lo, hi) {
                    UINT_VIEW[0] = lo;
                    UINT_VIEW[1] = hi;
                    return DOUBLE_VIEW[0];
                };

                lowUintLE = function(n) {
                    DOUBLE_VIEW[0] = n;
                    return UINT_VIEW[0];
                };

                highUintLE = function(n) {
                    DOUBLE_VIEW[0] = n;
                    return UINT_VIEW[1];
                };

                this.doubleBits = doubleBitsLE;
                this.pack = toDoubleLE;
                this.lo = lowUintLE;
                this.hi = highUintLE;
            } else if (UINT_VIEW[0] === 0x3ff00000) {
                //Use big endian
                doubleBitsBE = function(n) {
                    DOUBLE_VIEW[0] = n;
                    return [UINT_VIEW[1], UINT_VIEW[0]];
                };

                toDoubleBE = function(lo, hi) {
                    UINT_VIEW[1] = lo;
                    UINT_VIEW[0] = hi;
                    return DOUBLE_VIEW[0];
                };

                lowUintBE = function(n) {
                    DOUBLE_VIEW[0] = n;
                    return UINT_VIEW[1];
                };

                highUintBE = function(n) {
                    DOUBLE_VIEW[0] = n;
                    return UINT_VIEW[0];
                };

                this.doubleBits = doubleBitsBE;
                this.pack = toDoubleBE;
                this.lo = lowUintBE;
                this.hi = highUintBE;
            } else {
                hasTypedArrays = false;
            }
        }

        // if (!hasTypedArrays) {
        //     var buffer = new Buffer(8)
        //     doubleBits = function(n) {
        //         buffer.writeDoubleLE(n, 0, true);
        //         return [buffer.readUInt32LE(0, true), buffer.readUInt32LE(4, true)];
        //     };

        //     toDouble = function(lo, hi) {
        //         buffer.writeUInt32LE(lo, 0, true);
        //         buffer.writeUInt32LE(hi, 4, true);
        //         return buffer.readDoubleLE(0, true);
        //     };
        //     lowUint = function(n) {
        //         buffer.writeDoubleLE(n, 0, true);
        //         return buffer.readUInt32LE(0, true);
        //     };

        //     highUint = function(n) {
        //         buffer.writeDoubleLE(n, 0, true);
        //         return buffer.readUInt32LE(4, true);
        //     };

        //     this.doubleBits = doubleBits;
        //     this.pack = toDouble;
        //     this.lo = lowUint;
        //     this.hi = highUint;
        // }
    };

    JXG.extend(JXG.Math.DoubleBits.prototype, /** @lends JXG.Math.DoubleBits.prototype */ {

        sign: function(n) {
            return this.hi(n) >>> 31;
        },

        exponent: function(n) {
            var b = this.hi(n);
            return ((b<<1) >>> 21) - 1023;
        },

        fraction: function(n) {
            var lo = this.lo(n),
                hi = this.hi(n),
                b = hi & ((1<<20) - 1);

            if (hi & 0x7ff00000) {
                b += (1<<20);
            }
            return [lo, b];
        },

        denormalized: function(n) {
            var hi = this.hi(n);
            return !(hi & 0x7ff00000);
        }
    });

    var doubleBits = new JXG.Math.DoubleBits(),

        /**
         * Interval for interval arithmetics. Consists of the properties
         * <ul>
         *  <li>lo
         *  <li>hi
         * </ul>
         * @name JXG.Math.Interval
         * @type Object
         */
        MatInterval = function (lo, hi) {
            if (lo !== undefined && hi !== undefined) {
                // possible cases:
                // - Interval(1, 2)
                // - Interval(Interval(1, 1), Interval(2, 2))     // singletons are required
                if (Mat.IntervalArithmetic.isInterval(lo)) {
                    if (!Mat.IntervalArithmetic.isSingleton(lo)) {
                        throw new TypeError('JXG.Math.IntervalArithmetic: interval `lo` must be a singleton');
                    }
                    this.lo = lo.lo;
                } else {
                    this.lo = lo;
                }
                if (Mat.IntervalArithmetic.isInterval(hi)) {
                    if (!Mat.IntervalArithmetic.isSingleton(hi)) {
                        throw new TypeError('JXG.Math.IntervalArithmetic: interval `hi` must be a singleton');
                    }
                    this.hi = hi.hi;
                } else {
                    this.hi = hi;
                }
            } else if (lo !== undefined) {
                // possible cases:
                // - Interval([1, 2])
                // - Interval([Interval(1, 1), Interval(2, 2)])
                if (Array.isArray(lo)) {
                  return new MatInterval(lo[0], lo[1]);
                }
                // - Interval(1)
                return new MatInterval(lo, lo);
            } else { // This else is necessary even if jslint declares it as redundant
                // possible cases:
                // - Interval()
                this.lo = this.hi = 0;
            }
        };

    JXG.extend(MatInterval.prototype, {
        print: function() {
            console.log('[',this.lo, this.hi,']');
        },

        set: function(lo, hi) {
            this.lo = lo;
            this.hi = hi;
            return this;
        },

        bounded: function(lo, hi) {
            return this.set(Mat.IntervalArithmetic.prev(lo), Mat.IntervalArithmetic.next(hi));
        },

        boundedSingleton: function(v) {
            return this.bounded(v, v);
        },

        assign: function(lo, hi) {
            if (typeof lo !== 'number' || typeof hi !== 'number') {
                throw new TypeError('JXG.Math.Interval#assign: arguments must be numbers');
            }
            if (isNaN(lo) || isNaN(hi) || lo > hi) {
                return this.setEmpty();
            }
            return this.set(lo, hi);
        },

        setEmpty: function() {
            return this.set(Number.POSITIVE_INFINITY, Number.NEGATIVE_INFINITY);
        },

        setWhole: function() {
            return this.set(Number.NEGATIVE_INFINITY, Number.POSITIVE_INFINITY);
        },

        open: function(lo, hi){
            return this.assign(Mat.IntervalArithmetic.next(lo), Mat.IntervalArithmetic.prev(hi));
        },

        halfOpenLeft: function(lo, hi) {
            return this.assign(Mat.IntervalArithmetic.next(lo), hi);
        },

        halfOpenRight: function(lo, hi) {
            return this.assign(lo, Mat.IntervalArithmetic.prev(hi));
        },

        toArray: function() {
            return [this.lo, this.hi];
        },

        clone: function() {
            return new MatInterval().set(this.lo, this.hi);
        }
    });

    /**
     * Object for interval arithmetics.
     * @name JXG.Math.IntervalArithmetic
     * @namespace
     */
    JXG.Math.IntervalArithmetic =  {

        Interval: function(lo, hi) {
            return new MatInterval(lo, hi);
        },

        isInterval: function(i) {
            return i !== null && typeof i === 'object' && typeof i.lo === 'number' && typeof i.hi === 'number';
        },

        isSingleton: function(i) {
            return i.lo === i.hi;
        },

        /*
         * Arithmetics
         */

        /**
         * Addition
         *
         * @param {JXG.Math.Interval|Number} x
         * @param {JXG.Math.Interval|Number} y
         * @returns JXG.Math.Interval
         */
        add: function(x, y) {
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (Type.isNumber(y)) {
                y = this.Interval(y);
            }
            return new MatInterval(this.addLo(x.lo, y.lo), this.addHi(x.hi, y.hi));
        },

        /**
         * Subtraction
         *
         * @param {JXG.Math.Interval|Number} x
         * @param {JXG.Math.Interval|Number} y
         * @returns JXG.Math.Interval
         */
         sub: function(x, y) {
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (Type.isNumber(y)) {
                y = this.Interval(y);
            }
            return new MatInterval(this.subLo(x.lo, y.hi), this.subHi(x.hi, y.lo));
        },

        /**
         * Multiplication
         *
         * @param {JXG.Math.Interval|Number} x
         * @param {JXG.Math.Interval|Number} y
         * @returns JXG.Math.Interval
         */
         mul: function(x, y) {
            var xl, xh, yl, yh, out;

            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (Type.isNumber(y)) {
                y = this.Interval(y);
            }

            if (this.isEmpty(x) || this.isEmpty(y)) {
              return this.EMPTY.clone();
            }
            xl = x.lo;
            xh = x.hi;
            yl = y.lo;
            yh = y.hi;
            out = new MatInterval();

            if (xl < 0) {
                if (xh > 0) {
                    if (yl < 0) {
                        if (yh > 0) {
                            // mixed * mixed
                            out.lo = Math.min(this.mulLo(xl, yh), this.mulLo(xh, yl));
                            out.hi = Math.max(this.mulHi(xl, yl), this.mulHi(xh, yh));
                        } else {
                            // mixed * negative
                            out.lo = this.mulLo(xh, yl);
                            out.hi = this.mulHi(xl, yl);
                        }
                    } else {
                        if (yh > 0) {
                            // mixed * positive
                            out.lo = this.mulLo(xl, yh);
                            out.hi = this.mulHi(xh, yh);
                        } else {
                            // mixed * zero
                            out.lo = 0;
                            out.hi = 0;
                        }
                    }
                } else {
                    if (yl < 0) {
                        if (yh > 0) {
                            // negative * mixed
                            out.lo = this.mulLo(xl, yh);
                            out.hi = this.mulHi(xl, yl);
                        } else {
                            // negative * negative
                            out.lo = this.mulLo(xh, yh);
                            out.hi = this.mulHi(xl, yl);
                        }
                    } else {
                        if (yh > 0) {
                            // negative * positive
                            out.lo = this.mulLo(xl, yh);
                            out.hi = this.mulHi(xh, yl);
                        } else {
                            // negative * zero
                            out.lo = 0;
                            out.hi = 0;
                        }
                    }
                }
            } else {
                if (xh > 0) {
                    if (yl < 0) {
                        if (yh > 0) {
                            // positive * mixed
                            out.lo = this.mulLo(xh, yl);
                            out.hi = this.mulHi(xh, yh);
                        } else {
                            // positive * negative
                            out.lo = this.mulLo(xh, yl);
                            out.hi = this.mulHi(xl, yh);
                        }
                    } else {
                        if (yh > 0) {
                            // positive * positive
                            out.lo = this.mulLo(xl, yl);
                            out.hi = this.mulHi(xh, yh);
                        } else {
                            // positive * zero
                            out.lo = 0;
                            out.hi = 0;
                        }
                    }
                } else {
                    // zero * any other value
                    out.lo = 0;
                    out.hi = 0;
                }
            }
            return out;
        },

        /**
         * Division
         *
         * @param {JXG.Math.Interval|Number} x
         * @param {JXG.Math.Interval|Number} y
         * @returns JXG.Math.Interval
         */
         div: function(x, y) {
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (Type.isNumber(y)) {
                y = this.Interval(y);
            }

            if (this.isEmpty(x) || this.isEmpty(y)) {
                return this.EMPTY.clone();
            }
            if (this.zeroIn(y)) {
                if (y.lo !== 0) {
                    if (y.hi !== 0) {
                        return this.divZero(x);
                    }
                    return this.divNegative(x, y.lo);
                }
                if (y.hi !== 0) {
                    return this.divPositive(x, y.hi);
                }
                return this.EMPTY.clone();
            }
            return this.divNonZero(x, y);
        },

        /**
         * Return +x (i.e. identity)
         *
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
         positive: function(x) {
            return new MatInterval(x.lo, x.hi);
        },

        /**
         * Return -x
         *
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
         negative: function(x) {
            if (Type.isNumber(x)) {
                return new MatInterval(-x);
            }
            return new MatInterval(-x.hi, -x.lo);
        },

        /*
         * Utils
         */

        /**
         * Test if interval is empty set.
         * @param {JXG.Math.Interval} i
         * @returns Boolean
         */
        isEmpty: function(i) {
            return i.lo > i.hi;
        },

        /**
         * Test if interval is (-Infinity, Infinity).
         * @param {JXG.Math.Interval} i
         * @returns Boolean
         */
        isWhole: function(i){
            return i.lo === -Infinity && i.hi === Infinity;
        },

        /**
         * Test if interval contains 0.
         * @param {JXG.Math.Interval} i
         * @returns Boolean
         */
         zeroIn: function(i) {
            return this.hasValue(i, 0);
        },

        /**
         * Test if interval contains a specific value.
         * @param {JXG.Math.Interval} i
         * @param {Number} value
         * @returns Boolean
         */
         hasValue: function(i, value) {
            if (this.isEmpty(i)) {
                return false;
            }
            return i.lo <= value && value <= i.hi;
        },

        /**
         * Test if interval x contains interval y.
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns Boolean
         */
         hasInterval: function(x, y) {
            if (this.isEmpty(x)) {
                return true;
            }
            return !this.isEmpty(y) && y.lo <= x.lo && x.hi <= y.hi;
        },

        /**
         * Test if intervals x and y have non-zero intersection.
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns Boolean
         */
         intervalsOverlap: function(x, y) {
            if (this.isEmpty(x) || this.isEmpty(y)) {
                return false;
            }
            return (x.lo <= y.lo && y.lo <= x.hi) || (y.lo <= x.lo && x.lo <= y.hi);
        },

        /*
         * Division
         */
        /**
         * @private
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns JXG.Math.Interval
         */
        divNonZero: function(x, y) {
            var xl = x.lo,
                xh = x.hi,
                yl = y.lo,
                yh = y.hi,
                out = new MatInterval();

            if (xh < 0) {
                if (yh < 0) {
                    out.lo = this.divLo(xh, yl);
                    out.hi = this.divHi(xl, yh);
                } else {
                    out.lo = this.divLo(xl, yl);
                    out.hi = this.divHi(xh, yh);
                }
            } else if (xl < 0) {
                if (yh < 0) {
                    out.lo = this.divLo(xh, yh);
                    out.hi = this.divHi(xl, yh);
                } else {
                    out.lo = this.divLo(xl, yl);
                    out.hi = this.divHi(xh, yl);
                }
            } else {
                if (yh < 0) {
                    out.lo = this.divLo(xh, yh);
                    out.hi = this.divHi(xl, yl);
                } else {
                    out.lo = this.divLo(xl, yh);
                    out.hi = this.divHi(xh, yl);
                }
            }
            return out;
        },

        /**
         * @private
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns JXG.Math.Interval
         */
         divPositive: function(x, v) {
            if (x.lo === 0 && x.hi === 0) {
                return x;
            }

            if (this.zeroIn(x)) {
                // mixed considering zero in both ends
                return this.WHOLE;
            }

            if (x.hi < 0) {
                // negative / v
                return new MatInterval(Number.NEGATIVE_INFINITY, this.divHi(x.hi, v));
            }
            // positive / v
            return new MatInterval(this.divLo(x.lo, v), Number.POSITIVE_INFINITY);
        },

        /**
         * @private
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns JXG.Math.Interval
         */
         divNegative: function(x, v) {
            if (x.lo === 0 && x.hi === 0) {
                return x;
            }

            if (this.zeroIn(x)) {
                // mixed considering zero in both ends
                return this.WHOLE;
            }

            if (x.hi < 0) {
                // negative / v
                return new MatInterval(this.divLo(x.hi, v), Number.POSITIVE_INFINITY);
            }
            // positive / v
            return new MatInterval(Number.NEGATIVE_INFINITY, this.divHi(x.lo, v));
        },

        /**
         * @private
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
         divZero: function(x) {
            if (x.lo === 0 && x.hi === 0) {
                return x;
            }
            return this.WHOLE;
        },

        /*
         * Algebra
         */
        /**
         * x mod y:  x - n * y
         * @param {JXG.Math.Interval|Number} x
         * @param {JXG.Math.Interval|Number} y
         * @returns JXG.Math.Interval
         */
        fmod: function(x, y) {
            var yb, n;
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (Type.isNumber(y)) {
                y = this.Interval(y);
            }
            if (this.isEmpty(x) || this.isEmpty(y)) {
                return this.EMPTY.clone();
            }
            yb = x.lo < 0 ? y.lo : y.hi;
            n = x.lo / yb;
            if (n < 0) {
                n = Math.ceil(n);
            } else {
                n = Math.floor(n);
            }
            // x mod y = x - n * y
            return this.sub(x, this.mul(y, new MatInterval(n)));
        },

        /**
         * 1 / x
         * @param {JXG.Math.Interval|Number} x
         * @returns JXG.Math.Interval
         */
        multiplicativeInverse: function(x) {
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (this.isEmpty(x)) {
                return this.EMPTY.clone();
            }
            if (this.zeroIn(x)) {
                if (x.lo !== 0) {
                    if (x.hi !== 0) {
                        // [negative, positive]
                        return this.WHOLE;
                    }
                    // [negative, zero]
                    return new MatInterval(Number.NEGATIVE_INFINITY, this.divHi(1, x.lo));
                }
                if (x.hi !== 0) {
                    // [zero, positive]
                    return new MatInterval(this.divLo(1, x.hi), Number.POSITIVE_INFINITY);
                }
                // [zero, zero]
                return this.EMPTY.clone();
            }
            // [positive, positive]
            return new MatInterval(this.divLo(1, x.hi), this.divHi(1, x.lo));
        },

        /**
         * x<sup>power</sup>
         * @param {JXG.Math.Interval|Number} x
         * @param {JXG.Math.Interval|Number} power
         * @returns JXG.Math.Interval
         */
        pow: function(x, power) {
            var yl, yh;

            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (this.isEmpty(x)) {
                return this.EMPTY.clone();
            }
            if (this.isInterval(power)) {
                if (!this.isSingleton(power)) {
                    return this.EMPTY.clone();
                }
                power = power.lo;
            }

            if (power === 0) {
                if (x.lo === 0 && x.hi === 0) {
                    // 0^0
                    return this.EMPTY.clone();
                }
                // x^0
                return this.ONE.clone();
            }
            if (power < 0) {
                // compute [1 / x]^-power if power is negative
                return this.pow(this.multiplicativeInverse(x), -power);
            }

            // power > 0
            if (power % 1 === 0) { // isSafeInteger(power) as boolean) {
                // power is integer
                if (x.hi < 0) {
                    // [negative, negative]
                    // assume that power is even so the operation will yield a positive interval
                    // if not then just switch the sign and order of the interval bounds
                    yl = this.powLo(-x.hi, power);
                    yh = this.powHi(-x.lo, power);
                    if ((power & 1) === 1) {
                        // odd power
                        return new MatInterval(-yh, -yl);
                    }
                    // even power
                    return new MatInterval(yl, yh);
                }
                if (x.lo < 0) {
                    // [negative, positive]
                    if ((power & 1) === 1) {
                        return new MatInterval(-this.powLo(-x.lo, power), this.powHi(x.hi, power));
                    }
                    // even power means that any negative number will be zero (min value = 0)
                    // and the max value will be the max of x.lo^power, x.hi^power
                    return new MatInterval(0, this.powHi(Math.max(-x.lo, x.hi), power));
                }
                // [positive, positive]
                return new MatInterval(this.powLo(x.lo, power), this.powHi(x.hi, power));
            }
            console.warn('power is not an integer, you should use nth-root instead, returning an empty interval');
            return this.EMPTY.clone();
        },

        /**
         * sqrt(x)
         * @param {JXG.Math.Interval|Number} x
         * @returns JXG.Math.Interval
         */
         sqrt: function(x) {
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            return this.nthRoot(x, 2);
        },

        /**
         * x<sup>1/n</sup>
         * @param {JXG.Math.Interval|Number} x
         * @param {Number} n
         * @returns JXG.Math.Interval
         */
        nthRoot: function(x, n) {
            var power,yl, yh, yp, yn;

            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (this.isEmpty(x) || n < 0) {
              // compute 1 / x^-power if power is negative
              return this.EMPTY.clone();
            }

            // singleton interval check
            if (this.isInterval(n)) {
                if (!this.isSingleton(n)) {
                    return this.EMPTY.clone();
                }
                n = n.lo;
            }

            power = 1 / n;
            if (x.hi < 0) {
                // [negative, negative]
                //if ((isSafeInteger(n) as boolean) && (n & 1) === 1) {
                if (n % 1 === 0 && (n & 1) === 1) {
                    // when n is odd we can always take the nth root
                    yl = this.powHi(-x.lo, power);
                    yh = this.powLo(-x.hi, power);
                    return new MatInterval(-yl, -yh);
                }

                // n is not odd therefore there's no nth root
                return this.EMPTY.clone();
            }
            if (x.lo < 0) {
                // [negative, positive]
                yp = this.powHi(x.hi, power);
                // if ((isSafeInteger(n) as boolean) && (n & 1) === 1) {
                if (n % 1 === 0 && (n & 1) === 1) {
                    // nth root of x.lo is possible (n is odd)
                    yn = -this.powHi(-x.lo, power);
                    return new MatInterval(yn, yp);
                }
                return new MatInterval(0, yp);
            }
            // [positive, positive]
            return new MatInterval(this.powLo(x.lo, power), this.powHi(x.hi, power));
        },

        /*
         * Misc
         */
        /**
         *
         * @param {JXG.Math.Interval|Number} x
         * @returns JXG.Math.Interval
         */
        exp: function(x) {
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (this.isEmpty(x)) {
                return this.EMPTY.clone();
            }
            return new MatInterval(this.expLo(x.lo), this.expHi(x.hi));
        },

        /**
         * Natural log
         * @param {JXG.Math.Interval|Number} x
         * @returns JXG.Math.Interval
         */
        log: function(x) {
            var l;
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (this.isEmpty(x)) {
                return this.EMPTY.clone();
            }
            l = x.lo <= 0 ? Number.NEGATIVE_INFINITY : this.logLo(x.lo);
            return new MatInterval(l, this.logHi(x.hi));
        },

        /**
         * Natural log, alias for {@link JXG.Math.IntervalArithmetic#log}.
         * @param {JXG.Math.Interval|Number} x
         * @returns JXG.Math.Interval
         */
        ln: function(x) {
            return this.log(x);
        },

        // export const LOG_EXP_10 = this.log(new MatInterval(10, 10))
        // export const LOG_EXP_2 = log(new MatInterval(2, 2))
        /**
         * Logarithm to base 10.
         * @param {JXG.Math.Interval|Number} x
         * @returns JXG.Math.Interval
         */
        log10: function(x) {
            if (this.isEmpty(x)) {
                return this.EMPTY.clone();
            }
            return this.div(this.log(x), this.log(new MatInterval(10, 10)));
        },

        /**
         * Logarithm to base 2.
         * @param {JXG.Math.Interval|Number} x
         * @returns JXG.Math.Interval
         */
        log2: function(x) {
            if (this.isEmpty(x)) {
                return this.EMPTY.clone();
            }
            return this.div(this.log(x), this.log(new MatInterval(2, 2)));
        },

        /**
         * Hull of intervals x and y
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns JXG.Math.Interval
         */
        hull: function(x, y) {
            var badX = this.isEmpty(x),
                badY = this.isEmpty(y);
            if (badX && badY) {
                return this.EMPTY.clone();
            }
            if (badX) {
                return y.clone();
            }
            if (badY) {
                return x.clone();
            }
            return new MatInterval(Math.min(x.lo, y.lo), Math.max(x.hi, y.hi));
        },

        /**
         * Intersection of intervals x and y
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns JXG.Math.Interval
         */
        intersection: function(x, y) {
            var lo, hi;
            if (this.isEmpty(x) || this.isEmpty(y)) {
                return this.EMPTY.clone();
            }
            lo = Math.max(x.lo, y.lo);
            hi = Math.min(x.hi, y.hi);
            if (lo <= hi) {
                return new MatInterval(lo, hi);
            }
            return this.EMPTY.clone();
        },

        /**
         * Union of overlapping intervals x and y
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns JXG.Math.Interval
         */
        union: function(x, y) {
            if (!this.intervalsOverlap(x, y)) {
                throw new Error('Interval#unions do not overlap');
            }
            return new MatInterval(Math.min(x.lo, y.lo), Math.max(x.hi, y.hi));
        },

        /**
         * Difference of overlapping intervals x and y
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns JXG.Math.Interval
         */
        difference: function(x, y) {
            if (this.isEmpty(x) || this.isWhole(y)) {
              return this.EMPTY.clone();
            }
            if (this.intervalsOverlap(x, y)) {
                if (x.lo < y.lo && y.hi < x.hi) {
                    // difference creates multiple subsets
                    throw new Error('Interval.difference: difference creates multiple intervals');
                }

                // handle corner cases first
                if ((y.lo <= x.lo && y.hi === Infinity) || (y.hi >= x.hi && y.lo === -Infinity)) {
                    return this.EMPTY.clone();
                }

                // NOTE: empty interval is handled automatically
                // e.g.
                //
                //    n = difference([0,1], [0,1]) // n = Interval(next(1), 1) = EMPTY
                //    isEmpty(n) === true
                //
                if (y.lo <= x.lo) {
                    return new MatInterval().halfOpenLeft(y.hi, x.hi);
                }

                // y.hi >= x.hi
                return new MatInterval().halfOpenRight(x.lo, y.lo);
            }
            return x.clone();
        },

        /**
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
        width: function(x) {
            if (this.isEmpty(x)) {
              return 0;
            }
            return this.subHi(x.hi, x.lo);
        },

        /**
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
        abs: function(x) {
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (this.isEmpty(x)) {
                return this.EMPTY.clone();
            }
            if (x.lo >= 0) {
                return x.clone();
            }
            if (x.hi <= 0) {
                return this.negative(x);
            }
            return new MatInterval(0, Math.max(-x.lo, x.hi));
        },

        /**
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns JXG.Math.Interval
         */
        max: function(x, y) {
            var badX = this.isEmpty(x),
                badY = this.isEmpty(y);
            if (badX && badY) {
                return this.EMPTY.clone();
            }
            if (badX) {
                return y.clone();
            }
            if (badY) {
                return x.clone();
            }
            return new MatInterval(Math.max(x.lo, y.lo), Math.max(x.hi, y.hi));
        },

        /**
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns JXG.Math.Interval
         */
        min: function(x, y) {
            var badX = this.isEmpty(x),
                badY = this.isEmpty(y);
            if (badX && badY) {
                return this.EMPTY.clone();
            }
            if (badX) {
                return y.clone();
            }
            if (badY) {
                return x.clone();
            }
            return new MatInterval(Math.min(x.lo, y.lo), Math.min(x.hi, y.hi));
        },

        /*
         * Trigonometric
         */
        onlyInfinity: function(x) {
            return !isFinite(x.lo) && x.lo === x.hi;
        },

        _handleNegative: function(interval) {
            var n;
            if (interval.lo < 0) {
                if (interval.lo === -Infinity) {
                    interval.lo = 0;
                    interval.hi = Infinity;
                } else {
                    n = Math.ceil(-interval.lo / this.piTwiceLow);
                    interval.lo += this.piTwiceLow * n;
                    interval.hi += this.piTwiceLow * n;
                }
            }
            return interval;
        },

        /**
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
        cos: function(x) {
            var cache, pi2, t, cosv,
                lo, hi, rlo, rhi;

            if (this.isEmpty(x) || this.onlyInfinity(x)) {
                return this.EMPTY.clone();
            }

            // create a clone of `x` because the clone is going to be modified
            cache = new MatInterval().set(x.lo, x.hi);
            this._handleNegative(cache);

            pi2 = this.PI_TWICE;
            t = this.fmod(cache, pi2);
            if (this.width(t) >= pi2.lo) {
                return new MatInterval(-1, 1);
            }

            // when t.lo > pi it's the same as
            // -cos(t - pi)
            if (t.lo >= this.piHigh) {
                cosv = this.cos(this.sub(t, this.PI));
                return this.negative(cosv);
            }

            lo = t.lo;
            hi = t.hi;
            rlo = this.cosLo(hi);
            rhi = this.cosHi(lo);
            // it's ensured that t.lo < pi and that t.lo >= 0
            if (hi <= this.piLow) {
                // when t.hi < pi
                // [cos(t.lo), cos(t.hi)]
                return new MatInterval(rlo, rhi);
            }
            if (hi <= pi2.lo) {
                // when t.hi < 2pi
                // [-1, max(cos(t.lo), cos(t.hi))]
                return new MatInterval(-1, Math.max(rlo, rhi));
            }
            // t.lo < pi and t.hi > 2pi
            return new MatInterval(-1, 1);
        },

        /**
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
        sin: function(x) {
            if (this.isEmpty(x) || this.onlyInfinity(x)) {
                return this.EMPTY.clone();
            }
            return this.cos(this.sub(x, this.PI_HALF));
        },

        /**
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
        tan: function(x) {
            var cache, t, pi;
            if (this.isEmpty(x) || this.onlyInfinity(x)) {
                return this.EMPTY.clone();
            }

            // create a clone of `x` because the clone is going to be modified
            cache = new MatInterval().set(x.lo, x.hi);
            this._handleNegative(cache);

            pi = this.PI;
            t = this.fmod(cache, pi);
            if (t.lo >= this.piHalfLow) {
                t = this.sub(t, pi);
            }
            if (t.lo <= -this.piHalfLow || t.hi >= this.piHalfLow) {
                return this.WHOLE.clone();
            }
            return new MatInterval(this.tanLo(t.lo), this.tanHi(t.hi));
        },

        /**
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
        asin: function(x) {
            var lo, hi;
            if (this.isEmpty(x) || x.hi < -1 || x.lo > 1) {
                return this.EMPTY.clone();
            }
            lo = x.lo <= -1 ? -this.piHalfHigh : this.asinLo(x.lo);
            hi = x.hi >= 1 ? this.piHalfHigh : this.asinHi(x.hi);
            return new MatInterval(lo, hi);
        },

        /**
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
        acos: function(x) {
            var lo, hi;
            if (this.isEmpty(x) || x.hi < -1 || x.lo > 1) {
                  return this.EMPTY.clone();
            }
            lo = x.hi >= 1 ? 0 : this.acosLo(x.hi);
            hi = x.lo <= -1 ? this.piHigh : this.acosHi(x.lo);
            return new MatInterval(lo, hi);
        },

        /**
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
        atan: function(x) {
            if (this.isEmpty(x)) {
                return this.EMPTY.clone();
            }
            return new MatInterval(this.atanLo(x.lo), this.atanHi(x.hi));
        },

        /**
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
        sinh: function(x) {
            if (this.isEmpty(x)) {
                return this.EMPTY.clone();
            }
            return new MatInterval(this.sinhLo(x.lo), this.sinhHi(x.hi));
        },

        /**
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
        cosh: function(x) {
            if (this.isEmpty(x)) {
              return this.EMPTY.clone();
            }
            if (x.hi < 0) {
                return new MatInterval(this.coshLo(x.hi), this.coshHi(x.lo));
            }
            if (x.lo >= 0) {
                return new MatInterval(this.coshLo(x.lo), this.coshHi(x.hi));
            }
            return new MatInterval(1, this.coshHi(-x.lo > x.hi ? x.lo : x.hi));
        },

        /**
         * @param {JXG.Math.Interval} x
         * @returns JXG.Math.Interval
         */
        tanh: function(x) {
            if (this.isEmpty(x)) {
                return this.EMPTY.clone();
            }
            return new MatInterval(this.tanhLo(x.lo), this.tanhHi(x.hi));
        },

        /*
         * Relational
         */

        /**
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns Boolean
         */
        equal: function(x, y) {
            if (this.isEmpty(x)) {
                return this.isEmpty(y);
            }
            return !this.isEmpty(y) && x.lo === y.lo && x.hi === y.hi;
        },

        // almostEqual: function(x, y): void {
        //     x = Array.isArray(x) ? x : x.toArray();
        //     y = Array.isArray(y) ? y : y.toArray();
        //     assertEps(x[0], y[0])
        //     assertEps(x[1], y[1])
        // },

        /**
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns Boolean
         */
        notEqual: function(x, y) {
            if (this.isEmpty(x)) {
                return !this.isEmpty(y);
            }
            return this.isEmpty(y) || x.hi < y.lo || x.lo > y.hi;
        },

        /**
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns Boolean
         */
        lt: function(x, y) {
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (Type.isNumber(y)) {
                y = this.Interval(y);
            }
            if (this.isEmpty(x) || this.isEmpty(y)) {
                return false;
            }
            return x.hi < y.lo;
        },

        /**
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns Boolean
         */
        gt: function(x, y) {
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (Type.isNumber(y)) {
                y = this.Interval(y);
            }
            if (this.isEmpty(x) || this.isEmpty(y)) {
                return false;
            }
            return x.lo > y.hi;
        },

        /**
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns Boolean
         */
        leq: function(x, y) {
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (Type.isNumber(y)) {
                y = this.Interval(y);
            }
            if (this.isEmpty(x) || this.isEmpty(y)) {
                return false;
            }
            return x.hi <= y.lo;
        },

        /**
         * @param {JXG.Math.Interval} x
         * @param {JXG.Math.Interval} y
         * @returns Boolean
         */
        geq: function(x, y) {
            if (Type.isNumber(x)) {
                x = this.Interval(x);
            }
            if (Type.isNumber(y)) {
                y = this.Interval(y);
            }
            if (this.isEmpty(x) || this.isEmpty(y)) {
                return false;
            }
            return x.lo >= y.hi;
        },

        /*
         * Constants
         */
        piLow: (3373259426.0 + 273688.0 / (1 << 21)) / (1 << 30),
        piHigh: (3373259426.0 + 273689.0 / (1 << 21)) / (1 << 30),
        piHalfLow: (3373259426.0 + 273688.0 / (1 << 21)) / (1 << 30) * 0.5,
        piHalfHigh: (3373259426.0 + 273689.0 / (1 << 21)) / (1 << 30) * 0.5,
        piTwiceLow: (3373259426.0 + 273688.0 / (1 << 21)) / (1 << 30) * 2,
        piTwiceHigh: (3373259426.0 + 273689.0 / (1 << 21)) / (1 << 30) * 2,

        /*
         * Round
         * Rounding functions for numbers
         */
        identity: function(v) {
            return v;
        },

        _prev: function(v) {
            if (v === Infinity) {
              return v;
            }
            return this.nextafter(v, -Infinity);
        },

        _next: function(v) {
            if (v === -Infinity) {
              return v;
            }
            return this.nextafter(v, Infinity);
        },

        prev: function(v) {
            return this._prev(v);
        },

        next: function(v) {
            return this._next(v);
        },

        toInteger: function(x) {
            return x < 0 ? Math.ceil(x) : Math.floor(x);
        },

        addLo: function(x, y) { return this.prev(x + y); },
        addHi: function(x, y) { return this.next(x + y); },
        subLo: function(x, y) { return this.prev(x - y); },
        subHi: function(x, y) { return this.next(x - y); },
        mulLo: function(x, y) { return this.prev(x * y); },
        mulHi: function(x, y) { return this.next(x * y); },
        divLo: function(x, y) { return this.prev(x / y); },
        divHi: function(x, y) { return this.next(x / y); },
        intLo: function(x) { return this.toInteger(this.prev(x)); },
        intHi: function(x) { return this.toInteger(this.next(x)); },
        logLo: function(x) { return this.prev(Math.log(x)); },
        logHi: function(x) { return this.next(Math.log(x)); },
        expLo: function(x) { return this.prev(Math.exp(x)); },
        expHi: function(x) { return this.next(Math.exp(x)); },
        sinLo: function(x) { return this.prev(Math.sin(x)); },
        sinHi: function(x) { return this.next(Math.sin(x)); },
        cosLo: function(x) { return this.prev(Math.cos(x)); },
        cosHi: function(x) { return this.next(Math.cos(x)); },
        tanLo: function(x) { return this.prev(Math.tan(x)); },
        tanHi: function(x) { return this.next(Math.tan(x)); },
        asinLo: function(x) { return this.prev(Math.asin(x)); },
        asinHi: function(x) { return this.next(Math.asin(x)); },
        acosLo: function(x) { return this.prev(Math.acos(x)); },
        acosHi: function(x) { return this.next(Math.acos(x)); },
        atanLo: function(x) { return this.prev(Math.atan(x)); },
        atanHi: function(x) { return this.next(Math.atan(x)); },
        sinhLo: function(x) { return this.prev(Mat.sinh(x)); },
        sinhHi: function(x) { return this.next(Mat.sinh(x)); },
        coshLo: function(x) { return this.prev(Mat.cosh(x)); },
        coshHi: function(x) { return this.next(Mat.cosh(x)); },
        tanhLo: function(x) { return this.prev(Mat.tanh(x)); },
        tanhHi: function(x) { return this.next(Mat.tanh(x)); },
        sqrtLo: function(x) { return this.prev(Math.sqrt(x)); },
        sqrtHi: function(x) { return this.next(Math.sqrt(x)); },

        powLo: function(x, power) {
            var y;
            if (power % 1 !== 0) {
                // power has decimals
                return this.prev(Math.pow(x, power));
            }

            y = (power & 1) === 1 ? x : 1;
            power >>= 1;
            while (power > 0) {
                x = this.mulLo(x, x);
                if ((power & 1) === 1) {
                    y = this.mulLo(x, y);
                }
                power >>= 1;
            }
            return y;
        },

        powHi: function(x, power) {
            var y;
            if (power % 1 !== 0) {
                // power has decimals
                return this.next(Math.pow(x, power));
            }

            y = (power & 1) === 1 ? x : 1;
            power >>= 1;
            while (power > 0) {
                x = this.mulHi(x, x);
                if ((power & 1) === 1) {
                    y = this.mulHi(x, y);
                }
                power >>= 1;
            }
            return y;
        },

        /**
         * @ignore
         * @private
         */
        disable: function() {
            this.next = this.prev = this.identity;
        },

        /**
         * @ignore
         * @private
         */
         enable: function() {
            this.prev = function(v) {
                return this._prev(v);
            };

            this.next = function(v) {
                return this._next(v);
            };
        },


        /*
         * nextafter
         */
        SMALLEST_DENORM: Math.pow(2, -1074),
        UINT_MAX: (-1)>>>0,

        nextafter: function(x, y) {
            var lo, hi;

            if (isNaN(x) || isNaN(y)) {
                return NaN;
            }
            if (x === y) {
                return x;
            }
            if (x === 0) {
                if (y < 0) {
                    return -this.SMALLEST_DENORM;
                }
                return this.SMALLEST_DENORM;
            }
            hi = doubleBits.hi(x);
            lo = doubleBits.lo(x);
            if ((y > x) === (x > 0)) {
                if (lo === this.UINT_MAX) {
                    hi += 1;
                    lo = 0;
                } else {
                  lo += 1;
                }
            } else {
                if (lo === 0) {
                    lo = this.UINT_MAX;
                    hi -= 1;
                } else {
                    lo -= 1;
                }
            }
            return doubleBits.pack(lo, hi);
        }

    };

    JXG.Math.IntervalArithmetic.PI       = new MatInterval(Mat.IntervalArithmetic.piLow, Mat.IntervalArithmetic.piHigh);
    JXG.Math.IntervalArithmetic.PI_HALF  = new MatInterval(Mat.IntervalArithmetic.piHalfLow, Mat.IntervalArithmetic.piHalfHigh);
    JXG.Math.IntervalArithmetic.PI_TWICE = new MatInterval(Mat.IntervalArithmetic.piTwiceLow, Mat.IntervalArithmetic.piTwiceHigh);
    JXG.Math.IntervalArithmetic.ZERO     = new MatInterval(0);
    JXG.Math.IntervalArithmetic.ONE      = new MatInterval(1);
    JXG.Math.IntervalArithmetic.WHOLE    = new MatInterval().setWhole();
    JXG.Math.IntervalArithmetic.EMPTY    = new MatInterval().setEmpty();

    return JXG.Math.IntervalArithmetic;
});



/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Alfred Wassermann

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/math
 utils/type
 */

define('math/extrapolate',['math/math'], function (Mat) {

    "use strict";

    /**
     * Functions for extrapolation of sequences. Used for finding limits of sequences which is used for curve plotting.
     * @name JXG.Math.Extrapolate
     * @exports Mat.Extrapolate as JXG.Math.Extrapolate
     * @namespace
     */
    Mat.Extrapolate = {
        upper: 15,
        infty: 1.e+4,

        /**
         * Wynn's epsilon algorithm. Ported from the FORTRAN version in
         * Ernst Joachim Weniger, "Nonlinear sequence transformations for the acceleration of convergence
         * and the summation of divergent series", Computer Physics Reports Vol. 10, 189-371 (1989).
         *
         * @param {Number} s_n next value of sequence, i.e. n-th element of sequence
         * @param {Number} n index of s_n in the sequence
         * @param {Array} e One-dimensional array containing the extrapolation data. Has to be supplied by the calling routine.
         * @returns {Number} New estimate of the limit of the sequence.
         *
         * @memberof JXG.Math.Extrapolate
         */
        wynnEps: function(s_n, n, e) {
            var HUGE = 1.e+20,
                TINY = 1.e-15,
                f0 = 1, // f0 may be changed to other values, see vanden Broeck, Schwartz (1979)
                f, j, aux1, aux2, diff, estlim;

            e[n] = s_n;
            if (n === 0) {
                estlim = s_n;
            } else {
                aux2 = 0.0;
                for (j = n; j > 0; j--) {
                    aux1 = aux2;
                    aux2 = e[j - 1];
                    diff = e[j] - aux2;
                    if (Math.abs(diff) <= TINY) {
                        e[j - 1] = HUGE;
                    } else {
                        f = ((n - j + 1) % 2 === 1) ? f0 : 1;
                        e[j - 1] = aux1 * f + 1 / diff;
                    }
                }
                estlim = e[n % 2];
            }

            return estlim;
        },

        // wynnRho: function(s_n, n, e) {
        //     var HUGE = 1.e+20,
        //         TINY = 1.e-15,
        //         j, f,
        //         aux1, aux2, diff, estlim;

        //     e[n] = s_n;
        //     if (n === 0) {
        //         estlim = s_n;
        //     } else {
        //         aux2 = 0.0;
        //         for (j = n; j >= 1; j--) {
        //             aux1 = aux2;
        //             aux2 = e[j - 1];
        //             diff = e[j] - aux2;
        //             if (Math.abs(diff) <= TINY) {
        //                 e[j - 1] = HUGE;
        //             } else {
        //                 f = ((n - j + 1) % 2 === 1) ? n - j + 1  : 1;
        //                 e[j - 1] = aux1 + f / diff;
        //             }
        //         }
        //         estlim = e[n % 2];
        //     }

        //     return estlim;
        // },

        /**
         * Aitken transformation. Ported from the FORTRAN version in
         * Ernst Joachim Weniger, "Nonlinear sequence transformations for the acceleration of convergence
         * and the summation of divergent series", Computer Physics Reports Vol. 10, 189-371 (1989).
         *
         * @param {Number} s_n next value of sequence, i.e. n-th element of sequence
         * @param {Number} n index of s_n in the sequence
         * @param {Array} a One-dimensional array containing the extrapolation data. Has to be supplied by the calling routine.
         * @returns {Number} New estimate of the limit of the sequence.
         *
         * @memberof JXG.Math.Extrapolate
         */
        aitken: function(s_n, n, a) {
            var estlim,
                HUGE = 1.e+20,
                TINY = 1.e-15,
                denom, v,
                lowmax, j, m;

            a[n] = s_n;
            if (n < 2) {
                estlim = s_n;
            } else {
                lowmax = n / 2;
                for (j = 1; j <= lowmax; j++) {
                    m = n - 2 * j;
                    denom = a[m + 2] - 2 * a[m + 1] + a[m];
                    if (Math.abs(denom) < TINY) {
                         a[m] = HUGE;
                    } else {
                        v = a[m] - a[m + 1];
                        a[m] -= v * v / denom;
                    }
                }
                estlim = a[n % 2];
            }
            return estlim;
        },

        /**
         * Iterated Brezinski transformation. Ported from the FORTRAN version in
         * Ernst Joachim Weniger, "Nonlinear sequence transformations for the acceleration of convergence
         * and the summation of divergent series", Computer Physics Reports Vol. 10, 189-371 (1989).
         *
         * @param {Number} s_n next value of sequence, i.e. n-th element of sequence
         * @param {Number} n index of s_n in the sequence
         * @param {Array} a One-dimensional array containing the extrapolation data. Has to be supplied by the calling routine.
         * @returns {Number} New estimate of the limit of the sequence.
         *
         * @memberof JXG.Math.Extrapolate
         */
        brezinski: function(s_n, n, a) {
            var estlim,
                HUGE = 1.e+20,
                TINY = 1.e-15,
                denom,
                d0, d1, d2,
                lowmax, j, m;

            a[n] = s_n;
            if (n < 3) {
                estlim = s_n;
            } else {
                lowmax = n / 3;
                m = n;
                for (j = 1; j <= lowmax; j++) {
                    m -= 3;
                    d0 = a[m + 1] - a[m];
                    d1 = a[m + 2] - a[m + 1];
                    d2 = a[m + 3] - a[m + 2];
                    denom = d2 * (d1 - d0) - d0 * (d2 - d1);
                    if (Math.abs(denom) < TINY) {
                        a[m] = HUGE;
                    } else {
                        a[m] = a[m + 1] - d0 * d1 * (d2 - d1) / denom;
                    }
                }
                estlim = a[n % 3];
            }
            return estlim;
        },

        /**
         * Extrapolated iteration to approximate the value f(x_0).
         *
         * @param {Number} x0 Value for which the limit of f is to be determined. f(x0) may or may not exist.
         * @param {Number} h0 Initial (signed) distance from x0.
         * @param {Function} f Function for which the limit at x0 is to be determined
         * @param {String} method String to choose the method. Available values: "wynnEps", "aitken", "brezinski"
         * @param {Number} step_type Approximation method. step_type = 0 uses the sequence x0 + h0/n; step_type = 1 uses the sequence x0 + h0 * 2^(-n)
         *
         * @returns {Array} Array of length 3. Position 0: estimated value for f(x0), position 1: 'finite', 'infinite', or 'NaN'.
         * Position 2: value between 0 and 1 judging the reliability of the result (1: high, 0: not successful).
         *
         * @memberof JXG.Math.Extrapolate
         * @see JXG.Math.Extrapolate.limit
         * @see JXG.Math.Extrapolate.wynnEps
         * @see JXG.Math.Extrapolate.aitken
         * @see JXG.Math.Extrapolate.brezinski
         */
        iteration: function(x0, h0, f, method, step_type) {
            var n, v, w,
                estlim = NaN,
                diff,
                r = 0.5,
                E = [],
                result = 'finite',
                h = h0;

            step_type = step_type || 0;

            for (n = 1; n <= this.upper; n++) {
                h = (step_type === 0) ?  h0 / (n + 1) : h * r;
                v = f(x0 + h, true);

                w = this[method](v, n - 1, E);
//console.log(n, x0 + h, v, w);
                if (isNaN(w)) {
                    result = 'NaN';
                    break;
                }
                if (v !== 0 && w / v > this.infty) {
                    estlim = w;
                    result = 'infinite';
                    break;
                }
                diff = w - estlim;
                if (Math.abs(diff) < 1.e-7) {
                    break;
                }
                estlim = w;
            }
            return [estlim, result, 1 - (n - 1) / this.upper];
        },

        /**
         * Levin transformation. See Numerical Recipes, ed. 3.
         * Not yet ready for use.
         *
         * @param {Number} s_n next value of sequence, i.e. n-th element of sequence
         * @param {Number} n index of s_n in the sequence
         * @param {Array} numer One-dimensional array containing the extrapolation data for the numerator. Has to be supplied by the calling routine.
         * @param {Array} denom One-dimensional array containing the extrapolation data for the denominator. Has to be supplied by the calling routine.
         *
         * @memberof JXG.Math.Extrapolate
        */
        levin: function(s_n, n, omega, beta, numer, denom) {
            var HUGE = 1.e+20,
                TINY = 1.e-15,
                j,
                fact, ratio, term, estlim;

            term = 1.0 / (beta + n);
            numer[n] = s_n / omega;
            denom[n] = 1 / omega;
            if (n > 0) {
                numer[n - 1] = numer[n] - numer[n - 1];
                denom[n - 1] = denom[n] - denom[n - 1];
                if (n > 1) {
                    ratio = (beta + n - 1) * term;
                    for (j = 2; j <= n; j++) {
                        fact = (beta + n - j) * Math.pow(ratio, j - 2) * term;
                        numer[n - j] = numer[n - j + 1] - fact * numer[n - j];
                        denom[n - j] = denom[n - j + 1] - fact * denom[n - j];
                        term *= ratio;
                    }
                }
            }
            if (Math.abs(denom[0]) < TINY) {
                estlim = HUGE;
            } else {
                estlim = numer[0] / denom[0];
            }
            return estlim;
        },

        iteration_levin: function(x0, h0, f, step_type) {
            var n, v, w,
                estlim = NaN,
                v_prev,
                delta, diff, omega,
                beta = 1,
                r = 0.5,
                numer = [],
                denom = [],
                result = 'finite',
                h = h0, transform = 'u';

            step_type = step_type || 0;

            v_prev = f(x0 + h0, true);
            for (n = 1; n <= this.upper; n++) {
                h = (step_type === 0) ?  h0 / (n + 1) : h * r;
                v = f(x0 + h, true);
                delta = v - v_prev;
                if (Math.abs(delta) < 1) {
                    transform = 'u';
                } else {
                    transform = 't';
                }
                if (transform === 'u') {
                    omega = (beta + n) * delta; // u transformation
                } else {
                    omega = delta;              // t transformation
                }

                v_prev = v;
                w = this.levin(v, n - 1, omega, beta, numer, denom);
                diff = w - estlim;
// console.log(n, delta, transform, x0 + h, v, w, diff);

                if (isNaN(w)) {
                    result = 'NaN';
                    break;
                }
                if (v !== 0 && w / v > this.infty) {
                    estlim = w;
                    result = 'infinite';
                    break;
                }
                if (Math.abs(diff) < 1.e-7) {
                    break;
                }
                estlim = w;
            }
            return [estlim, result, 1 - (n - 1) / this.upper];
        },

        /**
         *
         * @param {Number} x0 Value for which the limit of f is to be determined. f(x0) may or may not exist.
         * @param {Number} h0 Initial (signed) distance from x0.
         * @param {Function} f Function for which the limit at x0 is to be determined
         *
         * @returns {Array} Array of length 3. Position 0: estimated value for f(x0), position 1: 'finite', 'infinite', or 'NaN'.
         * Position 2: value between 0 and 1 judging the reliability of the result (1: high, 0: not successful).
         * In case that the extrapolation fails, position 1 and 2 contain 'direct' and 0.
         *
         * @example
         * var f1 = (x) => Math.log(x),
         *     f2 = (x) => Math.tan(x - Math.PI * 0.5),
         *     f3 = (x) => 4 / x;
         *
         * var x0 = 0.0000001;
         * var h = 0.1;
         * for (let f of [f1, f2, f3]) {
         *     console.log("x0=", x0, f.toString());
         *     console.log(JXG.Math.Extrapolate.limit(x0, h, f));
         *  }
         *
         * </pre><div id="JXG5e8c6a7e-eeae-43fb-a669-26b5c9e40cab" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG5e8c6a7e-eeae-43fb-a669-26b5c9e40cab',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var f1 = (x) => Math.log(x),
         *         f2 = (x) => Math.tan(x - Math.PI * 0.5),
         *         f3 = (x) => 4 / x;
         *
         *     var x0 = 0.0000001;
         *     var h = 0.1;
         *     for (let f of [f1, f2, f3]) {
         *         console.log("x0=", x0, f.toString());
         *         console.log(JXG.Math.Extrapolate.limit(x0, h, f));
         *      }
         * 
         *     })();
         * 
         * </script><pre>
         *
         *
         * @see JXG.Math.Extrapolate.iteration
         * @memberof JXG.Math.Extrapolate
         */
        limit: function(x0, h0, f) {
            return this.iteration_levin(x0, h0, f, 0);
            //return this.iteration(x0, h0, f, 'wynnEps', 1);

            // var algs = ['wynnEps', 'levin'], //, 'wynnEps', 'levin', 'aitken', 'brezinski'],
            //     le = algs.length,
            //     i, t, res;
            // for (i = 0; i < le; i++) {
            //     for (t = 0; t < 1; t++) {
            //         if (algs[i] === 'levin') {
            //             res = this.iteration_levin(x0, h0, f, t);
            //         } else {
            //             res = this.iteration(x0, h0, f, algs[i], t);
            //         }
            //         if (res[2] > 0.6) {
            //             return res;
            //         }
            //         console.log(algs[i], t, res)
            //     }
            // }
            // return [f(x0 + Math.sign(h0) * Math.sqrt(Mat.eps)), 'direct', 0];
        }
    };

    return Mat.Extrapolate;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG:true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 math/math
 utils/type
 */

define('math/qdt',['math/math', 'utils/type'], function (Mat, Type) {

    "use strict";

    /**
     * Instantiate a new quad tree.
     *
     * @name JXG.Math.Quadtree
     * @exports Mat.Quadtree as JXG.Math.Quadtree
     * @param {Array} bbox Bounding box of the new quad (sub)tree.
     * @constructor
     */
    Mat.Quadtree = function (bbox) {
        /**
         * The maximum number of points stored in a quad tree node
         * before it is subdivided.
         * @type Number
         * @default 10
         */
        this.capacity = 10;

        /**
         * Point storage.
         * @name JXG.Math.Quadtree#points
         * @type Array
         */
        this.points = [];
        this.xlb = bbox[0];
        this.xub = bbox[2];
        this.ylb = bbox[3];
        this.yub = bbox[1];

        /**
         * In a subdivided quad tree this represents the top left subtree.
         * @name JXG.Math.Quadtree#northWest
         * @type JXG.Math.Quadtree
         */
        this.northWest = null;

        /**
         * In a subdivided quad tree this represents the top right subtree.
         * @name JXG.Math.Quadtree#northEast
         * @type JXG.Math.Quadtree
         */
        this.northEast = null;

        /**
         * In a subdivided quad tree this represents the bottom right subtree.
         * @name JXG.Math.Quadtree#southEast
         * @type JXG.Math.Quadtree
         */
        this.southEast = null;

        /**
         * In a subdivided quad tree this represents the bottom left subtree.
         * @name JXG.Math.Quadtree#southWest
         * @type JXG.Math.Quadtree
         */
        this.southWest = null;
    };

    Type.extend(Mat.Quadtree.prototype, /** @lends JXG.Math.Quadtree.prototype */ {
        /**
         * Checks if the given coordinates are inside the quad tree.
         * @param {Number} x
         * @param {Number} y
         * @returns {Boolean}
         */
        contains: function (x, y) {
            return this.xlb < x && x <= this.xub && this.ylb < y && y <= this.yub;
        },

        /**
         * Insert a new point into this quad tree.
         * @param {JXG.Coords} p
         * @returns {Boolean}
         */
        insert: function (p) {
            if (!this.contains(p.usrCoords[1], p.usrCoords[2])) {
                return false;
            }

            if (this.points.length < this.capacity) {
                this.points.push(p);
                return true;
            }

            if (this.northWest === null) {
                this.subdivide();
            }

            if (this.northWest.insert(p)) {
                return true;
            }

            if (this.northEast.insert(p)) {
                return true;
            }

            if (this.southEast.insert(p)) {
                return true;
            }

            return !!this.southWest.insert(p);


        },

        /**
         * Subdivide the quad tree.
         */
        subdivide: function () {
            var i,
                l = this.points.length,
                mx = this.xlb + (this.xub - this.xlb) / 2,
                my = this.ylb + (this.yub - this.ylb) / 2;

            this.northWest = new Mat.Quadtree([this.xlb, this.yub, mx, my]);
            this.northEast = new Mat.Quadtree([mx, this.yub, this.xub, my]);
            this.southEast = new Mat.Quadtree([this.xlb, my, mx, this.ylb]);
            this.southWest = new Mat.Quadtree([mx, my, this.xub, this.ylb]);

            for (i = 0; i < l; i += 1) {
                this.northWest.insert(this.points[i]);
                this.northEast.insert(this.points[i]);
                this.southEast.insert(this.points[i]);
                this.southWest.insert(this.points[i]);
            }
        },

        /**
         * Internal _query method that lacks adjustment of the parameter.
         * @name JXG.Math.Quadtree#_query
         * @param {Number} x
         * @param {Number} y
         * @returns {Boolean|JXG.Quadtree} The quad tree if the point is found, false
         * if none of the quad trees contains the point (i.e. the point is not inside
         * the root tree's AABB).
         * @private
         */
        _query: function (x, y) {
            var r;

            if (this.contains(x, y)) {
                if (this.northWest === null) {
                    return this;
                }

                r = this.northWest._query(x, y);
                if (r) {
                    return r;
                }

                r = this.northEast._query(x, y);
                if (r) {
                    return r;
                }

                r = this.southEast._query(x, y);
                if (r) {
                    return r;
                }

                r = this.southWest._query(x, y);
                if (r) {
                    return r;
                }
            }

            return false;
        },

        /**
         * Retrieve the smallest quad tree that contains the given point.
         * @name JXG.Math.Quadtree#_query
         * @param {JXG.Coords|Number} xp
         * @param {Number} y
         * @returns {Boolean|JXG.Quadtree} The quad tree if the point is found, false
         * if none of the quad trees contains the point (i.e. the point is not inside
         * the root tree's AABB).
         * @private
         */
        query: function (xp, y) {
            var _x, _y;

            if (Type.exists(y)) {
                _x = xp;
                _y = y;
            } else {
                _x = xp.usrCoords[1];
                _y = xp.usrCoords[2];
            }

            return this._query(_x, _y);
        }
    });

    return Mat.Quadtree;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */

/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/
/*eslint no-loss-of-precision: off */

/* depends:
 utils/type
 math/math
 */

/**
 * @fileoverview In this file the namespace Math.Numerics is defined, which holds numerical
 * algorithms for solving linear equations etc.
 */

define('math/numerics',['jxg', 'utils/type', 'utils/env', 'math/math'], function (JXG, Type, Env, Mat) {

    "use strict";

    // Predefined butcher tableaus for the common Runge-Kutta method (fourth order), Heun method (second order), and Euler method (first order).
    var predefinedButcher = {
        rk4: {
            s: 4,
            A: [
                [ 0,  0,  0, 0],
                [0.5, 0,  0, 0],
                [ 0, 0.5, 0, 0],
                [ 0,  0,  1, 0]
            ],
            b: [1.0 / 6.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 6.0],
            c: [0, 0.5, 0.5, 1]
        },
        heun: {
            s: 2,
            A: [
                [0, 0],
                [1, 0]
            ],
            b: [0.5, 0.5],
            c: [0, 1]
        },
        euler: {
            s: 1,
            A: [
                [0]
            ],
            b: [1],
            c: [0]
        }
    };

    /**
     * The JXG.Math.Numerics namespace holds numerical algorithms, constants, and variables.
     * @name JXG.Math.Numerics
     * @exports Mat.Numerics as JXG.Math.Numerics
     * @namespace
     */
    Mat.Numerics = {

    //JXG.extend(Mat.Numerics, /** @lends JXG.Math.Numerics */ {
        /**
         * Solves a system of linear equations given by A and b using the Gauss-Jordan-elimination.
         * The algorithm runs in-place. I.e. the entries of A and b are changed.
         * @param {Array} A Square matrix represented by an array of rows, containing the coefficients of the lineare equation system.
         * @param {Array} b A vector containing the linear equation system's right hand side.
         * @throws {Error} If a non-square-matrix is given or if b has not the right length or A's rank is not full.
         * @returns {Array} A vector that solves the linear equation system.
         * @memberof JXG.Math.Numerics
         */
        Gauss: function (A, b) {
            var i, j, k,
                // copy the matrix to prevent changes in the original
                Acopy,
                // solution vector, to prevent changing b
                x,
                eps = Mat.eps,
                // number of columns of A
                n = A.length > 0 ? A[0].length : 0;

            if ((n !== b.length) || (n !== A.length)) {
                throw new Error("JXG.Math.Numerics.Gauss: Dimensions don't match. A must be a square matrix and b must be of the same length as A.");
            }

            // initialize solution vector
            Acopy = [];
            x = b.slice(0, n);

            for (i = 0; i < n; i++) {
                Acopy[i] = A[i].slice(0, n);
            }

            // Gauss-Jordan-elimination
            for (j = 0; j < n; j++) {
                for (i = n - 1; i > j; i--) {
                    // Is the element which is to eliminate greater than zero?
                    if (Math.abs(Acopy[i][j]) > eps) {
                        // Equals pivot element zero?
                        if (Math.abs(Acopy[j][j]) < eps) {
                            // At least numerically, so we have to exchange the rows
                            Type.swap(Acopy, i, j);
                            Type.swap(x, i, j);
                        } else {
                            // Saves the L matrix of the LR-decomposition. unnecessary.
                            Acopy[i][j] /= Acopy[j][j];
                            // Transform right-hand-side b
                            x[i] -= Acopy[i][j] * x[j];

                            // subtract the multiple of A[i][j] / A[j][j] of the j-th row from the i-th.
                            for (k = j + 1; k < n; k++) {
                                Acopy[i][k] -= Acopy[i][j] * Acopy[j][k];
                            }
                        }
                    }
                }

                // The absolute values of all coefficients below the j-th row in the j-th column are smaller than JXG.Math.eps.
                if (Math.abs(Acopy[j][j]) < eps) {
                    throw new Error("JXG.Math.Numerics.Gauss(): The given matrix seems to be singular.");
                }
            }

            this.backwardSolve(Acopy, x, true);

            return x;
        },

        /**
         * Solves a system of linear equations given by the right triangular matrix R and vector b.
         * @param {Array} R Right triangular matrix represented by an array of rows. All entries a_(i,j) with i &lt; j are ignored.
         * @param {Array} b Right hand side of the linear equation system.
         * @param {Boolean} [canModify=false] If true, the right hand side vector is allowed to be changed by this method.
         * @returns {Array} An array representing a vector that solves the system of linear equations.
         * @memberof JXG.Math.Numerics
         */
        backwardSolve: function (R, b, canModify) {
            var x, m, n, i, j;

            if (canModify) {
                x = b;
            } else {
                x = b.slice(0, b.length);
            }

            // m: number of rows of R
            // n: number of columns of R
            m = R.length;
            n = R.length > 0 ? R[0].length : 0;

            for (i = m - 1; i >= 0; i--) {
                for (j = n - 1; j > i; j--) {
                    x[i] -= R[i][j] * x[j];
                }
                x[i] /= R[i][i];
            }

            return x;
        },

        /**
         * @private
         * Gauss-Bareiss algorithm to compute the
         * determinant of matrix without fractions.
         * See Henri Cohen, "A Course in Computational
         * Algebraic Number Theory (Graduate texts
         * in mathematics; 138)", Springer-Verlag,
         * ISBN 3-540-55640-0 / 0-387-55640-0
         * Third, Corrected Printing 1996
         * "Algorithm 2.2.6", pg. 52-53
         * @memberof JXG.Math.Numerics
         */
        gaussBareiss: function (mat) {
            var k, c, s, i, j, p, n, M, t,
                eps = Mat.eps;

            n = mat.length;

            if (n <= 0) {
                return 0;
            }

            if (mat[0].length < n) {
                n = mat[0].length;
            }

            // Copy the input matrix to M
            M = [];

            for (i = 0; i < n; i++) {
                M[i] = mat[i].slice(0, n);
            }

            c = 1;
            s = 1;

            for (k = 0; k < n - 1; k++) {
                p = M[k][k];

                // Pivot step
                if (Math.abs(p) < eps) {
                    for (i = k + 1; i < n; i++) {
                        if (Math.abs(M[i][k]) >= eps) {
                            break;
                        }
                    }

                    // No nonzero entry found in column k -> det(M) = 0
                    if (i === n) {
                        return 0.0;
                    }

                    // swap row i and k partially
                    for (j = k; j < n; j++) {
                        t = M[i][j];
                        M[i][j] = M[k][j];
                        M[k][j] = t;
                    }
                    s = -s;
                    p = M[k][k];
                }

                // Main step
                for (i = k + 1; i < n; i++) {
                    for (j = k + 1; j < n; j++) {
                        t = p * M[i][j] - M[i][k] * M[k][j];
                        M[i][j] = t / c;
                    }
                }

                c = p;
            }

            return s * M[n - 1][n - 1];
        },

        /**
         * Computes the determinant of a square nxn matrix with the
         * Gauss-Bareiss algorithm.
         * @param {Array} mat Matrix.
         * @returns {Number} The determinant pf the matrix mat.
         *                   The empty matrix returns 0.
         * @memberof JXG.Math.Numerics
         */
        det: function (mat) {
            var n = mat.length;

            if (n === 2 && mat[0].length === 2) {
                return mat[0][0] * mat[1][1] - mat[1][0] * mat[0][1];
            }

            return this.gaussBareiss(mat);
        },

        /**
         * Compute the Eigenvalues and Eigenvectors of a symmetric 3x3 matrix with the Jacobi method
         * Adaption of a FORTRAN program by Ed Wilson, Dec. 25, 1990
         * @param {Array} Ain A symmetric 3x3 matrix.
         * @returns {Array} [A,V] the matrices A and V. The diagonal of A contains the Eigenvalues, V contains the Eigenvectors.
         * @memberof JXG.Math.Numerics
         */
        Jacobi: function (Ain) {
            var i, j, k, aa, si, co, tt, ssum, amax,
                eps = Mat.eps * Mat.eps,
                sum = 0.0,
                n = Ain.length,
                V = [
                    [0, 0, 0],
                    [0, 0, 0],
                    [0, 0, 0]
                ],
                A = [
                    [0, 0, 0],
                    [0, 0, 0],
                    [0, 0, 0]
                ],
                nloops = 0;

            // Initialization. Set initial Eigenvectors.
            for (i = 0; i < n; i++) {
                for (j = 0; j < n; j++) {
                    V[i][j] = 0.0;
                    A[i][j] = Ain[i][j];
                    sum += Math.abs(A[i][j]);
                }
                V[i][i] = 1.0;
            }

            // Trivial problems
            if (n === 1) {
                return [A, V];
            }

            if (sum <= 0.0) {
                return [A, V];
            }

            sum /= (n * n);

            // Reduce matrix to diagonal
            do {
                ssum = 0.0;
                amax = 0.0;
                for (j = 1; j < n; j++) {
                    for (i = 0; i < j; i++) {
                        // Check if A[i][j] is to be reduced
                        aa = Math.abs(A[i][j]);

                        if (aa > amax) {
                            amax = aa;
                        }

                        ssum += aa;

                        if (aa >= eps) {
                            // calculate rotation angle
                            aa = Math.atan2(2.0 * A[i][j], A[i][i] - A[j][j]) * 0.5;
                            si = Math.sin(aa);
                            co = Math.cos(aa);

                            // Modify 'i' and 'j' columns
                            for (k = 0; k < n; k++) {
                                tt = A[k][i];
                                A[k][i] = co * tt + si * A[k][j];
                                A[k][j] = -si * tt + co * A[k][j];
                                tt = V[k][i];
                                V[k][i] = co * tt + si * V[k][j];
                                V[k][j] = -si * tt + co * V[k][j];
                            }

                            // Modify diagonal terms
                            A[i][i] = co * A[i][i] + si * A[j][i];
                            A[j][j] = -si * A[i][j] + co * A[j][j];
                            A[i][j] = 0.0;

                            // Make 'A' matrix symmetrical
                            for (k = 0; k < n; k++) {
                                A[i][k] = A[k][i];
                                A[j][k] = A[k][j];
                            }
                            // A[i][j] made zero by rotation
                        }
                    }
                }
                nloops += 1;
            } while (Math.abs(ssum) / sum > eps && nloops < 2000);

            return [A, V];
        },

        /**
         * Calculates the integral of function f over interval using Newton-Cotes-algorithm.
         * @param {Array} interval The integration interval, e.g. [0, 3].
         * @param {function} f A function which takes one argument of type number and returns a number.
         * @param {Object} [config] The algorithm setup. Accepted properties are number_of_nodes of type number and integration_type
         * with value being either 'trapez', 'simpson', or 'milne'.
         * @param {Number} [config.number_of_nodes=28]
         * @param {String} [config.integration_type='milne'] Possible values are 'milne', 'simpson', 'trapez'
         * @returns {Number} Integral value of f over interval
         * @throws {Error} If config.number_of_nodes doesn't match config.integration_type an exception is thrown. If you want to use
         * simpson rule respectively milne rule config.number_of_nodes must be dividable by 2 respectively 4.
         * @example
         * function f(x) {
         *   return x*x;
         * }
         *
         * // calculates integral of <tt>f</tt> from 0 to 2.
         * var area1 = JXG.Math.Numerics.NewtonCotes([0, 2], f);
         *
         * // the same with an anonymous function
         * var area2 = JXG.Math.Numerics.NewtonCotes([0, 2], function (x) { return x*x; });
         *
         * // use trapez rule with 16 nodes
         * var area3 = JXG.Math.Numerics.NewtonCotes([0, 2], f,
         *                                   {number_of_nodes: 16, integration_type: 'trapez'});
         * @memberof JXG.Math.Numerics
         */
        NewtonCotes: function (interval, f, config) {
            var evaluation_point, i, number_of_intervals,
                integral_value = 0.0,
                number_of_nodes = config && Type.isNumber(config.number_of_nodes) ? config.number_of_nodes : 28,
                available_types = {trapez: true, simpson: true, milne: true},
                integration_type = config && config.integration_type && available_types.hasOwnProperty(config.integration_type) && available_types[config.integration_type] ? config.integration_type : 'milne',
                step_size = (interval[1] - interval[0]) / number_of_nodes;

            switch (integration_type) {
            case 'trapez':
                integral_value = (f(interval[0]) + f(interval[1])) * 0.5;
                evaluation_point = interval[0];

                for (i = 0; i < number_of_nodes - 1; i++) {
                    evaluation_point += step_size;
                    integral_value += f(evaluation_point);
                }

                integral_value *= step_size;
                break;
            case 'simpson':
                if (number_of_nodes % 2 > 0) {
                    throw new Error("JSXGraph:  INT_SIMPSON requires config.number_of_nodes dividable by 2.");
                }

                number_of_intervals = number_of_nodes / 2.0;
                integral_value = f(interval[0]) + f(interval[1]);
                evaluation_point = interval[0];

                for (i = 0; i < number_of_intervals - 1; i++) {
                    evaluation_point += 2.0 * step_size;
                    integral_value += 2.0 * f(evaluation_point);
                }

                evaluation_point = interval[0] - step_size;

                for (i = 0; i < number_of_intervals; i++) {
                    evaluation_point += 2.0 * step_size;
                    integral_value += 4.0 * f(evaluation_point);
                }

                integral_value *= step_size / 3.0;
                break;
            default:
                if (number_of_nodes % 4 > 0) {
                    throw new Error("JSXGraph: Error in INT_MILNE: config.number_of_nodes must be a multiple of 4");
                }

                number_of_intervals = number_of_nodes * 0.25;
                integral_value = 7.0 * (f(interval[0]) + f(interval[1]));
                evaluation_point = interval[0];

                for (i = 0; i < number_of_intervals - 1; i++) {
                    evaluation_point += 4.0 * step_size;
                    integral_value += 14.0 * f(evaluation_point);
                }

                evaluation_point = interval[0] - 3.0 * step_size;

                for (i = 0; i < number_of_intervals; i++) {
                    evaluation_point += 4.0 * step_size;
                    integral_value += 32.0 * (f(evaluation_point) + f(evaluation_point + 2 * step_size));
                }

                evaluation_point = interval[0] - 2.0 * step_size;

                for (i = 0; i < number_of_intervals; i++) {
                    evaluation_point += 4.0 * step_size;
                    integral_value += 12.0 * f(evaluation_point);
                }

                integral_value *= 2.0 * step_size / 45.0;
            }
            return integral_value;
        },

       /**
         * Calculates the integral of function f over interval using Romberg iteration.
         * @param {Array} interval The integration interval, e.g. [0, 3].
         * @param {function} f A function which takes one argument of type number and returns a number.
         * @param {Object} [config] The algorithm setup. Accepted properties are max_iterations of type number and precision eps.
         * @param {Number} [config.max_iterations=20]
         * @param {Number} [config.eps=0.0000001]
         * @returns {Number} Integral value of f over interval
         * @example
         * function f(x) {
         *   return x*x;
         * }
         *
         * // calculates integral of <tt>f</tt> from 0 to 2.
         * var area1 = JXG.Math.Numerics.Romberg([0, 2], f);
         *
         * // the same with an anonymous function
         * var area2 = JXG.Math.Numerics.Romberg([0, 2], function (x) { return x*x; });
         *
         * // use trapez rule with maximum of 16 iterations or stop if the precision 0.0001 has been reached.
         * var area3 = JXG.Math.Numerics.Romberg([0, 2], f,
         *                                   {max_iterations: 16, eps: 0.0001});
         * @memberof JXG.Math.Numerics
         */
        Romberg: function (interval, f, config) {
            var a, b, h, s, n,
                k, i, q,
                p = [],
                integral = 0.0,
                last = Infinity,
                m = config && Type.isNumber(config.max_iterations) ? config.max_iterations : 20,
                eps = config && Type.isNumber(config.eps) ? config.eps : config.eps || 0.0000001;

            a = interval[0];
            b = interval[1];
            h = b - a;
            n = 1;

            p[0] = 0.5 * h * (f(a) + f(b));

            for (k = 0; k < m; ++k) {
                s = 0;
                h *= 0.5;
                n *= 2;
                q = 1;

                for (i = 1; i < n; i += 2) {
                    s += f(a + i * h);
                }

                p[k + 1] = 0.5 * p[k] + s * h;

                integral = p[k + 1];
                for (i = k - 1; i >= 0; --i) {
                    q *= 4;
                    p[i] = p[i + 1] + (p[i + 1] - p[i]) / (q - 1.0);
                    integral = p[i];
                }

                if (Math.abs(integral - last) < eps * Math.abs(integral)) {
                    break;
                }
                last = integral;
            }

            return integral;
        },

       /**
         * Calculates the integral of function f over interval using Gauss-Legendre quadrature.
         * @param {Array} interval The integration interval, e.g. [0, 3].
         * @param {function} f A function which takes one argument of type number and returns a number.
         * @param {Object} [config] The algorithm setup. Accepted property is the order n of type number. n is allowed to take
         * values between 2 and 18, default value is 12.
         * @param {Number} [config.n=16]
         * @returns {Number} Integral value of f over interval
         * @example
         * function f(x) {
         *   return x*x;
         * }
         *
         * // calculates integral of <tt>f</tt> from 0 to 2.
         * var area1 = JXG.Math.Numerics.GaussLegendre([0, 2], f);
         *
         * // the same with an anonymous function
         * var area2 = JXG.Math.Numerics.GaussLegendre([0, 2], function (x) { return x*x; });
         *
         * // use 16 point Gauss-Legendre rule.
         * var area3 = JXG.Math.Numerics.GaussLegendre([0, 2], f,
         *                                   {n: 16});
         * @memberof JXG.Math.Numerics
         */
        GaussLegendre: function (interval, f, config) {
            var a, b,
                i, m,
                xp, xm,
                result = 0.0,
                table_xi = [],
                table_w = [],
                xi, w,
                n = config && Type.isNumber(config.n) ? config.n : 12;

            if (n > 18) {
                n = 18;
            }

            /* n = 2 */
            table_xi[2] = [0.5773502691896257645091488];
            table_w[2] = [1.0000000000000000000000000];

            /* n = 4 */
            table_xi[4] = [0.3399810435848562648026658, 0.8611363115940525752239465];
            table_w[4] = [0.6521451548625461426269361, 0.3478548451374538573730639];

            /* n = 6 */
            table_xi[6] = [0.2386191860831969086305017, 0.6612093864662645136613996, 0.9324695142031520278123016];
            table_w[6] = [0.4679139345726910473898703, 0.3607615730481386075698335, 0.1713244923791703450402961];

            /* n = 8 */
            table_xi[8] = [0.1834346424956498049394761, 0.5255324099163289858177390, 0.7966664774136267395915539, 0.9602898564975362316835609];
            table_w[8] = [0.3626837833783619829651504, 0.3137066458778872873379622, 0.2223810344533744705443560, 0.1012285362903762591525314];

            /* n = 10 */
            table_xi[10] = [0.1488743389816312108848260, 0.4333953941292471907992659, 0.6794095682990244062343274, 0.8650633666889845107320967, 0.9739065285171717200779640];
            table_w[10] = [0.2955242247147528701738930, 0.2692667193099963550912269, 0.2190863625159820439955349, 0.1494513491505805931457763, 0.0666713443086881375935688];

            /* n = 12 */
            table_xi[12] = [0.1252334085114689154724414, 0.3678314989981801937526915, 0.5873179542866174472967024, 0.7699026741943046870368938, 0.9041172563704748566784659, 0.9815606342467192506905491];
            table_w[12] = [0.2491470458134027850005624, 0.2334925365383548087608499, 0.2031674267230659217490645, 0.1600783285433462263346525, 0.1069393259953184309602547, 0.0471753363865118271946160];

            /* n = 14 */
            table_xi[14] = [0.1080549487073436620662447, 0.3191123689278897604356718, 0.5152486363581540919652907, 0.6872929048116854701480198, 0.8272013150697649931897947, 0.9284348836635735173363911, 0.9862838086968123388415973];
            table_w[14] = [0.2152638534631577901958764, 0.2051984637212956039659241, 0.1855383974779378137417166, 0.1572031671581935345696019, 0.1215185706879031846894148, 0.0801580871597602098056333, 0.0351194603317518630318329];

            /* n = 16 */
            table_xi[16] = [0.0950125098376374401853193, 0.2816035507792589132304605, 0.4580167776572273863424194, 0.6178762444026437484466718, 0.7554044083550030338951012, 0.8656312023878317438804679, 0.9445750230732325760779884, 0.9894009349916499325961542];
            table_w[16] = [0.1894506104550684962853967, 0.1826034150449235888667637, 0.1691565193950025381893121, 0.1495959888165767320815017, 0.1246289712555338720524763, 0.0951585116824927848099251, 0.0622535239386478928628438, 0.0271524594117540948517806];

            /* n = 18 */
            table_xi[18] = [0.0847750130417353012422619, 0.2518862256915055095889729, 0.4117511614628426460359318, 0.5597708310739475346078715, 0.6916870430603532078748911, 0.8037049589725231156824175, 0.8926024664975557392060606, 0.9558239495713977551811959, 0.9915651684209309467300160];
            table_w[18] = [0.1691423829631435918406565, 0.1642764837458327229860538, 0.1546846751262652449254180, 0.1406429146706506512047313, 0.1225552067114784601845191, 0.1009420441062871655628140, 0.0764257302548890565291297, 0.0497145488949697964533349, 0.0216160135264833103133427];

            /* n = 3 */
            table_xi[3] = [0.0000000000000000000000000, 0.7745966692414833770358531];
            table_w[3] = [0.8888888888888888888888889, 0.5555555555555555555555556];

            /* n = 5 */
            table_xi[5] = [0.0000000000000000000000000, 0.5384693101056830910363144, 0.9061798459386639927976269];
            table_w[5] = [0.5688888888888888888888889, 0.4786286704993664680412915, 0.2369268850561890875142640];

            /* n = 7 */
            table_xi[7] = [0.0000000000000000000000000, 0.4058451513773971669066064, 0.7415311855993944398638648, 0.9491079123427585245261897];
            table_w[7] = [0.4179591836734693877551020, 0.3818300505051189449503698, 0.2797053914892766679014678, 0.1294849661688696932706114];

            /* n = 9 */
            table_xi[9] = [0.0000000000000000000000000, 0.3242534234038089290385380, 0.6133714327005903973087020, 0.8360311073266357942994298, 0.9681602395076260898355762];
            table_w[9] = [0.3302393550012597631645251, 0.3123470770400028400686304, 0.2606106964029354623187429, 0.1806481606948574040584720, 0.0812743883615744119718922];

            /* n = 11 */
            table_xi[11] = [0.0000000000000000000000000, 0.2695431559523449723315320, 0.5190961292068118159257257, 0.7301520055740493240934163, 0.8870625997680952990751578, 0.9782286581460569928039380];
            table_w[11] = [0.2729250867779006307144835, 0.2628045445102466621806889, 0.2331937645919904799185237, 0.1862902109277342514260976, 0.1255803694649046246346943, 0.0556685671161736664827537];

            /* n = 13 */
            table_xi[13] = [0.0000000000000000000000000, 0.2304583159551347940655281, 0.4484927510364468528779129, 0.6423493394403402206439846, 0.8015780907333099127942065, 0.9175983992229779652065478, 0.9841830547185881494728294];
            table_w[13] = [0.2325515532308739101945895, 0.2262831802628972384120902, 0.2078160475368885023125232, 0.1781459807619457382800467, 0.1388735102197872384636018, 0.0921214998377284479144218, 0.0404840047653158795200216];

            /* n = 15 */
            table_xi[15] = [0.0000000000000000000000000, 0.2011940939974345223006283, 0.3941513470775633698972074, 0.5709721726085388475372267, 0.7244177313601700474161861, 0.8482065834104272162006483, 0.9372733924007059043077589, 0.9879925180204854284895657];
            table_w[15] = [0.2025782419255612728806202, 0.1984314853271115764561183, 0.1861610000155622110268006, 0.1662692058169939335532009, 0.1395706779261543144478048, 0.1071592204671719350118695, 0.0703660474881081247092674, 0.0307532419961172683546284];

            /* n = 17 */
            table_xi[17] = [0.0000000000000000000000000, 0.1784841814958478558506775, 0.3512317634538763152971855, 0.5126905370864769678862466, 0.6576711592166907658503022, 0.7815140038968014069252301, 0.8802391537269859021229557, 0.9506755217687677612227170, 0.9905754753144173356754340];
            table_w[17] = [0.1794464703562065254582656, 0.1765627053669926463252710, 0.1680041021564500445099707, 0.1540457610768102880814316, 0.1351363684685254732863200, 0.1118838471934039710947884, 0.0850361483171791808835354, 0.0554595293739872011294402, 0.0241483028685479319601100];

            a = interval[0];
            b = interval[1];

            //m = Math.ceil(n * 0.5);
            m = (n + 1) >> 1;

            xi = table_xi[n];
            w = table_w[n];

            xm = 0.5 * (b - a);
            xp = 0.5 * (b + a);

            if (n & 1 === 1) { // n odd
                result = w[0] * f(xp);
                for (i = 1; i < m; ++i) {
                    result += w[i] * (f(xp + xm * xi[i]) + f(xp - xm * xi[i]));
                }
            } else { // n even
                result = 0.0;
                for (i = 0; i < m; ++i) {
                    result += w[i] * (f(xp + xm * xi[i]) + f(xp - xm * xi[i]));
                }
            }

            return xm * result;
        },

        /**
         * Scale error in Gauss Kronrod quadrature.
         * Internal method used in {@link JXG.Math.Numerics._gaussKronrod}.
         * @private
         */
        _rescale_error: function (err, result_abs, result_asc) {
            var scale, min_err,
                DBL_MIN = 2.2250738585072014e-308,
                DBL_EPS = 2.2204460492503131e-16;

            err = Math.abs(err);
            if (result_asc !== 0 && err !== 0) {
                scale = Math.pow((200 * err / result_asc), 1.5);

                if (scale < 1.0) {
                    err = result_asc * scale;
                } else {
                    err = result_asc;
                }
            }
            if (result_abs > DBL_MIN / (50 * DBL_EPS)) {
                min_err = 50 * DBL_EPS * result_abs;

                if (min_err > err) {
                    err = min_err;
                }
            }

            return err;
        },

        /**
         * Generic Gauss-Kronrod quadrature algorithm.
         * Internal method used in {@link JXG.Math.Numerics.GaussKronrod15},
         * {@link JXG.Math.Numerics.GaussKronrod21},
         * {@link JXG.Math.Numerics.GaussKronrod31}.
         * Taken from QUADPACK.
         *
         * @param {Array} interval The integration interval, e.g. [0, 3].
         * @param {function} f A function which takes one argument of type number and returns a number.
         * @param {Number} n order
         * @param {Array} xgk Kronrod quadrature abscissae
         * @param {Array} wg Weights of the Gauss rule
         * @param {Array} wgk Weights of the Kronrod rule
         * @param {Object} resultObj Object returning resultObj.abserr, resultObj.resabs, resultObj.resasc.
         * See the library QUADPACK for an explanation.
         *
         * @returns {Number} Integral value of f over interval
         *
         * @private
         */
        _gaussKronrod: function (interval, f, n, xgk, wg, wgk, resultObj) {
            var a = interval[0],
                b = interval[1],
                up,
                result,

                center = 0.5 * (a + b),
                half_length = 0.5 * (b - a),
                abs_half_length = Math.abs(half_length),
                f_center = f(center),

                result_gauss = 0.0,
                result_kronrod = f_center * wgk[n - 1],

                result_abs = Math.abs(result_kronrod),
                result_asc = 0.0,
                mean = 0.0,
                err = 0.0,

                j, jtw, abscissa, fval1, fval2, fsum,
                jtwm1,
                fv1 = [], fv2 = [];

            if (n % 2 === 0) {
                result_gauss = f_center * wg[n / 2 - 1];
            }

            up = Math.floor((n - 1) / 2);
            for (j = 0; j < up; j++) {
                jtw = j * 2 + 1;  // in original fortran j=1,2,3 jtw=2,4,6
                abscissa = half_length * xgk[jtw];
                fval1 = f(center - abscissa);
                fval2 = f(center + abscissa);
                fsum = fval1 + fval2;
                fv1[jtw] = fval1;
                fv2[jtw] = fval2;
                result_gauss += wg[j] * fsum;
                result_kronrod += wgk[jtw] * fsum;
                result_abs += wgk[jtw] * (Math.abs(fval1) + Math.abs(fval2));
            }

            up = Math.floor(n / 2);
            for (j = 0; j < up; j++) {
                jtwm1 = j * 2;
                abscissa = half_length * xgk[jtwm1];
                fval1 = f(center - abscissa);
                fval2 = f(center + abscissa);
                fv1[jtwm1] = fval1;
                fv2[jtwm1] = fval2;
                result_kronrod += wgk[jtwm1] * (fval1 + fval2);
                result_abs += wgk[jtwm1] * (Math.abs(fval1) + Math.abs(fval2));
            }

            mean = result_kronrod * 0.5;
            result_asc = wgk[n - 1] * Math.abs(f_center - mean);

            for (j = 0; j < n - 1; j++) {
                result_asc += wgk[j] * (Math.abs(fv1[j] - mean) + Math.abs(fv2[j] - mean));
            }

            // scale by the width of the integration region
            err = (result_kronrod - result_gauss) * half_length;

            result_kronrod *= half_length;
            result_abs *= abs_half_length;
            result_asc *= abs_half_length;
            result = result_kronrod;

            resultObj.abserr = this._rescale_error(err, result_abs, result_asc);
            resultObj.resabs = result_abs;
            resultObj.resasc = result_asc;

            return result;
        },

        /**
         * 15 point Gauss-Kronrod quadrature algorithm, see the library QUADPACK
         * @param {Array} interval The integration interval, e.g. [0, 3].
         * @param {function} f A function which takes one argument of type number and returns a number.
         * @param {Object} resultObj Object returning resultObj.abserr, resultObj.resabs, resultObj.resasc. See the library
         *  QUADPACK for an explanation.
         *
         * @returns {Number} Integral value of f over interval
         *
         * @memberof JXG.Math.Numerics
         */
        GaussKronrod15: function (interval, f, resultObj) {
            /* Gauss quadrature weights and kronrod quadrature abscissae and
                weights as evaluated with 80 decimal digit arithmetic by
                L. W. Fullerton, Bell Labs, Nov. 1981. */

            var xgk =    /* abscissae of the 15-point kronrod rule */
                    [
                        0.991455371120812639206854697526329,
                        0.949107912342758524526189684047851,
                        0.864864423359769072789712788640926,
                        0.741531185599394439863864773280788,
                        0.586087235467691130294144838258730,
                        0.405845151377397166906606412076961,
                        0.207784955007898467600689403773245,
                        0.000000000000000000000000000000000
                    ],

            /* xgk[1], xgk[3], ... abscissae of the 7-point gauss rule.
                xgk[0], xgk[2], ... abscissae to optimally extend the 7-point gauss rule */

                wg =     /* weights of the 7-point gauss rule */
                    [
                        0.129484966168869693270611432679082,
                        0.279705391489276667901467771423780,
                        0.381830050505118944950369775488975,
                        0.417959183673469387755102040816327
                    ],

                wgk =    /* weights of the 15-point kronrod rule */
                    [
                        0.022935322010529224963732008058970,
                        0.063092092629978553290700663189204,
                        0.104790010322250183839876322541518,
                        0.140653259715525918745189590510238,
                        0.169004726639267902826583426598550,
                        0.190350578064785409913256402421014,
                        0.204432940075298892414161999234649,
                        0.209482141084727828012999174891714
                    ];

            return this._gaussKronrod(interval, f, 8, xgk, wg, wgk, resultObj);
        },

        /**
         * 21 point Gauss-Kronrod quadrature algorithm, see the library QUADPACK
         * @param {Array} interval The integration interval, e.g. [0, 3].
         * @param {function} f A function which takes one argument of type number and returns a number.
         * @param {Object} resultObj Object returning resultObj.abserr, resultObj.resabs, resultObj.resasc. See the library
         *  QUADPACK for an explanation.
         *
         * @returns {Number} Integral value of f over interval
         *
         * @memberof JXG.Math.Numerics
         */
        GaussKronrod21: function (interval, f, resultObj) {
            /* Gauss quadrature weights and kronrod quadrature abscissae and
                weights as evaluated with 80 decimal digit arithmetic by
                L. W. Fullerton, Bell Labs, Nov. 1981. */

            var xgk =   /* abscissae of the 21-point kronrod rule */
                    [
                        0.995657163025808080735527280689003,
                        0.973906528517171720077964012084452,
                        0.930157491355708226001207180059508,
                        0.865063366688984510732096688423493,
                        0.780817726586416897063717578345042,
                        0.679409568299024406234327365114874,
                        0.562757134668604683339000099272694,
                        0.433395394129247190799265943165784,
                        0.294392862701460198131126603103866,
                        0.148874338981631210884826001129720,
                        0.000000000000000000000000000000000
                    ],

                /* xgk[1], xgk[3], ... abscissae of the 10-point gauss rule.
                xgk[0], xgk[2], ... abscissae to optimally extend the 10-point gauss rule */
                wg =     /* weights of the 10-point gauss rule */
                    [
                        0.066671344308688137593568809893332,
                        0.149451349150580593145776339657697,
                        0.219086362515982043995534934228163,
                        0.269266719309996355091226921569469,
                        0.295524224714752870173892994651338
                    ],

                wgk =   /* weights of the 21-point kronrod rule */
                    [
                        0.011694638867371874278064396062192,
                        0.032558162307964727478818972459390,
                        0.054755896574351996031381300244580,
                        0.075039674810919952767043140916190,
                        0.093125454583697605535065465083366,
                        0.109387158802297641899210590325805,
                        0.123491976262065851077958109831074,
                        0.134709217311473325928054001771707,
                        0.142775938577060080797094273138717,
                        0.147739104901338491374841515972068,
                        0.149445554002916905664936468389821
                    ];

            return this._gaussKronrod(interval, f, 11, xgk, wg, wgk, resultObj);
        },

        /**
         * 31 point Gauss-Kronrod quadrature algorithm, see the library QUADPACK
         * @param {Array} interval The integration interval, e.g. [0, 3].
         * @param {function} f A function which takes one argument of type number and returns a number.
         * @param {Object} resultObj Object returning resultObj.abserr, resultObj.resabs, resultObj.resasc. See the library
         *  QUADPACK for an explanation.
         *
         * @returns {Number} Integral value of f over interval
         *
         * @memberof JXG.Math.Numerics
         */
        GaussKronrod31: function (interval, f, resultObj) {
            /* Gauss quadrature weights and kronrod quadrature abscissae and
                weights as evaluated with 80 decimal digit arithmetic by
                L. W. Fullerton, Bell Labs, Nov. 1981. */

            var xgk =   /* abscissae of the 21-point kronrod rule */
                    [
                        0.998002298693397060285172840152271,
                        0.987992518020485428489565718586613,
                        0.967739075679139134257347978784337,
                        0.937273392400705904307758947710209,
                        0.897264532344081900882509656454496,
                        0.848206583410427216200648320774217,
                        0.790418501442465932967649294817947,
                        0.724417731360170047416186054613938,
                        0.650996741297416970533735895313275,
                        0.570972172608538847537226737253911,
                        0.485081863640239680693655740232351,
                        0.394151347077563369897207370981045,
                        0.299180007153168812166780024266389,
                        0.201194093997434522300628303394596,
                        0.101142066918717499027074231447392,
                        0.000000000000000000000000000000000
                    ],

                /* xgk[1], xgk[3], ... abscissae of the 10-point gauss rule.
                xgk[0], xgk[2], ... abscissae to optimally extend the 10-point gauss rule */
                wg =     /* weights of the 10-point gauss rule */
                    [
                        0.030753241996117268354628393577204,
                        0.070366047488108124709267416450667,
                        0.107159220467171935011869546685869,
                        0.139570677926154314447804794511028,
                        0.166269205816993933553200860481209,
                        0.186161000015562211026800561866423,
                        0.198431485327111576456118326443839,
                        0.202578241925561272880620199967519
                    ],

                wgk =   /* weights of the 21-point kronrod rule */
                    [
                        0.005377479872923348987792051430128,
                        0.015007947329316122538374763075807,
                        0.025460847326715320186874001019653,
                        0.035346360791375846222037948478360,
                        0.044589751324764876608227299373280,
                        0.053481524690928087265343147239430,
                        0.062009567800670640285139230960803,
                        0.069854121318728258709520077099147,
                        0.076849680757720378894432777482659,
                        0.083080502823133021038289247286104,
                        0.088564443056211770647275443693774,
                        0.093126598170825321225486872747346,
                        0.096642726983623678505179907627589,
                        0.099173598721791959332393173484603,
                        0.100769845523875595044946662617570,
                        0.101330007014791549017374792767493
                    ];

            return this._gaussKronrod(interval, f, 16, xgk, wg, wgk, resultObj);
        },

        /**
         * Generate workspace object for {@link JXG.Math.Numerics.Qag}.
         * @param {Array} interval The integration interval, e.g. [0, 3].
         * @param {Number} n Max. limit
         * @returns {Object} Workspace object
         *
         * @private
         * @memberof JXG.Math.Numerics
         */
        _workspace: function (interval, n) {
            return {
                limit: n,
                size: 0,
                nrmax: 0,
                i: 0,
                alist: [interval[0]],
                blist: [interval[1]],
                rlist: [0.0],
                elist: [0.0],
                order: [0],
                level: [0],

                qpsrt: function () {
                    var last = this.size - 1,
                        limit = this.limit,
                        errmax, errmin, i, k, top,
                        i_nrmax = this.nrmax,
                        i_maxerr = this.order[i_nrmax];

                    /* Check whether the list contains more than two error estimates */
                    if (last < 2) {
                        this.order[0] = 0;
                        this.order[1] = 1;
                        this.i = i_maxerr;
                        return;
                    }

                    errmax = this.elist[i_maxerr];

                    /* This part of the routine is only executed if, due to a difficult
                        integrand, subdivision increased the error estimate. In the normal
                        case the insert procedure should start after the nrmax-th largest
                        error estimate. */
                    while (i_nrmax > 0 && errmax > this.elist[this.order[i_nrmax - 1]]) {
                        this.order[i_nrmax] = this.order[i_nrmax - 1];
                        i_nrmax--;
                    }

                    /* Compute the number of elements in the list to be maintained in
                        descending order. This number depends on the number of
                        subdivisions still allowed. */
                    if (last < (limit / 2 + 2)) {
                        top = last;
                    } else {
                        top = limit - last + 1;
                    }

                    /* Insert errmax by traversing the list top-down, starting
                        comparison from the element elist(order(i_nrmax+1)). */
                    i = i_nrmax + 1;

                    /* The order of the tests in the following line is important to
                        prevent a segmentation fault */
                    while (i < top && errmax < this.elist[this.order[i]]) {
                        this.order[i - 1] = this.order[i];
                        i++;
                    }

                    this.order[i - 1] = i_maxerr;

                    /* Insert errmin by traversing the list bottom-up */
                    errmin = this.elist[last];
                    k = top - 1;

                    while (k > i - 2 && errmin >= this.elist[this.order[k]]) {
                        this.order[k + 1] = this.order[k];
                        k--;
                    }

                    this.order[k + 1] = last;

                    /* Set i_max and e_max */
                    i_maxerr = this.order[i_nrmax];
                    this.i = i_maxerr;
                    this.nrmax = i_nrmax;
                },

                set_initial_result: function (result, error) {
                    this.size = 1;
                    this.rlist[0] = result;
                    this.elist[0] = error;
                },

                update: function (a1, b1, area1, error1, a2, b2, area2, error2) {
                    var i_max = this.i,
                        i_new = this.size,
                        new_level = this.level[this.i] + 1;

                    /* append the newly-created intervals to the list */

                    if (error2 > error1) {
                        this.alist[i_max] = a2;        /* blist[maxerr] is already == b2 */
                        this.rlist[i_max] = area2;
                        this.elist[i_max] = error2;
                        this.level[i_max] = new_level;

                        this.alist[i_new] = a1;
                        this.blist[i_new] = b1;
                        this.rlist[i_new] = area1;
                        this.elist[i_new] = error1;
                        this.level[i_new] = new_level;
                    } else {
                        this.blist[i_max] = b1;        /* alist[maxerr] is already == a1 */
                        this.rlist[i_max] = area1;
                        this.elist[i_max] = error1;
                        this.level[i_max] = new_level;

                        this.alist[i_new] = a2;
                        this.blist[i_new] = b2;
                        this.rlist[i_new] = area2;
                        this.elist[i_new] = error2;
                        this.level[i_new] = new_level;
                    }

                    this.size++;

                    if (new_level > this.maximum_level) {
                        this.maximum_level = new_level;
                    }

                    this.qpsrt();
                },

                retrieve: function() {
                    var i = this.i;
                    return {
                        a: this.alist[i],
                        b: this.blist[i],
                        r: this.rlist[i],
                        e: this.elist[i]
                    };
                },

                sum_results: function () {
                    var nn = this.size,
                        k,
                        result_sum = 0.0;

                    for (k = 0; k < nn; k++) {
                        result_sum += this.rlist[k];
                    }

                    return result_sum;
                },

                subinterval_too_small: function (a1, a2,  b2) {
                    var e = 2.2204460492503131e-16,
                        u = 2.2250738585072014e-308,
                        tmp = (1 + 100 * e) * (Math.abs(a2) + 1000 * u);

                    return Math.abs(a1) <= tmp && Math.abs(b2) <= tmp;
                }

            };
        },

        /**
         * Quadrature algorithm qag from QUADPACK.
         * Internal method used in {@link JXG.Math.Numerics.GaussKronrod15},
         * {@link JXG.Math.Numerics.GaussKronrod21},
         * {@link JXG.Math.Numerics.GaussKronrod31}.
         *
         * @param {Array} interval The integration interval, e.g. [0, 3].
         * @param {function} f A function which takes one argument of type number and returns a number.
         * @param {Object} [config] The algorithm setup. Accepted propert are max. recursion limit of type number,
         * and epsrel and epsabs, the relative and absolute required precision of type number. Further,
         * q the internal quadrature sub-algorithm of type function.
         * @param {Number} [config.limit=15]
         * @param {Number} [config.epsrel=0.0000001]
         * @param {Number} [config.epsabs=0.0000001]
         * @param {Number} [config.q=JXG.Math.Numerics.GaussKronrod15]
         * @returns {Number} Integral value of f over interval
         *
         * @example
         * function f(x) {
         *   return x*x;
         * }
         *
         * // calculates integral of <tt>f</tt> from 0 to 2.
         * var area1 = JXG.Math.Numerics.Qag([0, 2], f);
         *
         * // the same with an anonymous function
         * var area2 = JXG.Math.Numerics.Qag([0, 2], function (x) { return x*x; });
         *
         * // use JXG.Math.Numerics.GaussKronrod31 rule as sub-algorithm.
         * var area3 = JXG.Math.Numerics.Quag([0, 2], f,
         *                                   {q: JXG.Math.Numerics.GaussKronrod31});
         * @memberof JXG.Math.Numerics
         */
        Qag: function (interval, f, config) {
            var DBL_EPS = 2.2204460492503131e-16,
                ws = this._workspace(interval, 1000),

                limit = config && Type.isNumber(config.limit) ? config.limit : 15,
                epsrel = config && Type.isNumber(config.epsrel) ? config.epsrel : 0.0000001,
                epsabs = config && Type.isNumber(config.epsabs) ? config.epsabs : 0.0000001,
                q = config && Type.isFunction(config.q) ? config.q : this.GaussKronrod15,

                resultObj = {},
                area, errsum,
                result0, abserr0, resabs0, resasc0,
                result,
                tolerance,
                iteration = 0,
                roundoff_type1 = 0, roundoff_type2 = 0, error_type = 0,
                round_off,

                a1, b1, a2, b2,
                a_i, b_i, r_i, e_i,
                area1 = 0, area2 = 0, area12 = 0,
                error1 = 0, error2 = 0, error12 = 0,
                resasc1, resasc2,
                // resabs1, resabs2,
                wsObj,
                delta;


            if (limit > ws.limit) {
                JXG.warn('iteration limit exceeds available workspace');
            }
            if (epsabs <= 0 && (epsrel < 50 * Mat.eps || epsrel < 0.5e-28)) {
                JXG.warn('tolerance cannot be acheived with given epsabs and epsrel');
            }

            result0 = q.apply(this, [interval, f, resultObj]);
            abserr0 = resultObj.abserr;
            resabs0 = resultObj.resabs;
            resasc0 = resultObj.resasc;

            ws.set_initial_result(result0, abserr0);
            tolerance = Math.max(epsabs, epsrel * Math.abs(result0));
            round_off = 50 * DBL_EPS * resabs0;

            if (abserr0 <= round_off && abserr0 > tolerance) {
                result = result0;
                // abserr = abserr0;

                JXG.warn('cannot reach tolerance because of roundoff error on first attempt');
                return -Infinity;
            }

            if ((abserr0 <= tolerance && abserr0 !== resasc0) || abserr0 === 0.0) {
                result = result0;
                // abserr = abserr0;

                return result;
            }

            if (limit === 1) {
                result = result0;
                // abserr = abserr0;

                JXG.warn('a maximum of one iteration was insufficient');
                return -Infinity;
            }

            area = result0;
            errsum = abserr0;
            iteration = 1;

            do {
                area1 = 0;
                area2 = 0;
                area12 = 0;
                error1 = 0;
                error2 = 0;
                error12 = 0;

                /* Bisect the subinterval with the largest error estimate */
                wsObj = ws.retrieve();
                a_i = wsObj.a;
                b_i = wsObj.b;
                r_i = wsObj.r;
                e_i = wsObj.e;

                a1 = a_i;
                b1 = 0.5 * (a_i + b_i);
                a2 = b1;
                b2 = b_i;

                area1 = q.apply(this, [[a1, b1], f, resultObj]);
                error1 = resultObj.abserr;
                // resabs1 = resultObj.resabs;
                resasc1 = resultObj.resasc;

                area2 = q.apply(this, [[a2, b2], f, resultObj]);
                error2 = resultObj.abserr;
                // resabs2 = resultObj.resabs;
                resasc2 = resultObj.resasc;

                area12 = area1 + area2;
                error12 = error1 + error2;

                errsum += (error12 - e_i);
                area += area12 - r_i;

                if (resasc1 !== error1 && resasc2 !== error2) {
                    delta = r_i - area12;
                    if (Math.abs(delta) <= 1.0e-5 * Math.abs(area12) && error12 >= 0.99 * e_i) {
                        roundoff_type1++;
                    }
                    if (iteration >= 10 && error12 > e_i) {
                        roundoff_type2++;
                    }
                }

                tolerance = Math.max(epsabs, epsrel * Math.abs(area));

                if (errsum > tolerance) {
                    if (roundoff_type1 >= 6 || roundoff_type2 >= 20) {
                        error_type = 2;   /* round off error */
                    }

                /* set error flag in the case of bad integrand behaviour at
                    a point of the integration range */

                    if (ws.subinterval_too_small(a1, a2, b2)) {
                        error_type = 3;
                    }
                }

                ws.update(a1, b1, area1, error1, a2, b2, area2, error2);
                wsObj = ws.retrieve();
                a_i = wsObj.a_i;
                b_i = wsObj.b_i;
                r_i = wsObj.r_i;
                e_i = wsObj.e_i;

                iteration++;

            } while (iteration < limit && !error_type && errsum > tolerance);

            result = ws.sum_results();
            // abserr = errsum;
/*
  if (errsum <= tolerance)
    {
      return GSL_SUCCESS;
    }
  else if (error_type == 2)
    {
      GSL_ERROR ("roundoff error prevents tolerance from being achieved",
                 GSL_EROUND);
    }
  else if (error_type == 3)
    {
      GSL_ERROR ("bad integrand behavior found in the integration interval",
                 GSL_ESING);
    }
  else if (iteration == limit)
    {
      GSL_ERROR ("maximum number of subdivisions reached", GSL_EMAXITER);
    }
  else
    {
      GSL_ERROR ("could not integrate function", GSL_EFAILED);
    }
*/

            return result;
        },

        /**
         * Integral of function f over interval.
         * @param {Array} interval The integration interval, e.g. [0, 3].
         * @param {function} f A function which takes one argument of type number and returns a number.
         * @returns {Number} The value of the integral of f over interval
         * @see JXG.Math.Numerics.NewtonCotes
         * @see JXG.Math.Numerics.Romberg
         * @see JXG.Math.Numerics.Qag
         * @memberof JXG.Math.Numerics
         */
        I: function (interval, f) {
            // return this.NewtonCotes(interval, f, {number_of_nodes: 16, integration_type: 'milne'});
            // return this.Romberg(interval, f, {max_iterations: 20, eps: 0.0000001});
            return this.Qag(interval, f, {q: this.GaussKronrod15, limit: 15, epsrel: 0.0000001, epsabs: 0.0000001});
        },

        /**
         * Newton's method to find roots of a funtion in one variable.
         * @param {function} f We search for a solution of f(x)=0.
         * @param {Number} x initial guess for the root, i.e. start value.
         * @param {Object} context optional object that is treated as "this" in the function body. This is useful if
         * the function is a method of an object and contains a reference to its parent object via "this".
         * @returns {Number} A root of the function f.
         * @memberof JXG.Math.Numerics
         */
        Newton: function (f, x, context) {
            var df,
                i = 0,
                h = Mat.eps,
                newf = f.apply(context, [x]);
                // nfev = 1;

            // For compatibility
            if (Type.isArray(x)) {
                x = x[0];
            }

            while (i < 50 && Math.abs(newf) > h) {
                df = this.D(f, context)(x);
                // nfev += 2;

                if (Math.abs(df) > h) {
                    x -= newf / df;
                } else {
                    x += (Math.random() * 0.2 - 1.0);
                }

                newf = f.apply(context, [x]);
                // nfev += 1;
                i += 1;
            }

            return x;
        },

        /**
         * Abstract method to find roots of univariate functions, which - for the time being -
         * is an alias for {@link JXG.Math.Numerics.chandrupatla}.
         * @param {function} f We search for a solution of f(x)=0.
         * @param {Number|Array} x initial guess for the root, i.e. starting value, or start interval enclosing the root.
         * @param {Object} context optional object that is treated as "this" in the function body. This is useful if
         * the function is a method of an object and contains a reference to its parent object via "this".
         * @returns {Number} A root of the function f.
         *
         * @see JXG.Math.Numerics.chandrupatla
         * @see JXG.Math.Numerics.fzero
         * @memberof JXG.Math.Numerics
         */
        root: function (f, x, context) {
            //return this.fzero(f, x, context);
            return this.chandrupatla(f, x, context);
        },

        /**
         * Compute an intersection of the curves c1 and c2
         * with a generalized Newton method.
         * We want to find values t1, t2 such that
         * c1(t1) = c2(t2), i.e.
         * (c1_x(t1)-c2_x(t2),c1_y(t1)-c2_y(t2)) = (0,0).
         * We set
         * (e,f) := (c1_x(t1)-c2_x(t2),c1_y(t1)-c2_y(t2))
         *
         * The Jacobian J is defined by
         * J = (a, b)
         *     (c, d)
         * where
         * a = c1_x'(t1)
         * b = -c2_x'(t2)
         * c = c1_y'(t1)
         * d = -c2_y'(t2)
         *
         * The inverse J^(-1) of J is equal to
         *  (d, -b)/
         *  (-c, a) / (ad-bc)
         *
         * Then, (t1new, t2new) := (t1,t2) - J^(-1)*(e,f).
         * If the function meetCurveCurve possesses the properties
         * t1memo and t2memo then these are taken as start values
         * for the Newton algorithm.
         * After stopping of the Newton algorithm the values of t1 and t2 are stored in
         * t1memo and t2memo.
         *
         * @param {JXG.Curve} c1 Curve, Line or Circle
         * @param {JXG.Curve} c2 Curve, Line or Circle
         * @param {Number} t1ini start value for t1
         * @param {Number} t2ini start value for t2
         * @returns {JXG.Coords} intersection point
         * @memberof JXG.Math.Numerics
         */
        generalizedNewton: function (c1, c2, t1ini, t2ini) {
            var t1, t2,
                a, b, c, d, disc,
                e, f, F,
                D00, D01,
                D10, D11,
                count = 0;

            if (this.generalizedNewton.t1memo) {
                t1 = this.generalizedNewton.t1memo;
                t2 = this.generalizedNewton.t2memo;
            } else {
                t1 = t1ini;
                t2 = t2ini;
            }

            e = c1.X(t1) - c2.X(t2);
            f = c1.Y(t1) - c2.Y(t2);
            F = e * e + f * f;

            D00 = this.D(c1.X, c1);
            D01 = this.D(c2.X, c2);
            D10 = this.D(c1.Y, c1);
            D11 = this.D(c2.Y, c2);

            while (F > Mat.eps && count < 10) {
                a = D00(t1);
                b = -D01(t2);
                c = D10(t1);
                d = -D11(t2);
                disc = a * d - b * c;
                t1 -= (d * e - b * f) / disc;
                t2 -= (a * f - c * e) / disc;
                e = c1.X(t1) - c2.X(t2);
                f = c1.Y(t1) - c2.Y(t2);
                F = e * e + f * f;
                count += 1;
            }

            this.generalizedNewton.t1memo = t1;
            this.generalizedNewton.t2memo = t2;

            if (Math.abs(t1) < Math.abs(t2)) {
                return [c1.X(t1), c1.Y(t1)];
            }

            return [c2.X(t2), c2.Y(t2)];
        },

        /**
         * Returns the Lagrange polynomials for curves with equidistant nodes, see
         * Jean-Paul Berrut, Lloyd N. Trefethen: Barycentric Lagrange Interpolation,
         * SIAM Review, Vol 46, No 3, (2004) 501-517.
         * The graph of the parametric curve [x(t),y(t)] runs through the given points.
         * @param {Array} p Array of JXG.Points
         * @returns {Array} An array consisting of two functions x(t), y(t) which define a parametric curve
         * f(t) = (x(t), y(t)), a number x1 (which equals 0) and a function x2 defining the curve's domain.
         * That means the curve is defined between x1 and x2(). x2 returns the (length of array p minus one).
         * @memberof JXG.Math.Numerics
         *
         * @example
         * var p = [];
         *
         * p[0] = board.create('point', [0, -2], {size:2, name: 'C(a)'});
         * p[1] = board.create('point', [-1.5, 5], {size:2, name: ''});
         * p[2] = board.create('point', [1, 4], {size:2, name: ''});
         * p[3] = board.create('point', [3, 3], {size:2, name: 'C(b)'});
         *
         * // Curve
         * var fg = JXG.Math.Numerics.Neville(p);
         * var graph = board.create('curve', fg, {strokeWidth:3, strokeOpacity:0.5});
         *
         * </pre><div id="JXG88a8b3a8-6561-44f5-a678-76bca13fd484" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG88a8b3a8-6561-44f5-a678-76bca13fd484',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var p = [];
         *
         *     p[0] = board.create('point', [0, -2], {size:2, name: 'C(a)'});
         *     p[1] = board.create('point', [-1.5, 5], {size:2, name: ''});
         *     p[2] = board.create('point', [1, 4], {size:2, name: ''});
         *     p[3] = board.create('point', [3, 3], {size:2, name: 'C(b)'});
         *
         *     // Curve
         *     var fg = JXG.Math.Numerics.Neville(p);
         *     var graph = board.create('curve', fg, {strokeWidth:3, strokeOpacity:0.5});
         *
         *     })();
         *
         * </script><pre>
         *
         */
        Neville: function (p) {
            var w = [],
                /** @ignore */
                makeFct = function (fun) {
                    return function (t, suspendedUpdate) {
                        var i, d, s,
                            bin = Mat.binomial,
                            len = p.length,
                            len1 = len - 1,
                            num = 0.0,
                            denom = 0.0;

                        if (!suspendedUpdate) {
                            s = 1;
                            for (i = 0; i < len; i++) {
                                w[i] = bin(len1, i) * s;
                                s *= (-1);
                            }
                        }

                        d = t;

                        for (i = 0; i < len; i++) {
                            if (d === 0) {
                                return p[i][fun]();
                            }
                            s = w[i] / d;
                            d -= 1;
                            num += p[i][fun]() * s;
                            denom += s;
                        }
                        return num / denom;
                    };
                },

                xfct = makeFct('X'),
                yfct = makeFct('Y');

            return [xfct, yfct, 0, function () {
                return p.length - 1;
            }];
        },

        /**
         * Calculates second derivatives at the given knots.
         * @param {Array} x x values of knots
         * @param {Array} y y values of knots
         * @returns {Array} Second derivatives of the interpolated function at the knots.
         * @see #splineEval
         * @memberof JXG.Math.Numerics
         */
        splineDef: function (x, y) {
            var pair, i, l,
                n = Math.min(x.length, y.length),
                diag = [],
                z = [],
                data = [],
                dx = [],
                delta = [],
                F = [];

            if (n === 2) {
                return [0, 0];
            }

            for (i = 0; i < n; i++) {
                pair = {X: x[i], Y: y[i]};
                data.push(pair);
            }
            data.sort(function (a, b) {
                return a.X - b.X;
            });
            for (i = 0; i < n; i++) {
                x[i] = data[i].X;
                y[i] = data[i].Y;
            }

            for (i = 0; i < n - 1; i++) {
                dx.push(x[i + 1] - x[i]);
            }
            for (i = 0; i < n - 2; i++) {
                delta.push(6 * (y[i + 2] - y[i + 1]) / (dx[i + 1]) - 6 * (y[i + 1] - y[i]) / (dx[i]));
            }

            // ForwardSolve
            diag.push(2 * (dx[0] + dx[1]));
            z.push(delta[0]);

            for (i = 0; i < n - 3; i++) {
                l = dx[i + 1] / diag[i];
                diag.push(2 * (dx[i + 1] + dx[i + 2]) - l * dx[i + 1]);
                z.push(delta[i + 1] - l * z[i]);
            }

            // BackwardSolve
            F[n - 3] = z[n - 3] / diag[n - 3];
            for (i = n - 4; i >= 0; i--) {
                F[i] = (z[i] - (dx[i + 1] * F[i + 1])) / diag[i];
            }

            // Generate f''-Vector
            for (i = n - 3; i >= 0; i--) {
                F[i + 1] = F[i];
            }

            // natural cubic spline
            F[0] = 0;
            F[n - 1] = 0;

            return F;
        },

        /**
         * Evaluate points on spline.
         * @param {Number,Array} x0 A single float value or an array of values to evaluate
         * @param {Array} x x values of knots
         * @param {Array} y y values of knots
         * @param {Array} F Second derivatives at knots, calculated by {@link JXG.Math.Numerics.splineDef}
         * @see #splineDef
         * @returns {Number,Array} A single value or an array, depending on what is given as x0.
         * @memberof JXG.Math.Numerics
         */
        splineEval: function (x0, x, y, F) {
            var i, j, a, b, c, d, x_,
                n = Math.min(x.length, y.length),
                l = 1,
                asArray = false,
                y0 = [];

            // number of points to be evaluated
            if (Type.isArray(x0)) {
                l = x0.length;
                asArray = true;
            } else {
                x0 = [x0];
            }

            for (i = 0; i < l; i++) {
                // is x0 in defining interval?
                if ((x0[i] < x[0]) || (x[i] > x[n - 1])) {
                    return NaN;
                }

                // determine part of spline in which x0 lies
                for (j = 1; j < n; j++) {
                    if (x0[i] <= x[j]) {
                        break;
                    }
                }

                j -= 1;

                // we're now in the j-th partial interval, i.e. x[j] < x0[i] <= x[j+1];
                // determine the coefficients of the polynomial in this interval
                a = y[j];
                b = (y[j + 1] - y[j]) / (x[j + 1] - x[j]) - (x[j + 1] - x[j]) / 6 * (F[j + 1] + 2 * F[j]);
                c = F[j] / 2;
                d = (F[j + 1] - F[j]) / (6 * (x[j + 1] - x[j]));
                // evaluate x0[i]
                x_ = x0[i] - x[j];
                //y0.push(a + b*x_ + c*x_*x_ + d*x_*x_*x_);
                y0.push(a + (b + (c + d * x_) * x_) * x_);
            }

            if (asArray) {
                return y0;
            }

            return y0[0];
        },

        /**
         * Generate a string containing the function term of a polynomial.
         * @param {Array} coeffs Coefficients of the polynomial. The position i belongs to x^i.
         * @param {Number} deg Degree of the polynomial
         * @param {String} varname Name of the variable (usually 'x')
         * @param {Number} prec Precision
         * @returns {String} A string containg the function term of the polynomial.
         * @memberof JXG.Math.Numerics
         */
        generatePolynomialTerm: function (coeffs, deg, varname, prec) {
            var i, t = [];

            for (i = deg; i >= 0; i--) {
                t = t.concat(['(', coeffs[i].toPrecision(prec), ')']);

                if (i > 1) {
                    t = t.concat(['*', varname, '<sup>', i, '<', '/sup> + ']);
                } else if (i === 1) {
                    t = t.concat(['*', varname, ' + ']);
                }
            }

            return t.join('');
        },

        /**
         * Computes the polynomial through a given set of coordinates in Lagrange form.
         * Returns the Lagrange polynomials, see
         * Jean-Paul Berrut, Lloyd N. Trefethen: Barycentric Lagrange Interpolation,
         * SIAM Review, Vol 46, No 3, (2004) 501-517.
         * <p>
         * It possesses the method getTerm() which returns the string containing the function term of the polynomial.
         * @param {Array} p Array of JXG.Points
         * @returns {function} A function of one parameter which returns the value of the polynomial, whose graph runs through the given points.
         * @memberof JXG.Math.Numerics
         *
         * @example
         * var p = [];
         * p[0] = board.create('point', [-1,2], {size:4});
         * p[1] = board.create('point', [0,3], {size:4});
         * p[2] = board.create('point', [1,1], {size:4});
         * p[3] = board.create('point', [3,-1], {size:4});
         * var f = JXG.Math.Numerics.lagrangePolynomial(p);
         * var graph = board.create('functiongraph', [f,-10, 10], {strokeWidth:3});
         *
         * </pre><div id="JXGc058aa6b-74d4-41e1-af94-df06169a2d89" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXGc058aa6b-74d4-41e1-af94-df06169a2d89',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var p = [];
         *     p[0] = board.create('point', [-1,2], {size:4});
         *     p[1] = board.create('point', [0,3], {size:4});
         *     p[2] = board.create('point', [1,1], {size:4});
         *     p[3] = board.create('point', [3,-1], {size:4});
         *     var f = JXG.Math.Numerics.lagrangePolynomial(p);
         *     var graph = board.create('functiongraph', [f,-10, 10], {strokeWidth:3});
         *
         *     })();
         *
         * </script><pre>
         *
         * @example
         * var points = [];
         * points[0] = board.create('point', [-1,2], {size:4});
         * points[1] = board.create('point', [0, 0], {size:4});
         * points[2] = board.create('point', [2, 1], {size:4});
         *
         * var f = JXG.Math.Numerics.lagrangePolynomial(points);
         * var graph = board.create('functiongraph', [f,-10, 10], {strokeWidth:3});
         * var txt = board.create('text', [-3, -4,  () => f.getTerm(2, 't', ' * ')], {fontSize: 16});
         *
         * </pre><div id="JXG73fdaf12-e257-4374-b488-ae063e4eecbb" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG73fdaf12-e257-4374-b488-ae063e4eecbb',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var points = [];
         *     points[0] = board.create('point', [-1,2], {size:4});
         *     points[1] = board.create('point', [0, 0], {size:4});
         *     points[2] = board.create('point', [2, 1], {size:4});
         *
         *     var f = JXG.Math.Numerics.lagrangePolynomial(points);
         *     var graph = board.create('functiongraph', [f,-10, 10], {strokeWidth:3});
         *     var txt = board.create('text', [-3, -4,  () => f.getTerm(2, 't', ' * ')], {fontSize: 16});
         *
         *     })();
         *
         * </script><pre>
         *
         */
        lagrangePolynomial: function (p) {
            var w = [],
                that = this,
                /** @ignore */
                fct = function (x, suspendedUpdate) {
                    var i, // j,
                        k, xi, s, //M,
                        len = p.length,
                        num = 0,
                        denom = 0;

                    if (!suspendedUpdate) {
                        for (i = 0; i < len; i++) {
                            w[i] = 1.0;
                            xi = p[i].X();

                            for (k = 0; k < len; k++) {
                                if (k !== i) {
                                    w[i] *= (xi - p[k].X());
                                }
                            }

                            w[i] = 1 / w[i];
                        }

                        // M = [];
                        // for (k = 0; k < len; k++) {
                        //     M.push([1]);
                        // }
                    }

                    for (i = 0; i < len; i++) {
                        xi = p[i].X();

                        if (x === xi) {
                            return p[i].Y();
                        }

                        s = w[i] / (x - xi);
                        denom += s;
                        num += s * p[i].Y();
                    }

                    return num / denom;
                };

            /**
             * Get the term of the Lagrange polynomial as string.
             * Calls {@link JXG.Math.Numerics#lagrangePolynomialTerm}.
             *
             * @name JXG.Math.Numerics#lagrangePolynomial.getTerm
             * @param {Number} digits Number of digits of the coefficients
             * @param {String} param Variable name
             * @param {String} dot Dot symbol
             * @returns {String} containing the term of Lagrange polynomial as string.
             * @see JXG.Math.Numerics#lagrangePolynomialTerm
             * @example
             * var points = [];
             * points[0] = board.create('point', [-1,2], {size:4});
             * points[1] = board.create('point', [0, 0], {size:4});
             * points[2] = board.create('point', [2, 1], {size:4});
             *
             * var f = JXG.Math.Numerics.lagrangePolynomial(points);
             * var graph = board.create('functiongraph', [f,-10, 10], {strokeWidth:3});
             * var txt = board.create('text', [-3, -4,  () => f.getTerm(2, 't', ' * ')], {fontSize: 16});
             *
             * </pre><div id="JXG73fdaf12-e257-4374-b488-ae063e4eeccf" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG73fdaf12-e257-4374-b488-ae063e4eeccf',
             *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
             *     var points = [];
             *     points[0] = board.create('point', [-1,2], {size:4});
             *     points[1] = board.create('point', [0, 0], {size:4});
             *     points[2] = board.create('point', [2, 1], {size:4});
             *
             *     var f = JXG.Math.Numerics.lagrangePolynomial(points);
             *     var graph = board.create('functiongraph', [f,-10, 10], {strokeWidth:3});
             *     var txt = board.create('text', [-3, -4,  () => f.getTerm(2, 't', ' * ')], {fontSize: 16});
             *
             *     })();
             *
             * </script><pre>
             *
             */
            fct.getTerm = function(digits, param, dot) {
                return that.lagrangePolynomialTerm(p, digits, param, dot)();
            };

            return fct;
        },
        // fct.getTerm = that.lagrangePolynomialTerm(p, 2, 'x');

        /**
         * Determine the Lagrange polynomial through an array of points and
         * return the term of the polynomial as string.
         *
         * @param {Array} points Array of JXG.Points
         * @param {Number} digits Number of decimal digits of the coefficients
         * @param {String} param Name of the parameter. Default: 'x'.
         * @param {String} dot Multiplication symbol. Default: ' * '.
         * @returns {String} containing the Lagrange polynomial through
         *    the supplied points.
         * @memberof JXG.Math.Numerics
         *
         * @example
         * var points = [];
         * points[0] = board.create('point', [-1,2], {size:4});
         * points[1] = board.create('point', [0, 0], {size:4});
         * points[2] = board.create('point', [2, 1], {size:4});
         *
         * var f = JXG.Math.Numerics.lagrangePolynomial(points);
         * var graph = board.create('functiongraph', [f,-10, 10], {strokeWidth:3});
         *
         * var f_txt = JXG.Math.Numerics.lagrangePolynomialTerm(points, 2, 't', ' * ');
         * var txt = board.create('text', [-3, -4, f_txt], {fontSize: 16});
         *
         * </pre><div id="JXGd45e9e96-7526-486d-aa43-e1178d5f2baa" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXGd45e9e96-7526-486d-aa43-e1178d5f2baa',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var points = [];
         *     points[0] = board.create('point', [-1,2], {size:4});
         *     points[1] = board.create('point', [0, 0], {size:4});
         *     points[2] = board.create('point', [2, 1], {size:4});
         *
         *     var f = JXG.Math.Numerics.lagrangePolynomial(points);
         *     var graph = board.create('functiongraph', [f,-10, 10], {strokeWidth:3});
         *
         *     var f_txt = JXG.Math.Numerics.lagrangePolynomialTerm(points, 2, 't', ' * ');
         *     var txt = board.create('text', [-3, -4, f_txt], {fontSize: 16});
         *
         *     })();
         *
         * </script><pre>
         *
         */
        lagrangePolynomialTerm: function(points, digits, param, dot) {
            return function() {
                var len = points.length,
                    zeroes = [],
                    coeffs = [],
                    coeffs_sum = [],
                    isLeading = true,
                    n, t,
                    i, j, c, p;

                param = param || 'x';
                if (dot === undefined) {
                    dot = ' * ';
                }

                n = len - 1;  // (Max) degree of the polynomial
                for (j = 0; j < len; j++) {
                    coeffs_sum[j] = 0;
                }

                for (i = 0; i < len; i++) {
                    c = points[i].Y();
                    p = points[i].X();
                    zeroes = [];
                    for (j = 0; j < len; j++) {
                        if (j !== i) {
                            c /= p - points[j].X();
                            zeroes.push(points[j].X());
                        }
                    }
                    coeffs = [1].concat(Mat.Vieta(zeroes));
                    for (j = 0; j < coeffs.length; j++) {
                        coeffs_sum[j] += (j%2===1?(-1):1) * coeffs[j] * c;
                    }
                }

                t = '';
                for (j = 0; j < coeffs_sum.length; j++) {
                    c = coeffs_sum[j];
                    if (Math.abs(c) < Mat.eps) {
                        continue;
                    }
                    if (JXG.exists(digits)) {
                        c = Env._round10(c, -digits);
                    }
                    if (isLeading) {
                        t += (c > 0) ? (c) : ('-' + (-c));
                        isLeading = false;
                    } else {
                        t += (c > 0) ? (' + ' + c) : (' - ' + (-c));
                    }

                    if (n - j > 1) {
                        t += dot + param + '^' + (n - j);
                    } else if (n - j === 1) {
                        t += dot + param;
                    }
                }
                return t; // board.jc.manipulate('f = map(x) -> ' + t + ';');
            };
        },

        /**
         * Determine the coefficients of a cardinal spline polynom, See
         * http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections
         * @param  {Number} x1 point 1
         * @param  {Number} x2 point 2
         * @param  {Number} t1 tangent slope 1
         * @param  {Number} t2 tangent slope 2
         * @return {Array}    coefficents array c for the polynomial t maps to
         * c[0] + c[1]*t + c[2]*t*t + c[3]*t*t*t
         */
        _initCubicPoly: function(x1, x2, t1, t2) {
            return [
                x1,
                t1,
                -3 * x1 + 3 * x2 - 2 * t1 - t2,
                2 * x1 -  2 * x2 + t1 + t2
            ];
        },

        /**
         * Computes the cubic cardinal spline curve through a given set of points. The curve
         * is uniformly parametrized.
         * Two artificial control points at the beginning and the end are added.
         *
         * The implementation (especially  the centripetal parametrization) is from
         * http://stackoverflow.com/questions/9489736/catmull-rom-curve-with-no-cusps-and-no-self-intersections .
         * @param {Array} points Array consisting of JXG.Points.
         * @param {Number|Function} tau The tension parameter, either a constant number or a function returning a number. This number is between 0 and 1.
         * tau=1/2 give Catmull-Rom splines.
         * @param {String} type (Optional) parameter which allows to choose between "uniform" (default) and
         * "centripetal" parameterization. Thus the two possible values are "uniform" or "centripetal".
         * @returns {Array} An Array consisting of four components: Two functions each of one parameter t
         * which return the x resp. y coordinates of the Catmull-Rom-spline curve in t, a zero value,
         * and a function simply returning the length of the points array
         * minus three.
         * @memberof JXG.Math.Numerics
        */
        CardinalSpline: function (points, tau_param, type) {
            var p,
                coeffs = [],
                makeFct,
                tau, _tau,
                that = this;

            if (Type.isFunction(tau_param)) {
                _tau = tau_param;
            } else {
                _tau = function () { return tau_param; };
            }

            if (type === undefined) {
                type = 'uniform';
            }

            /** @ignore */
            makeFct = function (which) {
                return function (t, suspendedUpdate) {
                    var s, c,
                        // control point at the beginning and at the end
                        first, last,
                        t1, t2, dt0, dt1, dt2,
                        // dx, dy,
                        len;

                    if (points.length < 2) {
                        return NaN;
                    }

                    if (!suspendedUpdate) {
                        tau = _tau();

                        // New point list p: [first, points ..., last]
                        first = {
                            X: function () { return 2 * points[0].X() - points[1].X(); },
                            Y: function () { return 2 * points[0].Y() - points[1].Y(); },
                            Dist: function(p) {
                                var dx = this.X() - p.X(),
                                    dy = this.Y() - p.Y();
                                return Math.sqrt(dx * dx + dy * dy);
                            }
                        };

                        last = {
                            X: function () { return 2 * points[points.length - 1].X() - points[points.length - 2].X(); },
                            Y: function () { return 2 * points[points.length - 1].Y() - points[points.length - 2].Y(); },
                            Dist: function(p) {
                                var dx = this.X() - p.X(),
                                    dy = this.Y() - p.Y();
                                return Math.sqrt(dx * dx + dy * dy);
                            }
                        };

                        p = [first].concat(points, [last]);
                        len = p.length;

                        coeffs[which] = [];

                        for (s = 0; s < len - 3; s++) {
                            if (type === 'centripetal') {
                                // The order is important, since p[0].coords === undefined
                                dt0 = p[s].Dist(p[s + 1]);
                                dt1 = p[s + 2].Dist(p[s + 1]);
                                dt2 = p[s + 3].Dist(p[s + 2]);

                                dt0 = Math.sqrt(dt0);
                                dt1 = Math.sqrt(dt1);
                                dt2 = Math.sqrt(dt2);

                                if (dt1 < Mat.eps) { dt1 = 1.0; }
                                if (dt0 < Mat.eps) { dt0 = dt1; }
                                if (dt2 < Mat.eps) { dt2 = dt1; }

                                t1 = (p[s + 1][which]() - p[s][which]()) / dt0 -
                                     (p[s + 2][which]() - p[s][which]()) / (dt1 + dt0) +
                                     (p[s + 2][which]() - p[s + 1][which]()) / dt1;

                                t2 = (p[s + 2][which]() - p[s + 1][which]()) / dt1 -
                                     (p[s + 3][which]() - p[s + 1][which]()) / (dt2 + dt1) +
                                     (p[s + 3][which]() - p[s + 2][which]()) / dt2;

                                t1 *= dt1;
                                t2 *= dt1;

                                coeffs[which][s] = that._initCubicPoly(
                                     p[s + 1][which](),
                                     p[s + 2][which](),
                                     tau * t1,
                                     tau * t2
                                );
                            } else {
                                coeffs[which][s] = that._initCubicPoly(
                                    p[s + 1][which](),
                                    p[s + 2][which](),
                                    tau * (p[s + 2][which]() - p[s][which]()),
                                    tau * (p[s + 3][which]() - p[s + 1][which]())
                                );
                            }
                        }
                    }

                    if (isNaN(t)) {
                        return NaN;
                    }

                    len = points.length;
                    // This is necessary for our advanced plotting algorithm:
                    if (t <= 0.0) {
                        return points[0][which]();
                    }
                    if (t >= len) {
                        return points[len - 1][which]();
                    }

                    s = Math.floor(t);
                    if (s === t) {
                        return points[s][which]();
                    }

                    t -= s;
                    c = coeffs[which][s];
                    if (c === undefined) {
                        return NaN;
                    }

                    return (((c[3] * t + c[2]) * t + c[1]) * t + c[0]);
                };
            };

            return [makeFct('X'), makeFct('Y'), 0,
                function () {
                    return points.length - 1;
                }];
        },

        /**
         * Computes the cubic Catmull-Rom spline curve through a given set of points. The curve
         * is uniformly parametrized. The curve is the cardinal spline curve for tau=0.5.
         * Two artificial control points at the beginning and the end are added.
         * @param {Array} points Array consisting of JXG.Points.
         * @param {String} type (Optional) parameter which allows to choose between "uniform" (default) and
         * "centripetal" parameterization. Thus the two possible values are "uniform" or "centripetal".
         * @returns {Array} An Array consisting of four components: Two functions each of one parameter t
         * which return the x resp. y coordinates of the Catmull-Rom-spline curve in t, a zero value, and a function simply
         * returning the length of the points array minus three.
         * @memberof JXG.Math.Numerics
        */
        CatmullRomSpline: function (points, type) {
            return this.CardinalSpline(points, 0.5, type);
        },

        /**
         * Computes the regression polynomial of a given degree through a given set of coordinates.
         * Returns the regression polynomial function.
         * @param {Number,function,Slider} degree number, function or slider.
         * Either
         * @param {Array} dataX Array containing either the x-coordinates of the data set or both coordinates in
         * an array of {@link JXG.Point}s or {@link JXG.Coords}.
         * In the latter case, the <tt>dataY</tt> parameter will be ignored.
         * @param {Array} dataY Array containing the y-coordinates of the data set,
         * @returns {function} A function of one parameter which returns the value of the regression polynomial of the given degree.
         * It possesses the method getTerm() which returns the string containing the function term of the polynomial.
         * @memberof JXG.Math.Numerics
         */
        regressionPolynomial: function (degree, dataX, dataY) {
            var coeffs, deg, dX, dY, inputType, fct,
                term = '';

            // Slider
            if (Type.isPoint(degree) && Type.isFunction(degree.Value)) {
                /** @ignore */
                deg = function () {
                    return degree.Value();
                };
            // function
            } else if (Type.isFunction(degree)) {
                deg = degree;
            // number
            } else if (Type.isNumber(degree)) {
                /** @ignore */
                deg = function () {
                    return degree;
                };
            } else {
                throw new Error("JSXGraph: Can't create regressionPolynomial from degree of type'" + (typeof degree) + "'.");
            }

            // Parameters degree, dataX, dataY
            if (arguments.length === 3 && Type.isArray(dataX) && Type.isArray(dataY)) {
                inputType = 0;
            // Parameters degree, point array
            } else if (arguments.length === 2 && Type.isArray(dataX) && dataX.length > 0 && Type.isPoint(dataX[0])) {
                inputType = 1;
            } else if (arguments.length === 2 && Type.isArray(dataX) && dataX.length > 0 && dataX[0].usrCoords && dataX[0].scrCoords) {
                inputType = 2;
            } else {
                throw new Error("JSXGraph: Can't create regressionPolynomial. Wrong parameters.");
            }

            /** @ignore */
            fct = function (x, suspendedUpdate) {
                var i, j, M, MT, y, B, c, s, d,
                    // input data
                    len = dataX.length;

                d = Math.floor(deg());

                if (!suspendedUpdate) {
                    // point list as input
                    if (inputType === 1) {
                        dX = [];
                        dY = [];

                        for (i = 0; i < len; i++) {
                            dX[i] = dataX[i].X();
                            dY[i] = dataX[i].Y();
                        }
                    }

                    if (inputType === 2) {
                        dX = [];
                        dY = [];

                        for (i = 0; i < len; i++) {
                            dX[i] = dataX[i].usrCoords[1];
                            dY[i] = dataX[i].usrCoords[2];
                        }
                    }

                    // check for functions
                    if (inputType === 0) {
                        dX = [];
                        dY = [];

                        for (i = 0; i < len; i++) {
                            if (Type.isFunction(dataX[i])) {
                                dX.push(dataX[i]());
                            } else {
                                dX.push(dataX[i]);
                            }

                            if (Type.isFunction(dataY[i])) {
                                dY.push(dataY[i]());
                            } else {
                                dY.push(dataY[i]);
                            }
                        }
                    }

                    M = [];

                    for (j = 0; j < len; j++) {
                        M.push([1]);
                    }

                    for (i = 1; i <= d; i++) {
                        for (j = 0; j < len; j++) {
                            M[j][i] = M[j][i - 1] * dX[j];
                        }
                    }

                    y = dY;
                    MT = Mat.transpose(M);
                    B = Mat.matMatMult(MT, M);
                    c = Mat.matVecMult(MT, y);
                    coeffs = Mat.Numerics.Gauss(B, c);
                    term = Mat.Numerics.generatePolynomialTerm(coeffs, d, 'x', 3);
                }

                // Horner's scheme to evaluate polynomial
                s = coeffs[d];

                for (i = d - 1; i >= 0; i--) {
                    s = (s * x + coeffs[i]);
                }

                return s;
            };

            fct.getTerm = function () {
                return term;
            };

            return fct;
        },

        /**
         * Computes the cubic Bezier curve through a given set of points.
         * @param {Array} points Array consisting of 3*k+1 {@link JXG.Points}.
         * The points at position k with k mod 3 = 0 are the data points,
         * points at position k with k mod 3 = 1 or 2 are the control points.
         * @returns {Array} An array consisting of two functions of one parameter t which return the
         * x resp. y coordinates of the Bezier curve in t, one zero value, and a third function accepting
         * no parameters and returning one third of the length of the points.
         * @memberof JXG.Math.Numerics
         */
        bezier: function (points) {
            var len, flen,
                /** @ignore */
                makeFct = function (which) {
                    return function (t, suspendedUpdate) {
                        var z = Math.floor(t) * 3,
                            t0 = t % 1,
                            t1 = 1 - t0;

                        if (!suspendedUpdate) {
                            flen = 3 * Math.floor((points.length - 1) / 3);
                            len = Math.floor(flen / 3);
                        }

                        if (t < 0) {
                            return points[0][which]();
                        }

                        if (t >= len) {
                            return points[flen][which]();
                        }

                        if (isNaN(t)) {
                            return NaN;
                        }

                        return t1 * t1 * (t1 * points[z][which]() + 3 * t0 * points[z + 1][which]()) + (3 * t1 * points[z + 2][which]() + t0 * points[z + 3][which]()) * t0 * t0;
                    };
                };

            return [makeFct('X'), makeFct('Y'), 0,
                function () {
                    return Math.floor(points.length / 3);
                }];
        },

        /**
         * Computes the B-spline curve of order k (order = degree+1) through a given set of points.
         * @param {Array} points Array consisting of JXG.Points.
         * @param {Number} order Order of the B-spline curve.
         * @returns {Array} An Array consisting of four components: Two functions each of one parameter t
         * which return the x resp. y coordinates of the B-spline curve in t, a zero value, and a function simply
         * returning the length of the points array minus one.
         * @memberof JXG.Math.Numerics
         */
        bspline: function (points, order) {
            var knots,
                _knotVector = function (n, k) {
                    var j,
                        kn = [];

                    for (j = 0; j < n + k + 1; j++) {
                        if (j < k) {
                            kn[j] = 0.0;
                        } else if (j <= n) {
                            kn[j] = j - k + 1;
                        } else {
                            kn[j] = n - k + 2;
                        }
                    }

                    return kn;
                },

                _evalBasisFuncs = function (t, kn, k, s) {
                    var i, j, a, b, den,
                        N = [];

                    if (kn[s] <= t && t < kn[s + 1]) {
                        N[s] = 1;
                    } else {
                        N[s] = 0;
                    }

                    for (i = 2; i <= k; i++) {
                        for (j = s - i + 1; j <= s; j++) {
                            if (j <= s - i + 1 || j < 0) {
                                a = 0.0;
                            } else {
                                a = N[j];
                            }

                            if (j >= s) {
                                b = 0.0;
                            } else {
                                b = N[j + 1];
                            }

                            den = kn[j + i - 1] - kn[j];

                            if (den === 0) {
                                N[j] = 0;
                            } else {
                                N[j] = (t - kn[j]) / den * a;
                            }

                            den = kn[j + i] - kn[j + 1];

                            if (den !== 0) {
                                N[j] += (kn[j + i] - t) / den * b;
                            }
                        }
                    }
                    return N;
                },
                /** @ignore */
                makeFct = function (which) {
                    return function (t, suspendedUpdate) {
                        var y, j, s, N = [],
                            len = points.length,
                            n = len - 1,
                            k = order;

                        if (n <= 0) {
                            return NaN;
                        }

                        if (n + 2 <= k) {
                            k = n + 1;
                        }

                        if (t <= 0) {
                            return points[0][which]();
                        }

                        if (t >= n - k + 2) {
                            return points[n][which]();
                        }

                        s = Math.floor(t) + k - 1;
                        knots = _knotVector(n, k);
                        N = _evalBasisFuncs(t, knots, k, s);

                        y = 0.0;
                        for (j = s - k + 1; j <= s; j++) {
                            if (j < len && j >= 0) {
                                y += points[j][which]() * N[j];
                            }
                        }

                        return y;
                    };
                };

            return [makeFct('X'), makeFct('Y'), 0,
                function () {
                    return points.length - 1;
                }];
        },

        /**
         * Numerical (symmetric) approximation of derivative. suspendUpdate is piped through,
         * see {@link JXG.Curve#updateCurve}
         * and {@link JXG.Curve#hasPoint}.
         * @param {function} f Function in one variable to be differentiated.
         * @param {object} [obj] Optional object that is treated as "this" in the function body. This is useful, if the function is a
         * method of an object and contains a reference to its parent object via "this".
         * @returns {function} Derivative function of a given function f.
         * @memberof JXG.Math.Numerics
         */
        D: function (f, obj) {
            if (!Type.exists(obj)) {
                return function (x, suspendedUpdate) {
                    var h = 0.00001,
                        h2 = (h * 2.0);

                    // Experiments with Richardsons rule
                    /*
                    var phi = (f(x + h, suspendedUpdate) - f(x - h, suspendedUpdate)) / h2;
                    var phi2;
                    h *= 0.5;
                    h2 *= 0.5;
                    phi2 = (f(x + h, suspendedUpdate) - f(x - h, suspendedUpdate)) / h2;

                    return phi2 + (phi2 - phi) / 3.0;
                    */
                    return (f(x + h, suspendedUpdate) - f(x - h, suspendedUpdate)) / h2;
                };
            }

            return function (x, suspendedUpdate) {
                var h = 0.00001,
                    h2 = (h * 2.0);

                return (f.apply(obj, [x + h, suspendedUpdate]) - f.apply(obj, [x - h, suspendedUpdate])) / h2;
            };
        },

        /**
         * Evaluate the function term for {@see #riemann}.
         * @private
         * @param {Number} x function argument
         * @param {function} f JavaScript function returning a number
         * @param {String} type Name of the Riemann sum type, e.g. 'lower', see {@see #riemann}.
         * @param {Number} delta Width of the bars in user coordinates
         * @returns {Number} Upper (delta > 0) or lower (delta < 0) value of the bar containing x of the Riemann sum.
         *
         * @memberof JXG.Math.Numerics
         */
        _riemannValue: function (x, f, type, delta) {
            var y, y1, x1, delta1;

            if (delta < 0) { // delta is negative if the lower function term is evaluated
                if (type !== 'trapezoidal') {
                    x = x + delta;
                }
                delta *= -1;
                if (type === 'lower') {
                    type = 'upper';
                } else if (type === 'upper') {
                    type = 'lower';
                }
            }

            delta1 = delta * 0.01; // for 'lower' and 'upper'

            if (type === 'right') {
                y = f(x + delta);
            } else if (type === 'middle') {
                y = f(x + delta * 0.5);
            } else if (type === 'left' || type === 'trapezoidal') {
                y = f(x);
            } else if (type === 'lower') {
                y = f(x);

                for (x1 = x + delta1; x1 <= x + delta; x1 += delta1) {
                    y1 = f(x1);

                    if (y1 < y) {
                        y = y1;
                    }
                }

                y1 = f(x + delta);
                if (y1 < y) {
                    y = y1;
                }
            } else if (type === 'upper') {
                y = f(x);

                for (x1 = x + delta1; x1 <= x + delta; x1 += delta1) {
                    y1 = f(x1);
                    if (y1 > y) {
                        y = y1;
                    }
                }

                y1 = f(x + delta);
                if (y1 > y) {
                    y = y1;
                }
            } else if (type === 'random') {
                y = f(x + delta * Math.random());
            } else if (type === 'simpson') {
                y = (f(x) + 4 * f(x + delta * 0.5) + f(x + delta)) / 6.0;
            } else {
                y = f(x);  // default is lower
            }

            return y;
        },

        /**
         * Helper function to create curve which displays Riemann sums.
         * Compute coordinates for the rectangles showing the Riemann sum.
         * @param {Function,Array} f Function or array of two functions.
         * If f is a function the integral of this function is approximated by the Riemann sum.
         * If f is an array consisting of two functions the area between the two functions is filled
         * by the Riemann sum bars.
         * @param {Number} n number of rectangles.
         * @param {String} type Type of approximation. Possible values are: 'left', 'right', 'middle', 'lower', 'upper', 'random', 'simpson', or 'trapezoidal'.
         * @param {Number} start Left border of the approximation interval
         * @param {Number} end Right border of the approximation interval
         * @returns {Array} An array of two arrays containing the x and y coordinates for the rectangles showing the Riemann sum. This
         * array may be used as parent array of a {@link JXG.Curve}. The third parameteris the riemann sum, i.e. the sum of the volumes of all
         * rectangles.
         * @memberof JXG.Math.Numerics
         */
        riemann: function (gf, n, type, start, end) {
            var i, delta,
                xarr = [],
                yarr = [],
                j = 0,
                x = start, y,
                sum = 0,
                f, g,
                ylow, yup;

            if (Type.isArray(gf)) {
                g = gf[0];
                f = gf[1];
            } else {
                f = gf;
            }

            n = Math.floor(n);

            if (n <= 0) {
                return [xarr, yarr, sum];
            }

            delta = (end - start) / n;

            // Upper bar ends
            for (i = 0; i < n; i++) {
                y = this._riemannValue(x, f, type, delta);
                xarr[j] = x;
                yarr[j] = y;

                j += 1;
                x += delta;
                if (type === 'trapezoidal') {
                    y = f(x);
                }
                xarr[j] = x;
                yarr[j] = y;

                j += 1;
            }

            // Lower bar ends
            for (i = 0; i < n; i++) {
                if (g) {
                    y = this._riemannValue(x, g, type, -delta);
                } else {
                    y = 0.0;
                }
                xarr[j] = x;
                yarr[j] = y;

                j += 1;
                x -= delta;
                if (type === 'trapezoidal' && g) {
                    y = g(x);
                }
                xarr[j] = x;
                yarr[j] = y;

                // Add the area of the bar to 'sum'
                if (type !== 'trapezoidal') {
                    ylow = y;
                    yup = yarr[2 * (n - 1) - 2 * i];
                } else {
                    yup = 0.5 * (f(x + delta) + f(x));
                    if (g) {
                        ylow = 0.5 * (g(x + delta) + g(x));
                    } else {
                        ylow = 0.0;
                    }
                }
                sum += (yup - ylow) * delta;

                // Draw the vertical lines
                j += 1;
                xarr[j] = x;
                yarr[j] = yarr[2 * (n - 1) - 2 * i];

                j += 1;
            }

            return [xarr, yarr, sum];
        },

        /**
         * Approximate the integral by Riemann sums.
         * Compute the area described by the riemann sum rectangles.
         *
         * If there is an element of type {@link Riemannsum}, then it is more efficient
         * to use the method JXG.Curve.Value() of this element instead.
         *
         * @param {Function_Array} f Function or array of two functions.
         * If f is a function the integral of this function is approximated by the Riemann sum.
         * If f is an array consisting of two functions the area between the two functions is approximated
         * by the Riemann sum.
         * @param {Number} n number of rectangles.
         * @param {String} type Type of approximation. Possible values are: 'left', 'right', 'middle', 'lower', 'upper', 'random', 'simpson' or 'trapezoidal'.
         *
         * @param {Number} start Left border of the approximation interval
         * @param {Number} end Right border of the approximation interval
         * @returns {Number} The sum of the areas of the rectangles.
         * @memberof JXG.Math.Numerics
         */
        riemannsum: function (f, n, type, start, end) {
            JXG.deprecated('Numerics.riemannsum()', 'Numerics.riemann()');
            return this.riemann(f, n, type, start, end)[2];
        },

        /**
         * Solve initial value problems numerically using Runge-Kutta-methods.
         * See {@link http://en.wikipedia.org/wiki/Runge-Kutta_methods} for more information on the algorithm.
         * @param {object,String} butcher Butcher tableau describing the Runge-Kutta method to use. This can be either a string describing
         * a Runge-Kutta method with a Butcher tableau predefined in JSXGraph like 'euler', 'heun', 'rk4' or an object providing the structure
         * <pre>
         * {
         *     s: &lt;Number&gt;,
         *     A: &lt;matrix&gt;,
         *     b: &lt;Array&gt;,
         *     c: &lt;Array&gt;
         * }
         * </pre>
         * which corresponds to the Butcher tableau structure shown here: http://en.wikipedia.org/w/index.php?title=List_of_Runge%E2%80%93Kutta_methods&oldid=357796696
         * @param {Array} x0 Initial value vector. If the problem is of one-dimensional, the initial value also has to be given in an array.
         * @param {Array} I Interval on which to integrate.
         * @param {Number} N Number of evaluation points.
         * @param {function} f Function describing the right hand side of the first order ordinary differential equation, i.e. if the ode
         * is given by the equation <pre>dx/dt = f(t, x(t)).</pre> So f has to take two parameters, a number <tt>t</tt> and a
         * vector <tt>x</tt>, and has to return a vector of the same dimension as <tt>x</tt> has.
         * @returns {Array} An array of vectors describing the solution of the ode on the given interval I.
         * @example
         * // A very simple autonomous system dx(t)/dt = x(t);
         * function f(t, x) {
         *     return x;
         * }
         *
         * // Solve it with initial value x(0) = 1 on the interval [0, 2]
         * // with 20 evaluation points.
         * var data = JXG.Math.Numerics.rungeKutta('heun', [1], [0, 2], 20, f);
         *
         * // Prepare data for plotting the solution of the ode using a curve.
         * var dataX = [];
         * var dataY = [];
         * var h = 0.1;        // (I[1] - I[0])/N  = (2-0)/20
         * for(var i=0; i&lt;data.length; i++) {
         *     dataX[i] = i*h;
         *     dataY[i] = data[i][0];
         * }
         * var g = board.create('curve', [dataX, dataY], {strokeWidth:'2px'});
         * </pre><div class="jxgbox" id="JXGd2432d04-4ef7-4159-a90b-a2eb8d38c4f6" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         * var board = JXG.JSXGraph.initBoard('JXGd2432d04-4ef7-4159-a90b-a2eb8d38c4f6', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
         * function f(t, x) {
         *     // we have to copy the value.
         *     // return x; would just return the reference.
         *     return [x[0]];
         * }
         * var data = JXG.Math.Numerics.rungeKutta('heun', [1], [0, 2], 20, f);
         * var dataX = [];
         * var dataY = [];
         * var h = 0.1;
         * for(var i=0; i<data.length; i++) {
         *     dataX[i] = i*h;
         *     dataY[i] = data[i][0];
         * }
         * var g = board.create('curve', [dataX, dataY], {strokeColor:'red', strokeWidth:'2px'});
         * </script><pre>
         * @memberof JXG.Math.Numerics
         */
        rungeKutta: function (butcher, x0, I, N, f) {
            var e, i, j, k, l, s,
                x = [],
                y = [],
                h = (I[1] - I[0]) / N,
                t = I[0],
                dim = x0.length,
                result = [],
                r = 0;

            if (Type.isString(butcher)) {
                butcher = predefinedButcher[butcher] || predefinedButcher.euler;
            }
            s = butcher.s;

            // don't change x0, so copy it
            for (e = 0; e < dim; e++) {
                x[e] = x0[e];
            }

            for (i = 0; i < N; i++) {
                // Optimization doesn't work for ODEs plotted using time
                //        if((i % quotient == 0) || (i == N-1)) {
                result[r] = [];
                for (e = 0; e < dim; e++) {
                    result[r][e] = x[e];
                }

                r += 1;
                k = [];

                for (j = 0; j < s; j++) {
                    // init y = 0
                    for (e = 0; e < dim; e++) {
                        y[e] = 0.0;
                    }


                    // Calculate linear combination of former k's and save it in y
                    for (l = 0; l < j; l++) {
                        for (e = 0; e < dim; e++) {
                            y[e] += (butcher.A[j][l]) * h * k[l][e];
                        }
                    }

                    // add x(t) to y
                    for (e = 0; e < dim; e++) {
                        y[e] += x[e];
                    }

                    // calculate new k and add it to the k matrix
                    k.push(f(t + butcher.c[j] * h, y));
                }

                // init y = 0
                for (e = 0; e < dim; e++) {
                    y[e] = 0.0;
                }

                for (l = 0; l < s; l++) {
                    for (e = 0; e < dim; e++) {
                        y[e] += butcher.b[l] * k[l][e];
                    }
                }

                for (e = 0; e < dim; e++) {
                    x[e] = x[e] + h * y[e];
                }

                t += h;
            }

            return result;
        },

        /**
         * Maximum number of iterations in {@link JXG.Math.Numerics.fzero} and
         * {@link JXG.Math.Numerics.chandrupatla}
         * @type Number
         * @default 80
         * @memberof JXG.Math.Numerics
         */
        maxIterationsRoot: 80,

        /**
         * Maximum number of iterations in {@link JXG.Math.Numerics.fminbr}
         * @type Number
         * @default 500
         * @memberof JXG.Math.Numerics
         */
        maxIterationsMinimize: 500,

        /**
         * Given a value x_0, this function tries to find a second value x_1 such that
         * the function f has opposite signs at x_0 and x_1.
         * The return values have to be tested if the method succeeded.
         *
         * @param {Function} f Function, whose root is to be found
         * @param {Number} x0 Start value
         * @param {Object} object Parent object in case f is method of it
         * @returns {Array} [x_0, f(x_0), x_1, f(x_1)]
         *
         * @see JXG.Math.Numerics.fzero
         * @see JXG.Math.Numerics.chandrupatla
         *
         * @memberof JXG.Math.Numerics
         */
        findBracket: function(f, x0, object) {
            var a, aa, fa,
                blist, b, fb,
                u, fu,
                i, len;

            if (Type.isArray(x0)) {
                return x0;
            }

            a = x0;
            fa = f.call(object, a);
            // nfev += 1;

            // Try to get b, by trying several values related to a
            aa = (a === 0) ? 1 : a;
            blist = [
                a - 0.1 * aa, a + 0.1 * aa,
                a - 1, a + 1,
                a - 0.5 * aa, a + 0.5 * aa,
                a - 0.6 * aa, a + 0.6 * aa,
                a - 1 * aa, a + 1 * aa,
                a - 2 * aa, a + 2 * aa,
                a - 5 * aa, a + 5 * aa,
                a - 10 * aa, a + 10 * aa,
                a - 50 * aa, a + 50 * aa,
                a - 100 * aa, a + 100 * aa
            ];
            len = blist.length;

            for (i = 0; i < len; i++) {
                b = blist[i];
                fb = f.call(object, b);
                // nfev += 1;

                if (fa * fb <= 0) {
                    break;
                }
            }
            if (b < a) {
                u = a;
                a = b;
                b = u;

                fu = fa;
                fa = fb;
                fb = fu;
            }
            return [a, fa, b, fb];
        },

        /**
         *
         * Find zero of an univariate function f.
         * @param {function} f Function, whose root is to be found
         * @param {Array,Number} x0  Start value or start interval enclosing the root
         * @param {Object} object Parent object in case f is method of it
         * @returns {Number} the approximation of the root
         * Algorithm:
         *  Brent's root finder from
         *  G.Forsythe, M.Malcolm, C.Moler, Computer methods for mathematical
         *  computations. M., Mir, 1980, p.180 of the Russian edition
         *  http://www.netlib.org/c/brent.shar
         *
         * If x0 is an array containing lower and upper bound for the zero
         * algorithm 748 is applied. Otherwise, if x0 is a number,
         * the algorithm tries to bracket a zero of f starting from x0.
         * If this fails, we fall back to Newton's method.
         *
         * @see JXG.Math.Numerics.chandrupatla
         * @see JXG.Math.Numerics.root
         * @memberof JXG.Math.Numerics
         */
        fzero: function (f, x0, object) {
            var a, b, c,
                d, e,
                fa, fb, fc,
                res,
                prev_step, t1, cb, t2,
                // Actual tolerance
                tol_act,
                // Interpolation step is calculated in the form p/q; division
                // operations is delayed until the last moment
                p, q,
                // Step at this iteration
                new_step,
                eps = Mat.eps,
                maxiter = this.maxIterationsRoot,
                niter = 0;
                // nfev = 0;

            if (Type.isArray(x0)) {
                if (x0.length < 2) {
                    throw new Error("JXG.Math.Numerics.fzero: length of array x0 has to be at least two.");
                }

                a = x0[0];
                fa = f.call(object, a);
                // nfev += 1;
                b = x0[1];
                fb = f.call(object, b);
                // nfev += 1;
            } else {
                res = this.findBracket(f, x0, object);
                a = res[0];
                fa = res[1];
                b = res[2];
                fb = res[3];
            }

            if (Math.abs(fa) <= eps) {
                return a;
            }
            if (Math.abs(fb) <= eps) {
                return b;
            }

            if (fa * fb > 0) {
                // Bracketing not successful, fall back to Newton's method or to fminbr
                if (Type.isArray(x0)) {
                    return this.fminbr(f, [a, b], object);
                }

                return this.Newton(f, a, object);
            }

            // OK, we have enclosed a zero of f.
            // Now we can start Brent's method
            c = a;
            fc = fa;

            // Main iteration loop
            while (niter < maxiter) {
                // Distance from the last but one to the last approximation
                prev_step = b - a;

                // Swap data for b to be the best approximation
                if (Math.abs(fc) < Math.abs(fb)) {
                    a = b;
                    b = c;
                    c = a;

                    fa = fb;
                    fb = fc;
                    fc = fa;
                }
                tol_act = 2 * eps * Math.abs(b) + eps * 0.5;
                new_step = (c - b) * 0.5;

                if (Math.abs(new_step) <= tol_act || Math.abs(fb) <= eps) {
                    //  Acceptable approx. is found
                    return b;
                }

                // Decide if the interpolation can be tried
                // If prev_step was large enough and was in true direction Interpolatiom may be tried
                if (Math.abs(prev_step) >= tol_act && Math.abs(fa) > Math.abs(fb)) {
                    cb = c - b;

                    // If we have only two distinct points linear interpolation can only be applied
                    if (a === c) {
                        t1 = fb / fa;
                        p = cb * t1;
                        q = 1.0 - t1;
                    // Quadric inverse interpolation
                    } else {
                        q = fa / fc;
                        t1 = fb / fc;
                        t2 = fb / fa;

                        p = t2 * (cb * q * (q - t1) - (b - a) * (t1 - 1.0));
                        q = (q - 1.0) * (t1 - 1.0) * (t2 - 1.0);
                    }

                    // p was calculated with the opposite sign; make p positive
                    if (p > 0) {
                        q = -q;
                    // and assign possible minus to q
                    } else {
                        p = -p;
                    }

                    // If b+p/q falls in [b,c] and isn't too large it is accepted
                    // If p/q is too large then the bissection procedure can reduce [b,c] range to more extent
                    if (p < (0.75 * cb * q - Math.abs(tol_act * q) * 0.5) &&
                            p < Math.abs(prev_step * q * 0.5)) {
                        new_step = p / q;
                    }
                }

                // Adjust the step to be not less than tolerance
                if (Math.abs(new_step) < tol_act) {
                    new_step = (new_step > 0) ? tol_act : -tol_act;
                }

                // Save the previous approx.
                a  = b;
                fa = fb;
                b += new_step;
                fb = f.call(object, b);
                // Do step to a new approxim.
                // nfev += 1;

                // Adjust c for it to have a sign opposite to that of b
                if ((fb > 0 && fc > 0) || (fb < 0 && fc < 0)) {
                    c = a;
                    fc = fa;
                }
                niter++;
            } // End while

            return b;
        },

        /**
         * Find zero of an univariate function f.
         * @param {function} f Function, whose root is to be found
         * @param {Array,Number} x0  Start value or start interval enclosing the root
         * @param {Object} object Parent object in case f is method of it
         * @returns {Number} the approximation of the root
         * Algorithm:
         * Chandrupatla's method, see
         * Tirupathi R. Chandrupatla,
         * "A new hybrid quadratic/bisection algorithm for finding the zero of a nonlinear function without using derivatives",
         * Advances in Engineering Software, Volume 28, Issue 3, April 1997, Pages 145-149.
         *
         * If x0 is an array containing lower and upper bound for the zero
         * algorithm 748 is applied. Otherwise, if x0 is a number,
         * the algorithm tries to bracket a zero of f starting from x0.
         * If this fails, we fall back to Newton's method.
         *
         * @see JXG.Math.Numerics.root
         * @see JXG.Math.Numerics.fzero
         * @memberof JXG.Math.Numerics
         */
        chandrupatla: function (f, x0, object) {
            var a, fa, b, fb, res,
                niter = 0,
                maxiter = this.maxIterationsRoot,
                rand = (1 + Math.random() * 0.001),
                t = 0.5 * rand,
                eps = Mat.eps, // 1.e-10,
                dlt = 0.00001,
                x1, x2, x3, x,
                f1, f2, f3, y,
                xm, fm, tol, tl,
                xi, ph, fl, fh,
                AL, A, B, C, D;

            if (Type.isArray(x0)) {
                if (x0.length < 2) {
                    throw new Error("JXG.Math.Numerics.fzero: length of array x0 has to be at least two.");
                }

                a = x0[0];
                fa = f.call(object, a);
                // nfev += 1;
                b = x0[1];
                fb = f.call(object, b);
                // nfev += 1;
            } else {
                res = this.findBracket(f, x0, object);
                a = res[0];
                fa = res[1];
                b = res[2];
                fb = res[3];
            }

            if (fa * fb > 0) {
                // Bracketing not successful, fall back to Newton's method or to fminbr
                if (Type.isArray(x0)) {
                    return this.fminbr(f, [a, b], object);
                }

                return this.Newton(f, a, object);
            }

            x1 = a;  x2 = b;
            f1 = fa; f2 = fb;
            do {
                x = x1 + t * (x2 - x1);
                y = f.call(object, x);

                // Arrange 2-1-3: 2-1 interval, 1 middle, 3 discarded point
                if (Math.sign(y) === Math.sign(f1)) {
                    x3 = x1; x1 = x;
                    f3 = f1; f1 = y;
                } else {
                    x3 = x2; x2 = x1;
                    f3 = f2; f2 = f1;
                }
                x1 = x; f1 = y;

                xm = x1; fm = f1;
                if (Math.abs(f2) < Math.abs(f1)) {
                    xm = x2; fm = f2;
                }
                tol = 2 * eps * Math.abs(xm) + 0.5 * dlt;
                tl = tol / Math.abs(x2 - x1);
                if (tl > 0.5 || fm === 0) {
                    break;
                }
                // If inverse quadratic interpolation holds, use it
                xi = (x1 - x2) / (x3 - x2);
                ph = (f1 - f2) / (f3 - f2);
                fl = 1 - Math.sqrt(1 - xi);
                fh = Math.sqrt(xi);
                if (fl < ph && ph < fh) {
                    AL = (x3 - x1) / (x2 - x1);
                    A = f1 / (f2 - f1);
                    B = f3 / (f2 - f3);
                    C = f1 / (f3 - f1);
                    D = f2 / (f3 - f2);
                    t = A * B + C * D * AL;
                } else {
                    t = 0.5 * rand;
                }
                // Adjust t away from the interval boundary
                if (t < tl) {
                    t = tl;
                }
                if (t > 1 - tl) {
                    t = 1 - tl;
                }
                niter++;
            } while (niter <= maxiter);
            // console.log(niter);

            return xm;
        },

        /**
         *
         * Find minimum of an univariate function f.
         * <p>
         * Algorithm:
         *  G.Forsythe, M.Malcolm, C.Moler, Computer methods for mathematical
         *  computations. M., Mir, 1980, p.180 of the Russian edition
         *
         * @param {function} f Function, whose minimum is to be found
         * @param {Array} x0  Start interval enclosing the minimum
         * @param {Object} context Parent object in case f is method of it
         * @returns {Number} the approximation of the minimum value position
         * @memberof JXG.Math.Numerics
         **/
        fminbr: function (f, x0, context) {
            var a, b, x, v, w,
                fx, fv, fw,
                range, middle_range, tol_act, new_step,
                p, q, t, ft,
                // Golden section ratio
                r = (3.0 - Math.sqrt(5.0)) * 0.5,
                tol = Mat.eps,
                sqrteps = Mat.eps, //Math.sqrt(Mat.eps),
                maxiter = this.maxIterationsMinimize,
                niter = 0;
                // nfev = 0;

            if (!Type.isArray(x0) || x0.length < 2) {
                throw new Error("JXG.Math.Numerics.fminbr: length of array x0 has to be at least two.");
            }

            a = x0[0];
            b = x0[1];
            v = a + r * (b - a);
            fv = f.call(context, v);

            // First step - always gold section
            // nfev += 1;
            x = v;
            w = v;
            fx = fv;
            fw = fv;

            while (niter < maxiter) {
                // Range over the interval in which we are looking for the minimum
                range = b - a;
                middle_range = (a + b) * 0.5;

                // Actual tolerance
                tol_act = sqrteps * Math.abs(x) + tol / 3.0;

                if (Math.abs(x - middle_range) + range * 0.5 <= 2.0 * tol_act) {
                    // Acceptable approx. is found
                    return x;
                }

                // Obtain the golden section step
                new_step = r * (x < middle_range ? b - x : a - x);

                // Decide if the interpolation can be tried. If x and w are distinct interpolatiom may be tried
                if (Math.abs(x - w) >= tol_act) {
                    // Interpolation step is calculated as p/q;
                    // division operation is delayed until last moment
                    t = (x - w) * (fx - fv);
                    q = (x - v) * (fx - fw);
                    p = (x - v) * q - (x - w) * t;
                    q = 2 * (q - t);

                    if (q > 0) {                        // q was calculated with the op-
                        p = -p;                         // posite sign; make q positive
                    } else {                            // and assign possible minus to
                        q = -q;                         // p
                    }
                    if (Math.abs(p) < Math.abs(new_step * q) &&     // If x+p/q falls in [a,b]
                            p > q * (a - x + 2 * tol_act) &&        //  not too close to a and
                            p < q * (b - x - 2 * tol_act)) {        // b, and isn't too large
                        new_step = p / q;                          // it is accepted
                    }
                    // If p/q is too large then the
                    // golden section procedure can
                    // reduce [a,b] range to more
                    // extent
                }

                // Adjust the step to be not less than tolerance
                if (Math.abs(new_step) < tol_act) {
                    if (new_step > 0) {
                        new_step = tol_act;
                    } else {
                        new_step = -tol_act;
                    }
                }

                // Obtain the next approximation to min
                // and reduce the enveloping range

                // Tentative point for the min
                t = x + new_step;
                ft = f.call(context, t);
                // nfev += 1;

                // t is a better approximation
                if (ft <= fx) {
                    // Reduce the range so that t would fall within it
                    if (t < x) {
                        b = x;
                    } else {
                        a = x;
                    }

                    // Assign the best approx to x
                    v = w;
                    w = x;
                    x = t;

                    fv = fw;
                    fw = fx;
                    fx = ft;
                // x remains the better approx
                } else {
                    // Reduce the range enclosing x
                    if (t < x) {
                        a = t;
                    } else {
                        b = t;
                    }

                    if (ft <= fw || w === x) {
                        v = w;
                        w = t;
                        fv = fw;
                        fw = ft;
                    } else if (ft <= fv || v === x || v === w) {
                        v = t;
                        fv = ft;
                    }
                }
                niter += 1;
            }

            return x;
        },

        /**
         * Implements the Ramer-Douglas-Peucker algorithm.
         * It discards points which are not necessary from the polygonal line defined by the point array
         * pts. The computation is done in screen coordinates.
         * Average runtime is O(nlog(n)), worst case runtime is O(n^2), where n is the number of points.
         * @param {Array} pts Array of {@link JXG.Coords}
         * @param {Number} eps If the absolute value of a given number <tt>x</tt> is smaller than <tt>eps</tt> it is considered to be equal <tt>0</tt>.
         * @returns {Array} An array containing points which represent an apparently identical curve as the points of pts do, but contains fewer points.
         * @memberof JXG.Math.Numerics
         */
        RamerDouglasPeucker: function (pts, eps) {
            var allPts = [], newPts = [], i, k, len,

                /**
                 * findSplit() is a subroutine of {@link JXG.Math.Numerics.RamerDouglasPeucker}.
                 * It searches for the point between index i and j which
                 * has the largest distance from the line between the points i and j.
                 * @param {Array} pts Array of {@link JXG.Coords}
                 * @param {Number} i Index of a point in pts
                 * @param {Number} j Index of a point in pts
                 * @ignore
                 * @private
                 */
                findSplit = function (pts, i, j) {
                    var d, k, ci, cj, ck,
                        x0, y0, x1, y1,
                        den, lbda,
                        huge = 10000,
                        dist = 0,
                        f = i;

                    if (j - i < 2) {
                        return [-1.0, 0];
                    }

                    ci = pts[i].scrCoords;
                    cj = pts[j].scrCoords;

                    if (isNaN(ci[1]) || isNaN(ci[2])) {
                        return [NaN, i];
                    }
                    if (isNaN(cj[1]) || isNaN(cj[2])) {
                        return [NaN, j];
                    }

                    for (k = i + 1; k < j; k++) {
                        ck = pts[k].scrCoords;
                        if (isNaN(ck[1]) || isNaN(ck[2])) {
                            return [NaN, k];
                        }

                        x0 = ck[1] - ci[1];
                        y0 = ck[2] - ci[2];
                        x1 = cj[1] - ci[1];
                        y1 = cj[2] - ci[2];
                        x0 = x0 === Infinity ? huge : x0;
                        y0 = y0 === Infinity ? huge : y0;
                        x1 = x1 === Infinity ? huge : x1;
                        y1 = y1 === Infinity ? huge : y1;
                        x0 = x0 === -Infinity ? -huge : x0;
                        y0 = y0 === -Infinity ? -huge : y0;
                        x1 = x1 === -Infinity ? -huge : x1;
                        y1 = y1 === -Infinity ? -huge : y1;
                        den = x1 * x1 + y1 * y1;

                        if (den >= Mat.eps) {
                            lbda = (x0 * x1 + y0 * y1) / den;

                            if (lbda < 0.0) {
                                lbda = 0.0;
                            } else if (lbda > 1.0) {
                                lbda = 1.0;
                            }

                            x0 = x0 - lbda * x1;
                            y0 = y0 - lbda * y1;
                            d = x0 * x0 + y0 * y0;
                        } else {
                            lbda = 0.0;
                            d = x0 * x0 + y0 * y0;
                        }

                        if (d > dist) {
                            dist = d;
                            f = k;
                        }
                    }
                    return [Math.sqrt(dist), f];
                },

                /**
                 * RDP() is a private subroutine of {@link JXG.Math.Numerics.RamerDouglasPeucker}.
                 * It runs recursively through the point set and searches the
                 * point which has the largest distance from the line between the first point and
                 * the last point. If the distance from the line is greater than eps, this point is
                 * included in our new point set otherwise it is discarded.
                 * If it is taken, we recursively apply the subroutine to the point set before
                 * and after the chosen point.
                 * @param {Array} pts Array of {@link JXG.Coords}
                 * @param {Number} i Index of an element of pts
                 * @param {Number} j Index of an element of pts
                 * @param {Number} eps If the absolute value of a given number <tt>x</tt> is smaller than <tt>eps</tt> it is considered to be equal <tt>0</tt>.
                 * @param {Array} newPts Array of {@link JXG.Coords}
                 * @ignore
                 * @private
                 */
                RDP = function (pts, i, j, eps, newPts) {
                    var result = findSplit(pts, i, j),
                        k = result[1];

                    if (isNaN(result[0])) {
                        RDP(pts, i, k - 1, eps, newPts);
                        newPts.push(pts[k]);
                        do {
                            ++k;
                        } while (k <= j && isNaN(pts[k].scrCoords[1] + pts[k].scrCoords[2]));
                        if (k <= j) {
                            newPts.push(pts[k]);
                        }
                        RDP(pts, k + 1, j, eps, newPts);
                    } else if (result[0] > eps) {
                        RDP(pts, i, k, eps, newPts);
                        RDP(pts, k, j, eps, newPts);
                    } else {
                        newPts.push(pts[j]);
                    }
                };

            len = pts.length;

            i = 0;
            while (true) {
                // Search for the next point without NaN coordinates
                while (i < len && isNaN(pts[i].scrCoords[1] + pts[i].scrCoords[2])) {
                    i += 1;
                }
                // Search for the next position of a NaN point
                k = i + 1;
                while (k < len && !isNaN(pts[k].scrCoords[1] + pts[k].scrCoords[2])) {
                    k += 1;
                }
                k--;

                // Only proceed if something is left
                if (i < len && k > i) {
                    newPts = [];
                    newPts[0] = pts[i];
                    RDP(pts, i, k, eps, newPts);
                    allPts = allPts.concat(newPts);
                }
                if (i >= len) {
                    break;
                }
                // Push the NaN point
                if (k < len - 1) {
                    allPts.push(pts[k + 1]);
                }
                i = k + 1;
            }

            return allPts;
        },

        /**
         * Old name for the implementation of the Ramer-Douglas-Peucker algorithm.
         * @deprecated Use {@link JXG.Math.Numerics.RamerDouglasPeucker}
         * @memberof JXG.Math.Numerics
         */
        RamerDouglasPeuker: function (pts, eps) {
            JXG.deprecated('Numerics.RamerDouglasPeuker()', 'Numerics.RamerDouglasPeucker()');
            return this.RamerDouglasPeucker(pts, eps);
        },

        /**
         * Implements the Visvalingam-Whyatt algorithm.
         * See M. Visvalingam, J. D. Whyatt:
         * "Line generalisation by repeated elimination of the smallest area", C.I.S.R.G Discussion paper 10, July 1992
         *
         * The algorithm discards points which are not necessary from the polygonal line defined by the point array
         * pts (consisting of type JXG.Coords).
         * @param {Array} pts Array of {@link JXG.Coords}
         * @param {Number} numPoints Number of remaining intermediate points. The first and the last point of the original points will
         *    be taken in any case.
         * @returns {Array} An array containing points which approximates the curve defined by pts.
         * @memberof JXG.Math.Numerics
         *
         * @example
         *     var i, p = [];
         *     for (i = 0; i < 5; ++i) {
         *         p.push(board.create('point', [Math.random() * 12 - 6, Math.random() * 12 - 6]));
         *     }
         *
         *     // Plot a cardinal spline curve
         *     var splineArr = JXG.Math.Numerics.CardinalSpline(p, 0.5);
         *     var cu1 = board.create('curve', splineArr, {strokeColor: 'green'});
         *
         *     var c = board.create('curve', [[0],[0]], {strokeWidth: 2, strokeColor: 'black'});
         *     c.updateDataArray = function() {
         *         var i, len, points;
         *
         *         // Reduce number of intermediate points with Visvakingam-Whyatt to 6
         *         points = JXG.Math.Numerics.Visvalingam(cu1.points, 6);
         *         // Plot the remaining points
         *         len = points.length;
         *         this.dataX = [];
         *         this.dataY = [];
         *         for (i = 0; i < len; i++) {
         *             this.dataX.push(points[i].usrCoords[1]);
         *             this.dataY.push(points[i].usrCoords[2]);
         *         }
         *     };
         *     board.update();
         *
         * </pre><div id="JXGce0cc55c-b592-11e6-8270-104a7d3be7eb" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXGce0cc55c-b592-11e6-8270-104a7d3be7eb',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *
         *         var i, p = [];
         *         for (i = 0; i < 5; ++i) {
         *             p.push(board.create('point', [Math.random() * 12 - 6, Math.random() * 12 - 6]));
         *         }
         *
         *         // Plot a cardinal spline curve
         *         var splineArr = JXG.Math.Numerics.CardinalSpline(p, 0.5);
         *         var cu1 = board.create('curve', splineArr, {strokeColor: 'green'});
         *
         *         var c = board.create('curve', [[0],[0]], {strokeWidth: 2, strokeColor: 'black'});
         *         c.updateDataArray = function() {
         *             var i, len, points;
         *
         *             // Reduce number of intermediate points with Visvakingam-Whyatt to 6
         *             points = JXG.Math.Numerics.Visvalingam(cu1.points, 6);
         *             // Plot the remaining points
         *             len = points.length;
         *             this.dataX = [];
         *             this.dataY = [];
         *             for (i = 0; i < len; i++) {
         *                 this.dataX.push(points[i].usrCoords[1]);
         *                 this.dataY.push(points[i].usrCoords[2]);
         *             }
         *         };
         *         board.update();
         *
         *     })();
         *
         * </script><pre>
         *
         */
        Visvalingam: function(pts, numPoints) {
            var i, len, vol, lastVol,
                linkedList = [],
                heap = [],
                points = [],
                lft, rt, lft2, rt2,
                obj;

            len = pts.length;

            // At least one intermediate point is needed
            if (len <= 2) {
                return pts;
            }

            // Fill the linked list
            // Add first point to the linked list
            linkedList[0] = {
                    used: true,
                    lft: null,
                    node: null
                };

            // Add all intermediate points to the linked list,
            // whose triangle area is nonzero.
            lft = 0;
            for (i = 1; i < len - 1; i++) {
                vol = Math.abs(JXG.Math.Numerics.det([pts[i - 1].usrCoords,
                                              pts[i].usrCoords,
                                              pts[i + 1].usrCoords]));
                if (!isNaN(vol)) {
                    obj = {
                        v: vol,
                        idx: i
                    };
                    heap.push(obj);
                    linkedList[i] = {
                            used: true,
                            lft: lft,
                            node: obj
                        };
                    linkedList[lft].rt = i;
                    lft = i;
                }
            }

            // Add last point to the linked list
            linkedList[len - 1] = {
                    used: true,
                    rt: null,
                    lft: lft,
                    node: null
                };
            linkedList[lft].rt = len - 1;

            // Remove points until only numPoints intermediate points remain
            lastVol = -Infinity;
            while (heap.length > numPoints) {
                // Sort the heap with the updated volume values
                heap.sort(function(a, b) {
                    // descending sort
                    return b.v - a.v;
                });

                // Remove the point with the smallest triangle
                i = heap.pop().idx;
                linkedList[i].used = false;
                lastVol = linkedList[i].node.v;

                // Update the pointers of the linked list
                lft = linkedList[i].lft;
                rt = linkedList[i].rt;
                linkedList[lft].rt = rt;
                linkedList[rt].lft = lft;

                // Update the values for the volumes in the linked list
                lft2 = linkedList[lft].lft;
                if (lft2 !== null) {
                    vol = Math.abs(JXG.Math.Numerics.det(
                                [pts[lft2].usrCoords,
                                 pts[lft].usrCoords,
                                 pts[rt].usrCoords]));

                    linkedList[lft].node.v = (vol >= lastVol) ? vol : lastVol;
                }
                rt2 = linkedList[rt].rt;
                if (rt2 !== null) {
                    vol = Math.abs(JXG.Math.Numerics.det(
                                [pts[lft].usrCoords,
                                 pts[rt].usrCoords,
                                 pts[rt2].usrCoords]));

                    linkedList[rt].node.v = (vol >= lastVol) ? vol : lastVol;
                }
            }

            // Return an array with the remaining points
            i = 0;
            points = [pts[i]];
            do {
                i = linkedList[i].rt;
                points.push(pts[i]);
            } while (linkedList[i].rt !== null);

            return points;
        }
    };

    return Mat.Numerics;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Carsten Miller,
        Reinhard Oldenburg,
        Alfred Wassermann

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.

    This is a port of jcobyla

    - to JavaScript by Reihard Oldenburg and
    - to JSXGraph By Alfred Wassermann
*/
/*
 * jcobyla
 * 
 * The MIT License
 *
 * Copyright (c) 2012 Anders Gustafsson, Cureos AB.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files
 * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
 * publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE 
 * FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 * Remarks:
 * 
 * The original Fortran 77 version of this code was by Michael Powell (M.J.D.Powell @ damtp.cam.ac.uk)
 * The Fortran 90 version was by Alan Miller (Alan.Miller @ vic.cmis.csiro.au). Latest revision - 30 October 1998
 */

/**
 * Constrained Optimization BY Linear Approximation in Java.
 * 
 * COBYLA2 is an implementation of Powell's nonlinear derivative free constrained optimization that uses
 * a linear approximation approach. The algorithm is a sequential trust region algorithm that employs linear
 * approximations to the objective and constraint functions, where the approximations are formed by linear
 * interpolation at n + 1 points in the space of the variables and tries to maintain a regular shaped simplex
 * over iterations.
 * 
 * It solves nonsmooth NLP with a moderate number of variables (about 100). Inequality constraints only.
 * 
 * The initial point X is taken as one vertex of the initial simplex with zero being another, so, X should
 * not be entered as the zero vector.
 * 
 * @author Anders Gustafsson, Cureos AB. Translation to Javascript by Reinhard Oldenburg, Goethe-University
 */

/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true, continue: true*/

/* depends:
 jxg
 math/math
 utils/type
 */

define('math/nlp',['jxg'], function (JXG) {

    "use strict";

    /**
     * The JXG.Math.Nlp namespace holds numerical algorithms for non-linear optimization.
     * @name JXG.Math.Nlp
     * @namespace
     * 
     */
    JXG.Math.Nlp =  {

        arr: function(n) {
            var a = new Array(n),
                i;
            for (i = 0; i <n ; i++) {
                a[i] = 0.0;
            }
            return a;
        },

        arr2: function(n, m) {
            var i = 0,
                a = new Array(n);

            while (i < n) {
                a[i] = this.arr(m);
                i++;
            }
            return a;
        },

        arraycopy: function(x, a, iox, b, n) {
            var i = 0;
            while (i < n) {
                iox[i + b] = x[i + a];
                i++;
            }
        },

        // status Variables
        Normal: 0,
        MaxIterationsReached: 1,
        DivergingRoundingErrors: 2,

        /**
         * Minimizes the objective function F with respect to a set of inequality constraints CON,
         * and returns the optimal variable array. F and CON may be non-linear, and should preferably be smooth.
         * Calls {@link JXG.Math.Nlp#cobylb}.
         *
         * @param calcfc Interface implementation for calculating objective function and constraints.
         * @param n Number of variables.
         * @param m Number of constraints.
         * @param x On input initial values of the variables (zero-based array). On output
         * optimal values of the variables obtained in the COBYLA minimization.
         * @param rhobeg Initial size of the simplex.
         * @param rhoend Final value of the simplex.
         * @param iprint Print level, 0 <= iprint <= 3, where 0 provides no output and
         * 3 provides full output to the console.
         * @param maxfun Maximum number of function evaluations before terminating.
         * @returns {Number} Exit status of the COBYLA2 optimization.
         */
        FindMinimum: function(calcfc, n,  m, x, rhobeg, rhoend,  iprint,  maxfun) {
            // CobylaExitStatus FindMinimum(final Calcfc calcfc, int n, int m, double[] x, double rhobeg, double rhoend, int iprint, int maxfun)
            //     This subroutine minimizes an objective function F(X) subject to M
            //     inequality constraints on X, where X is a vector of variables that has
            //     N components.  The algorithm employs linear approximations to the
            //     objective and constraint functions, the approximations being formed by
            //     linear interpolation at N+1 points in the space of the variables.
            //     We regard these interpolation points as vertices of a simplex.  The
            //     parameter RHO controls the size of the simplex and it is reduced
            //     automatically from RHOBEG to RHOEND.  For each RHO the subroutine tries
            //     to achieve a good vector of variables for the current size, and then
            //     RHO is reduced until the value RHOEND is reached.  Therefore RHOBEG and
            //     RHOEND should be set to reasonable initial changes to and the required
            //     accuracy in the variables respectively, but this accuracy should be
            //     viewed as a subject for experimentation because it is not guaranteed.
            //     The subroutine has an advantage over many of its competitors, however,
            //     which is that it treats each constraint individually when calculating
            //     a change to the variables, instead of lumping the constraints together
            //     into a single penalty function.  The name of the subroutine is derived
            //     from the phrase Constrained Optimization BY Linear Approximations.

            //     The user must set the values of N, M, RHOBEG and RHOEND, and must
            //     provide an initial vector of variables in X.  Further, the value of
            //     IPRINT should be set to 0, 1, 2 or 3, which controls the amount of
            //     printing during the calculation. Specifically, there is no output if
            //     IPRINT=0 and there is output only at the end of the calculation if
            //     IPRINT=1.  Otherwise each new value of RHO and SIGMA is printed.
            //     Further, the vector of variables and some function information are
            //     given either when RHO is reduced or when each new value of F(X) is
            //     computed in the cases IPRINT=2 or IPRINT=3 respectively. Here SIGMA
            //     is a penalty parameter, it being assumed that a change to X is an
            //     improvement if it reduces the merit function
            //                F(X)+SIGMA*MAX(0.0, - C1(X), - C2(X),..., - CM(X)),
            //     where C1,C2,...,CM denote the constraint functions that should become
            //     nonnegative eventually, at least to the precision of RHOEND. In the
            //     printed output the displayed term that is multiplied by SIGMA is
            //     called MAXCV, which stands for 'MAXimum Constraint Violation'.  The
            //     argument ITERS is an integer variable that must be set by the user to a
            //     limit on the number of calls of CALCFC, the purpose of this routine being
            //     given below.  The value of ITERS will be altered to the number of calls
            //     of CALCFC that are made.
            //     In order to define the objective and constraint functions, we require
            //     a subroutine that has the name and arguments
            //                SUBROUTINE CALCFC (N,M,X,F,CON)
            //                DIMENSION X(:),CON(:)  .
            //     The values of N and M are fixed and have been defined already, while
            //     X is now the current vector of variables. The subroutine should return
            //     the objective and constraint functions at X in F and CON(1),CON(2),
            //     ...,CON(M).  Note that we are trying to adjust X so that F(X) is as
            //     small as possible subject to the constraint functions being nonnegative.

            // Local variables
            var mpp = m + 2,
                status,
                // Internal base-1 X array
                iox = this.arr(n + 1),
                that = this,
                fcalcfc;

            iox[0] = 0.0;
            this.arraycopy(x, 0, iox, 1, n);

            // Internal representation of the objective and constraints calculation method,
            // accounting for that X and CON arrays in the cobylb method are base-1 arrays.
            fcalcfc = function(n, m, thisx, con) {  // int n, int m, double[] x, double[] con
                    var ix = that.arr(n),
                        ocon, f;

                    that.arraycopy(thisx, 1, ix, 0, n);
                    ocon = that.arr(m);
                    f = calcfc(n, m, ix, ocon);
                    that.arraycopy(ocon, 0, con, 1, m);
                    return f;
                };

            status = this.cobylb(fcalcfc, n, m, mpp, iox, rhobeg, rhoend, iprint, maxfun);
            this.arraycopy(iox, 1, x, 0, n);

            return status;
        },

        //    private static CobylaExitStatus cobylb(Calcfc calcfc, int n, int m, int mpp, double[] x,
        //      double rhobeg, double rhoend, int iprint, int maxfun)
        /**
         * JavaScript implementation of the non-linear optimization method COBYLA.
         * @param {Function} calcfc 
         * @param {Number} n 
         * @param {Number} m 
         * @param {Number} mpp 
         * @param {Number} x 
         * @param {Number} rhobeg 
         * @param {Number} rhoend 
         * @param {Number} iprint 
         * @param {Number} maxfun 
         * @returns {Number} Exit status of the COBYLA2 optimization
         */
        cobylb: function (calcfc, n,  m,  mpp,  x, rhobeg,  rhoend,  iprint,  maxfun) {
            // calcf ist funktion die aufgerufen wird wie calcfc(n, m, ix, ocon)
            // N.B. Arguments CON, SIM, SIMI, DATMAT, A, VSIG, VETA, SIGBAR, DX, W & IACT
            //      have been removed.

            //     Set the initial values of some parameters. The last column of SIM holds
            //     the optimal vertex of the current simplex, and the preceding N columns
            //     hold the displacements from the optimal vertex to the other vertices.
            //     Further, SIMI holds the inverse of the matrix that is contained in the
            //     first N columns of SIM.

            // Local variables
            var status = -1,

                alpha = 0.25,
                beta = 2.1,
                gamma = 0.5,
                delta = 1.1,

                f = 0.0,
                resmax = 0.0,
                total,

                np = n + 1,
                mp = m + 1,
                rho = rhobeg,
                parmu = 0.0,

                iflag = false,
                ifull = false,
                parsig = 0.0,
                prerec = 0.0,
                prerem = 0.0,

                con = this.arr(1 + mpp),
                sim = this.arr2(1 + n, 1 + np),
                simi = this.arr2(1 + n, 1 + n),
                datmat = this.arr2(1 + mpp, 1 + np),
                a = this.arr2(1 + n, 1 + mp),
                vsig = this.arr(1 + n),
                veta = this.arr(1 + n),
                sigbar = this.arr(1 + n),
                dx = this.arr(1 + n),
                w = this.arr(1 + n),
                i, j, k, l,
                temp, tempa, nfvals,
                jdrop, ibrnch,
                skipVertexIdent,
                phimin, nbest,
                error,
                pareta, wsig, weta,
                cvmaxp, cvmaxm, dxsign,
                resnew, barmu, phi,
                vmold, vmnew, trured, ratio, edgmax, cmin, cmax, denom;

            if (iprint >= 2) {
                console.log("The initial value of RHO is " + rho + " and PARMU is set to zero.");
            }

            nfvals = 0;
            temp = 1.0 / rho;

            for (i = 1; i <= n; ++i) {
                sim[i][np] = x[i];
                sim[i][i] = rho;
                simi[i][i] = temp;
            }

            jdrop = np;
            ibrnch = false;

            //     Make the next call of the user-supplied subroutine CALCFC. These
            //     instructions are also used for calling CALCFC during the iterations of
            //     the algorithm.
            //alert("Iteration "+nfvals+" x="+x);
            L_40:
            do {
                if (nfvals >= maxfun && nfvals > 0) {
                    status = this.MaxIterationsReached;
                    break L_40;
                }

                ++nfvals;
                f = calcfc(n, m, x, con);
                resmax = 0.0;
                for (k = 1; k <= m; ++k) {
                    resmax = Math.max(resmax, -con[k]);
                }
                //alert(    "   f="+f+"  resmax="+resmax);

                if (nfvals === iprint - 1 || iprint === 3) {
                    this.PrintIterationResult(nfvals, f, resmax, x, n, iprint);
                }

                con[mp] = f;
                con[mpp] = resmax;

                //     Set the recently calculated function values in a column of DATMAT. This
                //     array has a column for each vertex of the current simplex, the entries of
                //     each column being the values of the constraint functions (if any)
                //     followed by the objective function and the greatest constraint violation
                //     at the vertex.
                skipVertexIdent = true;
                if (!ibrnch) {
                    skipVertexIdent = false;

                    for (i = 1; i <= mpp; ++i) {
                        datmat[i][jdrop] = con[i];
                    }

                    if (nfvals <= np) {
                        //     Exchange the new vertex of the initial simplex with the optimal vertex if
                        //     necessary. Then, if the initial simplex is not complete, pick its next
                        //     vertex and calculate the function values there.

                        if (jdrop <= n) {
                            if (datmat[mp][np] <= f) {
                                x[jdrop] = sim[jdrop][np];
                            } else {
                                sim[jdrop][np] = x[jdrop];
                                for (k = 1; k <= mpp; ++k) {
                                    datmat[k][jdrop] = datmat[k][np];
                                    datmat[k][np] = con[k];
                                }
                                for (k = 1; k <= jdrop; ++k) {
                                    sim[jdrop][k] = -rho;
                                    temp = 0.0;
                                    for (i = k; i <= jdrop; ++i) {
                                        temp -= simi[i][k];
                                    }
                                    simi[jdrop][k] = temp;
                                }
                            }
                        }
                        if (nfvals <= n) {
                            jdrop = nfvals;
                            x[jdrop] += rho;
                            continue L_40;
                        }
                    }
                    ibrnch = true;
                }

                L_140:
                do {
                    L_550:
                    do {
                        if (!skipVertexIdent) {
                            //     Identify the optimal vertex of the current simplex.
                            phimin = datmat[mp][np] + parmu * datmat[mpp][np];
                            nbest = np;

                            for (j = 1; j <= n; ++j) {
                                temp = datmat[mp][j] + parmu * datmat[mpp][j];
                                if (temp < phimin) {
                                    nbest = j;
                                    phimin = temp;
                                } else if (temp === phimin && parmu === 0.0 && datmat[mpp][j] < datmat[mpp][nbest]) {
                                    nbest = j;
                                }
                            }

                            //     Switch the best vertex into pole position if it is not there already,
                            //     and also update SIM, SIMI and DATMAT.
                            if (nbest <= n) {
                                for (i = 1; i <= mpp; ++i) {
                                    temp = datmat[i][np];
                                    datmat[i][np] = datmat[i][nbest];
                                    datmat[i][nbest] = temp;
                                }
                                for (i = 1; i <= n; ++i) {
                                    temp = sim[i][nbest];
                                    sim[i][nbest] = 0.0;
                                    sim[i][np] += temp;

                                    tempa = 0.0;
                                    for (k = 1; k <= n; ++k)
                                    {
                                        sim[i][k] -= temp;
                                        tempa -= simi[k][i];
                                    }
                                    simi[nbest][i] = tempa;
                                }
                            }

                            //     Make an error return if SIGI is a poor approximation to the inverse of
                            //     the leading N by N submatrix of SIG.
                            error = 0.0;
                            for (i = 1; i <= n; ++i) {
                                for (j = 1; j <= n; ++j) {
                                    temp = this.DOT_PRODUCT(
                                            this.PART(this.ROW(simi, i), 1, n),
                                            this.PART(this.COL(sim, j), 1, n)
                                        ) - (i === j ? 1.0 : 0.0);
                                    error = Math.max(error, Math.abs(temp));
                                }
                            }
                            if (error > 0.1) {
                                status = this.DivergingRoundingErrors;
                                break L_40;
                            }

                            //     Calculate the coefficients of the linear approximations to the objective
                            //     and constraint functions, placing minus the objective function gradient
                            //     after the constraint gradients in the array A. The vector W is used for
                            //     working space.
                            for (k = 1; k <= mp; ++k) {
                                con[k] = -datmat[k][np];
                                for (j = 1; j <= n; ++j) {
                                    w[j] = datmat[k][j] + con[k];
                                }

                                for (i = 1; i <= n; ++i) {
                                    a[i][k] = (k === mp ? -1.0 : 1.0) * this.DOT_PRODUCT(
                                        this.PART(w, 1, n), this.PART(this.COL(simi, i), 1, n));
                                }
                            }

                            //     Calculate the values of sigma and eta, and set IFLAG = 0 if the current
                            //     simplex is not acceptable.
                            iflag = true;
                            parsig = alpha * rho;
                            pareta = beta * rho;

                            for (j = 1; j <= n; ++j) {
                                wsig = 0.0;
                                for (k = 1; k <= n; ++k) {
                                    wsig += simi[j][k] * simi[j][k];
                                }
                                weta = 0.0;
                                for (k = 1; k <= n; ++k) {
                                    weta += sim[k][j] * sim[k][j];
                                }
                                vsig[j] = 1.0 / Math.sqrt(wsig);
                                veta[j] = Math.sqrt(weta);
                                if (vsig[j] < parsig || veta[j] > pareta) { iflag = false; }
                            }

                            //     If a new vertex is needed to improve acceptability, then decide which
                            //     vertex to drop from the simplex.
                            if (!ibrnch && !iflag) {
                                jdrop = 0;
                                temp = pareta;
                                for (j = 1; j <= n; ++j) {
                                    if (veta[j] > temp) {
                                        jdrop = j;
                                        temp = veta[j];
                                    }
                                }
                                if (jdrop === 0) {
                                    for (j = 1; j <= n; ++j) {
                                        if (vsig[j] < temp) {
                                            jdrop = j;
                                            temp = vsig[j];
                                        }
                                    }
                                }

                                //     Calculate the step to the new vertex and its sign.
                                temp = gamma * rho * vsig[jdrop];
                                for (k = 1; k <= n; ++k) {
                                    dx[k] = temp * simi[jdrop][k];
                                }
                                cvmaxp = 0.0;
                                cvmaxm = 0.0;
                                total = 0.0;
                                for (k = 1; k <= mp; ++k) {
                                    total = this.DOT_PRODUCT(
                                        this.PART(this.COL(a, k), 1, n),
                                        this.PART(dx, 1, n)
                                        );
                                    if (k < mp) {
                                        temp = datmat[k][np];
                                        cvmaxp = Math.max(cvmaxp, -total - temp);
                                        cvmaxm = Math.max(cvmaxm, total - temp);
                                    }
                                }
                                dxsign = parmu * (cvmaxp - cvmaxm) > 2.0 * total ? -1.0 : 1.0;

                                //     Update the elements of SIM and SIMI, and set the next X.
                                temp = 0.0;
                                for (i = 1; i <= n; ++i) {
                                    dx[i] = dxsign * dx[i];
                                    sim[i][jdrop] = dx[i];
                                    temp += simi[jdrop][i] * dx[i];
                                }
                                for (k = 1; k <= n; ++k) {
                                    simi[jdrop][k] /= temp;
                                }

                                for (j = 1; j <= n; ++j) {
                                    if (j !== jdrop) {
                                        temp = this.DOT_PRODUCT(
                                            this.PART(this.ROW(simi, j), 1, n),
                                            this.PART(dx, 1, n)
                                            );
                                        for (k = 1; k <= n; ++k) {
                                            simi[j][k] -= temp * simi[jdrop][k];
                                        }
                                    }
                                    x[j] = sim[j][np] + dx[j];
                                }
                                continue L_40;
                            }

                            //     Calculate DX = x(*)-x(0).
                            //     Branch if the length of DX is less than 0.5*RHO.
                            ifull = this.trstlp(n, m, a, con, rho, dx);
                            if (!ifull) {
                                temp = 0.0;
                                for (k = 1; k <= n; ++k) {
                                    temp += dx[k] * dx[k];
                                }
                                if (temp < 0.25 * rho * rho) {
                                    ibrnch = true;
                                    break L_550;
                                }
                            }

                            //     Predict the change to F and the new maximum constraint violation if the
                            //     variables are altered from x(0) to x(0) + DX.
                            total = 0.0;
                            resnew = 0.0;
                            con[mp] = 0.0;
                            for (k = 1; k <= mp; ++k) {
                                total = con[k] - this.DOT_PRODUCT(this.PART(this.COL(a, k), 1, n), this.PART(dx, 1, n));
                                if (k < mp) { resnew = Math.max(resnew, total); }
                            }

                            //     Increase PARMU if necessary and branch back if this change alters the
                            //     optimal vertex. Otherwise PREREM and PREREC will be set to the predicted
                            //     reductions in the merit function and the maximum constraint violation
                            //     respectively.
                            prerec = datmat[mpp][np] - resnew;
                            barmu = prerec > 0.0 ? total / prerec : 0.0;
                            if (parmu < 1.5 * barmu) {
                                parmu = 2.0 * barmu;
                                if (iprint >= 2) { console.log("Increase in PARMU to " + parmu); }
                                phi = datmat[mp][np] + parmu * datmat[mpp][np];
                                for (j = 1; j <= n; ++j) {
                                    temp = datmat[mp][j] + parmu * datmat[mpp][j];
                                    if (temp < phi || (temp === phi && parmu === 0.0 && datmat[mpp][j] < datmat[mpp][np])) {
                                        continue L_140;
                                    }
                                }
                            }
                            prerem = parmu * prerec - total;

                            //     Calculate the constraint and objective functions at x(*).
                            //     Then find the actual reduction in the merit function.
                            for (k = 1; k <= n; ++k) {
                                x[k] = sim[k][np] + dx[k];
                            }
                            ibrnch = true;
                            continue L_40;
                        }

                        skipVertexIdent = false;
                        vmold = datmat[mp][np] + parmu * datmat[mpp][np];
                        vmnew = f + parmu * resmax;
                        trured = vmold - vmnew;
                        if (parmu === 0.0 && f === datmat[mp][np]) {
                            prerem = prerec;
                            trured = datmat[mpp][np] - resmax;
                        }

                        //     Begin the operations that decide whether x(*) should replace one of the
                        //     vertices of the current simplex, the change being mandatory if TRURED is
                        //     positive. Firstly, JDROP is set to the index of the vertex that is to be
                        //     replaced.
                        ratio = trured <= 0.0 ? 1.0 : 0.0;
                        jdrop = 0;
                        for (j = 1; j <= n; ++j) {
                            temp = Math.abs(this.DOT_PRODUCT(this.PART(this.ROW(simi, j), 1, n), this.PART(dx, 1, n)));
                            if (temp > ratio) {
                                jdrop = j;
                                ratio = temp;
                            }
                            sigbar[j] = temp * vsig[j];
                        }

                        //     Calculate the value of ell.

                        edgmax = delta * rho;
                        l = 0;
                        for (j = 1; j <= n; ++j) {
                            if (sigbar[j] >= parsig || sigbar[j] >= vsig[j]) {
                                temp = veta[j];
                                if (trured > 0.0) {
                                    temp = 0.0;
                                    for (k = 1; k <= n; ++k) {
                                        temp += Math.pow(dx[k] - sim[k][j], 2.0);
                                    }
                                    temp = Math.sqrt(temp);
                                }
                                if (temp > edgmax) {
                                    l = j;
                                    edgmax = temp;
                                }
                            }
                        }
                        if (l > 0) { jdrop = l; }

                        if (jdrop !== 0) {
                            //     Revise the simplex by updating the elements of SIM, SIMI and DATMAT.
                            temp = 0.0;
                            for (i = 1; i <= n; ++i) {
                                sim[i][jdrop] = dx[i];
                                temp += simi[jdrop][i] * dx[i];
                            }
                            for (k = 1; k <= n; ++k) { simi[jdrop][k] /= temp; }
                            for (j = 1; j <= n; ++j) {
                                if (j !== jdrop) {
                                    temp = this.DOT_PRODUCT(this.PART(this.ROW(simi, j), 1, n), this.PART(dx, 1, n));
                                    for (k = 1; k <= n; ++k) {
                                        simi[j][k] -= temp * simi[jdrop][k];
                                    }
                                }
                            }
                            for (k = 1; k <= mpp; ++k) {
                                datmat[k][jdrop] = con[k];
                            }

                            //     Branch back for further iterations with the current RHO.
                            if (trured > 0.0 && trured >= 0.1 * prerem) {
                                continue L_140;
                            }
                        }
                    } while (false);

                    if (!iflag) {
                        ibrnch = false;
                        continue L_140;
                    }

                    if (rho <= rhoend) {
                        status = this.Normal;
                        break L_40;
                    }

                    //     Otherwise reduce RHO if it is not at its least value and reset PARMU.
                    cmin = 0.0;
                    cmax = 0.0;
                    rho *= 0.5;
                    if (rho <= 1.5 * rhoend) { rho = rhoend; }
                    if (parmu > 0.0) {
                        denom = 0.0;
                        for (k = 1; k <= mp; ++k) {
                            cmin = datmat[k][np];
                            cmax = cmin;
                            for (i = 1; i <= n; ++i) {
                                cmin = Math.min(cmin, datmat[k][i]);
                                cmax = Math.max(cmax, datmat[k][i]);
                            }
                            if (k <= m && cmin < 0.5 * cmax) {
                                temp = Math.max(cmax, 0.0) - cmin;
                                denom = denom <= 0.0 ? temp : Math.min(denom, temp);
                            }
                        }
                        if (denom === 0.0) {
                            parmu = 0.0;
                        } else if (cmax - cmin < parmu * denom) {
                            parmu = (cmax - cmin) / denom;
                        }
                    }
                    if (iprint >= 2) {
                        console.log("Reduction in RHO to "+rho+"  and PARMU = "+parmu);
                    }
                    if (iprint === 2) {
                        this.PrintIterationResult(nfvals, datmat[mp][np], datmat[mpp][np], this.COL(sim, np), n, iprint);
                    }
                } while (true);
            } while (true);

            switch (status) {
                case this.Normal:
                    if (iprint >= 1) { console.log("%nNormal return from subroutine COBYLA%n"); }
                    if (ifull) {
                        if (iprint >= 1) { this.PrintIterationResult(nfvals, f, resmax, x, n, iprint); }
                        return status;
                    }
                    break;
                case this.MaxIterationsReached:
                    if (iprint >= 1) {
                        console.log("%nReturn from subroutine COBYLA because the MAXFUN limit has been reached.%n");
                    }
                    break;
                case this.DivergingRoundingErrors:
                    if (iprint >= 1) {
                        console.log("%nReturn from subroutine COBYLA because rounding errors are becoming damaging.%n");
                    }
                    break;
            }

            for (k = 1; k <= n; ++k) { x[k] = sim[k][np]; }
            f = datmat[mp][np];
            resmax = datmat[mpp][np];
            if (iprint >= 1) { this.PrintIterationResult(nfvals, f, resmax, x, n, iprint); }

            return status;
        },

        trstlp: function(n,  m,  a, b, rho,  dx) { //(int n, int m, double[][] a, double[] b, double rho, double[] dx)
            // N.B. Arguments Z, ZDOTA, VMULTC, SDIRN, DXNEW, VMULTD & IACT have been removed.

            //     This subroutine calculates an N-component vector DX by applying the
            //     following two stages. In the first stage, DX is set to the shortest
            //     vector that minimizes the greatest violation of the constraints
            //       A(1,K)*DX(1)+A(2,K)*DX(2)+...+A(N,K)*DX(N) .GE. B(K), K = 2,3,...,M,
            //     subject to the Euclidean length of DX being at most RHO. If its length is
            //     strictly less than RHO, then we use the resultant freedom in DX to
            //     minimize the objective function
            //              -A(1,M+1)*DX(1) - A(2,M+1)*DX(2) - ... - A(N,M+1)*DX(N)
            //     subject to no increase in any greatest constraint violation. This
            //     notation allows the gradient of the objective function to be regarded as
            //     the gradient of a constraint. Therefore the two stages are distinguished
            //     by MCON .EQ. M and MCON .GT. M respectively. It is possible that a
            //     degeneracy may prevent DX from attaining the target length RHO. Then the
            //     value IFULL = 0 would be set, but usually IFULL = 1 on return.
            //
            //     In general NACT is the number of constraints in the active set and
            //     IACT(1),...,IACT(NACT) are their indices, while the remainder of IACT
            //     contains a permutation of the remaining constraint indices.  Further, Z
            //     is an orthogonal matrix whose first NACT columns can be regarded as the
            //     result of Gram-Schmidt applied to the active constraint gradients.  For
            //     J = 1,2,...,NACT, the number ZDOTA(J) is the scalar product of the J-th
            //     column of Z with the gradient of the J-th active constraint.  DX is the
            //     current vector of variables and here the residuals of the active
            //     constraints should be zero. Further, the active constraints have
            //     nonnegative Lagrange multipliers that are held at the beginning of
            //     VMULTC. The remainder of this vector holds the residuals of the inactive
            //     constraints at DX, the ordering of the components of VMULTC being in
            //     agreement with the permutation of the indices of the constraints that is
            //     in IACT. All these residuals are nonnegative, which is achieved by the
            //     shift RESMAX that makes the least residual zero.

            //     Initialize Z and some other variables. The value of RESMAX will be
            //     appropriate to DX = 0, while ICON will be the index of a most violated
            //     constraint if RESMAX is positive. Usually during the first stage the
            //     vector SDIRN gives a search direction that reduces all the active
            //     constraint violations by one simultaneously.

            // Local variables

            var temp = 0,
                nactx = 0,
                resold = 0.0,

                z = this.arr2(1 + n, 1 + n),
                zdota = this.arr(2 + m),
                vmultc = this.arr(2 + m),
                sdirn = this.arr(1 + n),
                dxnew = this.arr(1 + n),
                vmultd = this.arr(2 + m),
                iact = this.arr(2 + m),

                mcon = m,
                nact = 0,
                icon, resmax,
                i, k,
                first,
                optold, icount, step, stpful, optnew,
                ratio, isave, vsave,
                total,
                kp, kk, sp, alpha, beta,
                tot, spabs, acca, accb,
                zdotv, zdvabs, kw,
                dd, ss, sd,
                zdotw, zdwabs,
                kl, sumabs, tempa;

            for (i = 1; i <= n; ++i) {
                z[i][i] = 1.0;
                dx[i] = 0.0;
            }

            icon = 0;
            resmax = 0.0;
            if (m >= 1) {
                for (k = 1; k <= m; ++k) {
                    if (b[k] > resmax) {
                        resmax = b[k];
                        icon = k;
                    }
                }
                for (k = 1; k <= m; ++k) {
                    iact[k] = k;
                    vmultc[k] = resmax - b[k];
                }
            }

            //     End the current stage of the calculation if 3 consecutive iterations
            //     have either failed to reduce the best calculated value of the objective
            //     function or to increase the number of active constraints since the best
            //     value was calculated. This strategy prevents cycling, but there is a
            //     remote possibility that it will cause premature termination.

            first = true;
            do {
                L_60:
                do {
                    if (!first || (first && resmax === 0.0)) {
                        mcon = m + 1;
                        icon = mcon;
                        iact[mcon] = mcon;
                        vmultc[mcon] = 0.0;
                    }
                    first = false;

                    optold = 0.0;
                    icount = 0;
                    step = 0;
                    stpful = 0;

                    L_70:
                    do {
                        optnew = (mcon === m) ? resmax : -this.DOT_PRODUCT(
                                this.PART(dx, 1, n), this.PART(this.COL(a, mcon), 1, n)
                            );

                        if (icount === 0 || optnew < optold) {
                            optold = optnew;
                            nactx = nact;
                            icount = 3;
                        } else if (nact > nactx) {
                            nactx = nact;
                            icount = 3;
                        } else {
                            --icount;
                        }
                        if (icount === 0) { break L_60; }

                        //     If ICON exceeds NACT, then we add the constraint with index IACT(ICON) to
                        //     the active set. Apply Givens rotations so that the last N-NACT-1 columns
                        //     of Z are orthogonal to the gradient of the new constraint, a scalar
                        //     product being set to zero if its nonzero value could be due to computer
                        //     rounding errors. The array DXNEW is used for working space.
                        ratio = 0;
                        if (icon <= nact) {
                            if (icon < nact) {
                                //     Delete the constraint that has the index IACT(ICON) from the active set.

                                isave = iact[icon];
                                vsave = vmultc[icon];
                                k = icon;
                                do {
                                    kp = k + 1;
                                    kk = iact[kp];
                                    sp = this.DOT_PRODUCT(
                                            this.PART(this.COL(z, k), 1, n),
                                            this.PART(this.COL(a, kk), 1, n)
                                        );
                                    temp = Math.sqrt(sp * sp + zdota[kp] * zdota[kp]);
                                    alpha = zdota[kp] / temp;
                                    beta = sp / temp;
                                    zdota[kp] = alpha * zdota[k];
                                    zdota[k] = temp;
                                    for (i = 1; i <= n; ++i) {
                                        temp = alpha * z[i][kp] + beta * z[i][k];
                                        z[i][kp] = alpha * z[i][k] - beta * z[i][kp];
                                        z[i][k] = temp;
                                    }
                                    iact[k] = kk;
                                    vmultc[k] = vmultc[kp];
                                    k = kp;
                                } while (k < nact);

                                iact[k] = isave;
                                vmultc[k] = vsave;
                            }
                            --nact;

                            //     If stage one is in progress, then set SDIRN to the direction of the next
                            //     change to the current vector of variables.
                            if (mcon > m) {
                                //     Pick the next search direction of stage two.
                                temp = 1.0 / zdota[nact];
                                for (k = 1; k <= n; ++k) { sdirn[k] = temp * z[k][nact]; }
                            } else {
                                temp = this.DOT_PRODUCT(
                                        this.PART(sdirn, 1, n), this.PART(this.COL(z, nact + 1), 1, n)
                                    );
                                for (k = 1; k <= n; ++k) { sdirn[k] -= temp * z[k][nact + 1]; }
                            }
                        } else {
                            kk = iact[icon];
                            for (k = 1; k <= n; ++k) { dxnew[k] = a[k][kk]; }
                            tot = 0.0;

                            // {
                                k = n;
                                while (k > nact) {
                                    sp = 0.0;
                                    spabs = 0.0;
                                    for (i = 1; i <= n; ++i) {
                                        temp = z[i][k] * dxnew[i];
                                        sp += temp;
                                        spabs += Math.abs(temp);
                                    }
                                    acca = spabs + 0.1 * Math.abs(sp);
                                    accb = spabs + 0.2 * Math.abs(sp);
                                    if (spabs >= acca || acca >= accb) { sp = 0.0; }
                                    if (tot === 0.0) {
                                        tot = sp;
                                    } else {
                                        kp = k + 1;
                                        temp = Math.sqrt(sp * sp + tot * tot);
                                        alpha = sp / temp;
                                        beta = tot / temp;
                                        tot = temp;
                                        for (i = 1; i <= n; ++i) {
                                            temp = alpha * z[i][k] + beta * z[i][kp];
                                            z[i][kp] = alpha * z[i][kp] - beta * z[i][k];
                                            z[i][k] = temp;
                                        }
                                    }
                                    --k;
                                }
                            // }

                            if (tot === 0.0) {
                                //     The next instruction is reached if a deletion has to be made from the
                                //     active set in order to make room for the new active constraint, because
                                //     the new constraint gradient is a linear combination of the gradients of
                                //     the old active constraints.  Set the elements of VMULTD to the multipliers
                                //     of the linear combination.  Further, set IOUT to the index of the
                                //     constraint to be deleted, but branch if no suitable index can be found.

                                ratio = -1.0;
                                //{
                                    k = nact;
                                    do {
                                        zdotv = 0.0;
                                        zdvabs = 0.0;

                                        for (i = 1; i <= n; ++i) {
                                            temp = z[i][k] * dxnew[i];
                                            zdotv += temp;
                                            zdvabs += Math.abs(temp);
                                        }
                                        acca = zdvabs + 0.1 * Math.abs(zdotv);
                                        accb = zdvabs + 0.2 * Math.abs(zdotv);
                                        if (zdvabs < acca && acca < accb) {
                                            temp = zdotv / zdota[k];
                                            if (temp > 0.0 && iact[k] <= m) {
                                                tempa = vmultc[k] / temp;
                                                if (ratio < 0.0 || tempa < ratio) { ratio = tempa; }
                                            }

                                            if (k >= 2) {
                                                kw = iact[k];
                                                for (i = 1; i <= n; ++i) { dxnew[i] -= temp * a[i][kw]; }
                                            }
                                            vmultd[k] = temp;
                                        } else {
                                            vmultd[k] = 0.0;
                                        }
                                    } while (--k > 0);
                                //}
                                if (ratio < 0.0) { break L_60; }

                                //     Revise the Lagrange multipliers and reorder the active constraints so
                                //     that the one to be replaced is at the end of the list. Also calculate the
                                //     new value of ZDOTA(NACT) and branch if it is not acceptable.

                                for (k = 1; k <= nact; ++k) {
                                    vmultc[k] = Math.max(0.0, vmultc[k] - ratio * vmultd[k]);
                                }
                                if (icon < nact) {
                                    isave = iact[icon];
                                    vsave = vmultc[icon];
                                    k = icon;
                                    do {
                                        kp = k + 1;
                                        kw = iact[kp];
                                        sp = this.DOT_PRODUCT(
                                                this.PART(this.COL(z, k), 1, n),
                                                this.PART(this.COL(a, kw), 1, n)
                                            );
                                        temp = Math.sqrt(sp * sp + zdota[kp] * zdota[kp]);
                                        alpha = zdota[kp] / temp;
                                        beta = sp / temp;
                                        zdota[kp] = alpha * zdota[k];
                                        zdota[k] = temp;
                                        for (i = 1; i <= n; ++i) {
                                            temp = alpha * z[i][kp] + beta * z[i][k];
                                            z[i][kp] = alpha * z[i][k] - beta * z[i][kp];
                                            z[i][k] = temp;
                                        }
                                        iact[k] = kw;
                                        vmultc[k] = vmultc[kp];
                                        k = kp;
                                    } while (k < nact);
                                    iact[k] = isave;
                                    vmultc[k] = vsave;
                                }
                                temp = this.DOT_PRODUCT(
                                            this.PART(this.COL(z, nact), 1, n),
                                            this.PART(this.COL(a, kk), 1, n)
                                        );
                                if (temp === 0.0) { break L_60; }
                                zdota[nact] = temp;
                                vmultc[icon] = 0.0;
                                vmultc[nact] = ratio;
                            } else {
                                //     Add the new constraint if this can be done without a deletion from the
                                //     active set.

                                ++nact;
                                zdota[nact] = tot;
                                vmultc[icon] = vmultc[nact];
                                vmultc[nact] = 0.0;
                            }

                            //     Update IACT and ensure that the objective function continues to be
                            //     treated as the last active constraint when MCON>M.

                            iact[icon] = iact[nact];
                            iact[nact] = kk;
                            if (mcon > m && kk !== mcon) {
                                k = nact - 1;
                                sp = this.DOT_PRODUCT(
                                        this.PART(this.COL(z, k), 1, n),
                                        this.PART(this.COL(a, kk), 1, n)
                                    );
                                temp = Math.sqrt(sp * sp + zdota[nact] * zdota[nact]);
                                alpha = zdota[nact] / temp;
                                beta = sp / temp;
                                zdota[nact] = alpha * zdota[k];
                                zdota[k] = temp;
                                for (i = 1; i <= n; ++i) {
                                    temp = alpha * z[i][nact] + beta * z[i][k];
                                    z[i][nact] = alpha * z[i][k] - beta * z[i][nact];
                                    z[i][k] = temp;
                                }
                                iact[nact] = iact[k];
                                iact[k] = kk;
                                temp = vmultc[k];
                                vmultc[k] = vmultc[nact];
                                vmultc[nact] = temp;
                            }

                            //     If stage one is in progress, then set SDIRN to the direction of the next
                            //     change to the current vector of variables.
                            if (mcon > m) {
                                //     Pick the next search direction of stage two.
                                temp = 1.0 / zdota[nact];
                                for (k = 1; k <= n; ++k) { sdirn[k] = temp * z[k][nact]; }
                            } else {
                                kk = iact[nact];
                                temp = (this.DOT_PRODUCT(
                                            this.PART(sdirn, 1, n),
                                            this.PART(this.COL(a, kk), 1, n)
                                        ) - 1.0) / zdota[nact];
                                for (k = 1; k <= n; ++k) { sdirn[k] -= temp * z[k][nact]; }
                            }
                        }

                        //     Calculate the step to the boundary of the trust region or take the step
                        //     that reduces RESMAX to zero. The two statements below that include the
                        //     factor 1.0E-6 prevent some harmless underflows that occurred in a test
                        //     calculation. Further, we skip the step if it could be zero within a
                        //     reasonable tolerance for computer rounding errors.
                        dd = rho * rho;
                        sd = 0.0;
                        ss = 0.0;
                        for (i = 1; i <= n; ++i) {
                            if (Math.abs(dx[i]) >= 1.0E-6 * rho) { dd -= dx[i] * dx[i]; }
                            sd += dx[i] * sdirn[i];
                            ss += sdirn[i] * sdirn[i];
                        }
                        if (dd <= 0.0) { break L_60; }
                        temp = Math.sqrt(ss * dd);
                        if (Math.abs(sd) >= 1.0E-6 * temp) { temp = Math.sqrt(ss * dd + sd * sd); }
                        stpful = dd / (temp + sd);
                        step = stpful;
                        if (mcon === m) {
                            acca = step + 0.1 * resmax;
                            accb = step + 0.2 * resmax;
                            if (step >= acca || acca >= accb) { break L_70; }
                            step = Math.min(step, resmax);
                        }

                        //     Set DXNEW to the new variables if STEP is the steplength, and reduce
                        //     RESMAX to the corresponding maximum residual if stage one is being done.
                        //     Because DXNEW will be changed during the calculation of some Lagrange
                        //     multipliers, it will be restored to the following value later.
                        for (k = 1; k <= n; ++k) { dxnew[k] = dx[k] + step * sdirn[k]; }
                        if (mcon === m) {
                            resold = resmax;
                            resmax = 0.0;
                            for (k = 1; k <= nact; ++k) {
                                kk = iact[k];
                                temp = b[kk] - this.DOT_PRODUCT(
                                        this.PART(this.COL(a, kk), 1, n), this.PART(dxnew, 1, n)
                                    );
                                resmax = Math.max(resmax, temp);
                            }
                        }

                        //     Set VMULTD to the VMULTC vector that would occur if DX became DXNEW. A
                        //     device is included to force VMULTD(K) = 0.0 if deviations from this value
                        //     can be attributed to computer rounding errors. First calculate the new
                        //     Lagrange multipliers.
                        //{
                            k = nact;
                            do {
                                zdotw = 0.0;
                                zdwabs = 0.0;
                                for (i = 1; i <= n; ++i) {
                                    temp = z[i][k] * dxnew[i];
                                    zdotw += temp;
                                    zdwabs += Math.abs(temp);
                                }
                                acca = zdwabs + 0.1 * Math.abs(zdotw);
                                accb = zdwabs + 0.2 * Math.abs(zdotw);
                                if (zdwabs >= acca || acca >= accb) { zdotw = 0.0; }
                                vmultd[k] = zdotw / zdota[k];
                                if (k >= 2) {
                                    kk = iact[k];
                                    for (i = 1; i <= n; ++i) { dxnew[i] -= vmultd[k] * a[i][kk]; }
                                }
                            } while (k-- >= 2);
                            if (mcon > m) { vmultd[nact] = Math.max(0.0, vmultd[nact]); }
                        //}

                        //     Complete VMULTC by finding the new constraint residuals.

                        for (k = 1; k <= n; ++k) { dxnew[k] = dx[k] + step * sdirn[k]; }
                        if (mcon > nact) {
                            kl = nact + 1;
                            for (k = kl; k <= mcon; ++k) {
                                kk = iact[k];
                                total = resmax - b[kk];
                                sumabs = resmax + Math.abs(b[kk]);
                                for (i = 1; i <= n; ++i) {
                                    temp = a[i][kk] * dxnew[i];
                                    total += temp;
                                    sumabs += Math.abs(temp);
                                }
                                acca = sumabs + 0.1 * Math.abs(total);
                                accb = sumabs + 0.2 * Math.abs(total);
                                if (sumabs >= acca || acca >= accb) { total = 0.0; }
                                vmultd[k] = total;
                            }
                        }

                        //     Calculate the fraction of the step from DX to DXNEW that will be taken.

                        ratio = 1.0;
                        icon = 0;
                        for (k = 1; k <= mcon; ++k) {
                            if (vmultd[k] < 0.0) {
                                temp = vmultc[k] / (vmultc[k] - vmultd[k]);
                                if (temp < ratio) {
                                    ratio = temp;
                                    icon = k;
                                }
                            }
                        }

                        //     Update DX, VMULTC and RESMAX.

                        temp = 1.0 - ratio;
                        for (k = 1; k <= n; ++k) { dx[k] = temp * dx[k] + ratio * dxnew[k]; }
                        for (k = 1; k <= mcon; ++k) {
                            vmultc[k] = Math.max(0.0, temp * vmultc[k] + ratio * vmultd[k]);
                        }
                        if (mcon === m) { resmax = resold + ratio * (resmax - resold); }

                        //     If the full step is not acceptable then begin another iteration.
                        //     Otherwise switch to stage two or end the calculation.
                    } while (icon > 0);

                    if (step === stpful) {
                        return true;
                    }

                } while (true);

                //     We employ any freedom that may be available to reduce the objective
                //     function before returning a DX whose length is less than RHO.

            } while (mcon === m);

            return false;
        },

        PrintIterationResult: function(nfvals, f, resmax,  x,  n, iprint) {
            if (iprint > 1) { console.log("NFVALS = "+nfvals+"  F = "+f+"  MAXCV = "+resmax); }
            if (iprint > 1) { console.log("X = " + this.PART(x, 1, n)); }
        },

        ROW: function(src, rowidx) {
            return src[rowidx].slice();
            // var col,
            //     cols = src[0].length,
            //     dest = this.arr(cols);

            // for (col = 0; col < cols; ++col) {
            //     dest[col] = src[rowidx][col];
            // }
            // return dest;
        },

        COL: function(src, colidx) {
            var row,
                rows = src.length,
                dest = this.arr(rows);
            for (row = 0; row < rows; ++row) {
                dest[row] = src[row][colidx];
            }
            return dest;
        },

        PART: function(src, from, to) {
            return src.slice(from, to + 1);
            // var srcidx,
            //     dest = this.arr(to - from + 1),
            //     destidx = 0;
            // for (srcidx = from; srcidx <= to; ++srcidx, ++destidx) {
            //     dest[destidx] = src[srcidx];
            // }
            // return dest;
        },

        FORMAT: function(x) {
            return x.join(',');
            // var i, fmt = "";
            // for (i = 0; i < x.length; ++i) {
            //     fmt += ", " + x[i];
            // }
            // return fmt;
        },

        DOT_PRODUCT: function(lhs,  rhs) {
            var i, sum = 0.0,
                len = lhs.length;
            for (i = 0; i < len; ++i) {
                sum += lhs[i] * rhs[i];
            }
            return sum;
        }

    };

    return JXG.Math.Nlp;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/math
 utils/type
 */

define('math/statistics',['jxg', 'math/math', 'utils/type'], function (JXG, Mat, Type) {

    "use strict";

    /**
     * Functions for mathematical statistics. Most functions are like in the statistics package R.
     * @name JXG.Math.Statistics
     * @exports Mat.Statistics as JXG.Math.Statistics
     * @namespace
     */
    Mat.Statistics = {
        /**
         * Sums up all elements of the given array.
         * @param {Array} arr An array of numbers.
         * @returns {Number}
         * @memberof JXG.Math.Statistics
         */
        sum: function (arr) {
            var i,
                len = arr.length,
                res = 0;

            for (i = 0; i < len; i++) {
                res += arr[i];
            }
            return res;
        },

        /**
         * Multiplies all elements of the given array.
         * @param {Array} arr An array of numbers.
         * @returns {Number}
         * @memberof JXG.Math.Statistics
         */
        prod: function (arr) {
            var i,
                len = arr.length,
                res = 1;

            for (i = 0; i < len; i++) {
                res *= arr[i];
            }
            return res;
        },

        /**
         * Determines the mean value of the values given in an array.
         * @param {Array} arr
         * @returns {Number}
         * @memberof JXG.Math.Statistics
         */
        mean: function (arr) {
            if (arr.length > 0) {
                return this.sum(arr) / arr.length;
            }

            return 0.0;
        },

        /**
         * The median of a finite set of values is the value that divides the set
         * into two equal sized subsets.
         * @param {Array} arr The set of values.
         * @returns {Number}
         * @memberof JXG.Math.Statistics
         */
        median: function (arr) {
            var tmp, len;

            if (arr.length > 0) {
                if (ArrayBuffer.isView(arr)) {
                    tmp = new Float64Array(arr);
                    tmp.sort();
                } else {
                    tmp = arr.slice(0);
                    tmp.sort(function (a, b) {
                        return a - b;
                    });
                }
                len = tmp.length;

                if (len & 1) { // odd
                    return tmp[parseInt(len * 0.5, 10)];
                }

                return (tmp[len * 0.5 - 1] + tmp[len * 0.5]) * 0.5;
            }

            return 0.0;
        },

        /**
         * The P-th percentile ( 0 < P ≤ 100 ) of a list of N ordered values (sorted from least to greatest)
         * is the smallest value in the list such that no more than P percent of the data is strictly less
         * than the value and at least P percent of the data is less than or equal to that value. See {@link https://en.wikipedia.org/wiki/Percentile}.
         *
         * Here, the <i>linear interpolation between closest ranks</i> method is used.
         * @param {Array} arr The set of values, need not be ordered.
         * @param {Number|Array} percentile One or several percentiles
         * @returns {Number|Array} Depending if a number or an array is the input for percentile, a number or an array containing the percentils
         * is returned.
         */
        percentile: function(arr, percentile) {
            var tmp, len, i, p, res = [], per;

            if (arr.length > 0) {
                if (ArrayBuffer.isView(arr)) {
                    tmp = new Float64Array(arr);
                    tmp.sort();
                } else {
                    tmp = arr.slice(0);
                    tmp.sort(function (a, b) {
                        return a - b;
                    });
                }
                len = tmp.length;

                if (Type.isArray(percentile)) {
                    p = percentile;
                } else {
                    p = [percentile];
                }

                for (i = 0; i < p.length; i++) {
                    per = len * p[i] * 0.01;
                    if (parseInt(per, 10) === per) {
                        res.push( (tmp[per - 1] + tmp[per]) * 0.5 );
                    } else {
                        res.push( tmp[parseInt(per, 10)] );
                    }
                }

                if (Type.isArray(percentile)) {
                    return res;
                } else {
                    return res[0];
                }
            }

            return 0.0;
        },

        /**
         * Bias-corrected sample variance. A variance is a measure of how far a
         * set of numbers are spread out from each other.
         * @param {Array} arr
         * @returns {Number}
         * @memberof JXG.Math.Statistics
         */
        variance: function (arr) {
            var m, res, i, len = arr.length;

            if (len > 1) {
                m = this.mean(arr);
                res = 0;
                for (i = 0; i < len; i++) {
                    res += (arr[i] - m) * (arr[i] - m);
                }
                return res / (arr.length - 1);
            }

            return 0.0;
        },

        /**
         * Determines the <strong>s</strong>tandard <strong>d</strong>eviation which shows how much
         * variation there is from the average value of a set of numbers.
         * @param {Array} arr
         * @returns {Number}
         * @memberof JXG.Math.Statistics
         */
        sd: function (arr) {
            return Math.sqrt(this.variance(arr));
        },

        /**
         * Weighted mean value is basically the same as {@link JXG.Math.Statistics.mean} but here the values
         * are weighted, i.e. multiplied with another value called <em>weight</em>. The weight values are given
         * as a second array with the same length as the value array..
         * @throws {Error} If the dimensions of the arrays don't match.
         * @param {Array} arr Set of alues.
         * @param {Array} w Weight values.
         * @returns {Number}
         * @memberof JXG.Math.Statistics
         */
        weightedMean: function (arr, w) {
            if (arr.length !== w.length) {
                throw new Error('JSXGraph error (Math.Statistics.weightedMean): Array dimension mismatch.');
            }

            if (arr.length > 0) {
                return this.mean(this.multiply(arr, w));
            }

            return 0.0;
        },

        /**
         * Extracts the maximum value from the array.
         * @param {Array} arr
         * @returns {Number} The highest number from the array. It returns <tt>NaN</tt> if not every element could be
         * interpreted as a number and <tt>-Infinity</tt> if an empty array is given or no element could be interpreted
         * as a number.
         * @memberof JXG.Math.Statistics
         */
        max: function (arr) {
            return Math.max.apply(this, arr);
        },

        /**
         * Extracts the minimum value from the array.
         * @param {Array} arr
         * @returns {Number} The lowest number from the array. It returns <tt>NaN</tt> if not every element could be
         * interpreted as a number and <tt>Infinity</tt> if an empty array is given or no element could be interpreted
         * as a number.
         * @memberof JXG.Math.Statistics
         */
        min: function (arr) {
            return Math.min.apply(this, arr);
        },

        /**
         * Determines the lowest and the highest value from the given array.
         * @param {Array} arr
         * @returns {Array} The minimum value as the first and the maximum value as the second value.
         * @memberof JXG.Math.Statistics
         */
        range: function (arr) {
            return [this.min(arr), this.max(arr)];
        },

        /**
         * Determines the absolute value of every given value.
         * @param {Array|Number} arr
         * @returns {Array|Number}
         * @memberof JXG.Math.Statistics
         */
        abs: function (arr) {
            var i, len, res;

            if (Type.isArray(arr)) {
                if (arr.map) {
                    res = arr.map(Math.abs);
                } else {
                    len = arr.length;
                    res = [];

                    for (i = 0; i < len; i++) {
                        res[i] = Math.abs(arr[i]);
                    }
                }
            } else if (ArrayBuffer.isView(arr)) {
                res = arr.map(Math.abs);
            } else {
                res = Math.abs(arr);
            }
            return res;
        },

        /**
         * Adds up two (sequences of) values. If one value is an array and the other one is a number the number
         * is added to every element of the array. If two arrays are given and the lengths don't match the shortest
         * length is taken.
         * @param {Array|Number} arr1
         * @param {Array|Number} arr2
         * @returns {Array|Number}
         * @memberof JXG.Math.Statistics
         */
        add: function (arr1, arr2) {
            var i, len, res = [];

            arr1 = Type.evalSlider(arr1);
            arr2 = Type.evalSlider(arr2);

            if (Type.isArray(arr1) && Type.isNumber(arr2)) {
                len = arr1.length;

                for (i = 0; i < len; i++) {
                    res[i] = arr1[i] + arr2;
                }
            } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
                len = arr2.length;

                for (i = 0; i < len; i++) {
                    res[i] = arr1 + arr2[i];
                }
            } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
                len = Math.min(arr1.length, arr2.length);

                for (i = 0; i < len; i++) {
                    res[i] = arr1[i] + arr2[i];
                }
            } else {
                res = arr1 + arr2;
            }

            return res;
        },

        /**
         * Divides two (sequences of) values. If two arrays are given and the lengths don't match the shortest length
         * is taken.
         * @param {Array|Number} arr1 Dividend
         * @param {Array|Number} arr2 Divisor
         * @returns {Array|Number}
         * @memberof JXG.Math.Statistics
         */
        div: function (arr1, arr2) {
            var i, len, res = [];

            arr1 = Type.evalSlider(arr1);
            arr2 = Type.evalSlider(arr2);

            if (Type.isArray(arr1) && Type.isNumber(arr2)) {
                len = arr1.length;

                for (i = 0; i < len; i++) {
                    res[i] = arr1[i] / arr2;
                }
            } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
                len = arr2.length;

                for (i = 0; i < len; i++) {
                    res[i] = arr1 / arr2[i];
                }
            } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
                len = Math.min(arr1.length, arr2.length);

                for (i = 0; i < len; i++) {
                    res[i] = arr1[i] / arr2[i];
                }
            } else {
                res = arr1 / arr2;
            }

            return res;
        },

        /**
         * @function
         * @deprecated Use {@link JXG.Math.Statistics.div} instead.
         * @memberof JXG.Math.Statistics
         */
        divide: function () {
            JXG.deprecated('Statistics.divide()', 'Statistics.div()');
            Mat.Statistics.div.apply(Mat.Statistics, arguments);
        },

        /**
         * Divides two (sequences of) values and returns the remainder. If two arrays are given and the lengths don't
         * match the shortest length is taken.
         * @param {Array|Number} arr1 Dividend
         * @param {Array|Number} arr2 Divisor
         * @param {Boolean} [math=false] Mathematical mod or symmetric mod? Default is symmetric, the JavaScript <tt>%</tt> operator.
         * @returns {Array|Number}
         * @memberof JXG.Math.Statistics
         */
        mod: function (arr1, arr2, math) {
            var i, len, res = [], mod = function (a, m) {
                return a % m;
            };

            math = Type.def(math, false);

            if (math) {
                mod = Mat.mod;
            }

            arr1 = Type.evalSlider(arr1);
            arr2 = Type.evalSlider(arr2);

            if (Type.isArray(arr1) && Type.isNumber(arr2)) {
                len = arr1.length;

                for (i = 0; i < len; i++) {
                    res[i] = mod(arr1[i], arr2);
                }
            } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
                len = arr2.length;

                for (i = 0; i < len; i++) {
                    res[i] = mod(arr1, arr2[i]);
                }
            } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
                len = Math.min(arr1.length, arr2.length);

                for (i = 0; i < len; i++) {
                    res[i] = mod(arr1[i], arr2[i]);
                }
            } else {
                res = mod(arr1, arr2);
            }

            return res;
        },

        /**
         * Multiplies two (sequences of) values. If one value is an array and the other one is a number the number
         * is multiplied to every element of the array. If two arrays are given and the lengths don't match the shortest
         * length is taken.
         * @param {Array|Number} arr1
         * @param {Array|Number} arr2
         * @returns {Array|Number}
         * @memberof JXG.Math.Statistics
         */
        multiply: function (arr1, arr2) {
            var i, len, res = [];

            arr1 = Type.evalSlider(arr1);
            arr2 = Type.evalSlider(arr2);

            if (Type.isArray(arr1) && Type.isNumber(arr2)) {
                len = arr1.length;

                for (i = 0; i < len; i++) {
                    res[i] = arr1[i] * arr2;
                }
            } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
                len = arr2.length;

                for (i = 0; i < len; i++) {
                    res[i] = arr1 * arr2[i];
                }
            } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
                len = Math.min(arr1.length, arr2.length);

                for (i = 0; i < len; i++) {
                    res[i] = arr1[i] * arr2[i];
                }
            } else {
                res = arr1 * arr2;
            }

            return res;
        },

        /**
         * Subtracts two (sequences of) values. If two arrays are given and the lengths don't match the shortest
         * length is taken.
         * @param {Array|Number} arr1 Minuend
         * @param {Array|Number} arr2 Subtrahend
         * @returns {Array|Number}
         * @memberof JXG.Math.Statistics
         */
        subtract: function (arr1, arr2) {
            var i, len, res = [];

            arr1 = Type.evalSlider(arr1);
            arr2 = Type.evalSlider(arr2);

            if (Type.isArray(arr1) && Type.isNumber(arr2)) {
                len = arr1.length;

                for (i = 0; i < len; i++) {
                    res[i] = arr1[i] - arr2;
                }
            } else if (Type.isNumber(arr1) && Type.isArray(arr2)) {
                len = arr2.length;

                for (i = 0; i < len; i++) {
                    res[i] = arr1 - arr2[i];
                }
            } else if (Type.isArray(arr1) && Type.isArray(arr2)) {
                len = Math.min(arr1.length, arr2.length);

                for (i = 0; i < len; i++) {
                    res[i] = arr1[i] - arr2[i];
                }
            } else {
                res = arr1 - arr2;
            }

            return res;
        },

        /**
         * The Theil-Sen estimator can be used to determine a more robust linear regression of a set of sample
         * points than least squares regression in {@link JXG.Math.Numerics.regressionPolynomial}.
         *
         * If the function should be applied to an array a of points, a the coords array can be generated with
         * JavaScript array.map:
         *
         * <pre>
         * JXG.Math.Statistics.TheilSenRegression(a.map(el => el.coords));
         * </pre>
         *
         * @param {Array} coords Array of {@link JXG.Coords}.
         * @returns {Array} A stdform array of the regression line.
         * @memberof JXG.Math.Statistics
         *
         * @example
         * var board = JXG.JSXGraph.initBoard('jxgbox', { boundingbox: [-6,6,6,-6], axis : true });
         * var a=[];
         * a[0]=board.create('point', [0,0]);
         * a[1]=board.create('point', [3,0]);
         * a[2]=board.create('point', [0,3]);
         *
         * board.create('line', [
         *     () => JXG.Math.Statistics.TheilSenRegression(a.map(el => el.coords))
         *   ],
         *   {strokeWidth:1, strokeColor:'black'});
         *
         * </pre><div id="JXG0a28be85-91c5-44d3-aae6-114e81217cf0" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG0a28be85-91c5-44d3-aae6-114e81217cf0',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var board = JXG.JSXGraph.initBoard('jxgbox', { boundingbox: [-6,6,6,-6], axis : true });
         *     var a=[];
         *     a[0]=board.create('point', [0,0]);
         *     a[1]=board.create('point', [3,0]);
         *     a[2]=board.create('point', [0,3]);
         *
         *     board.create('line', [
         *         () => JXG.Math.Statistics.TheilSenRegression(a.map(el => el.coords))
         *       ],
         *       {strokeWidth:1, strokeColor:'black'});
         *
         *     })();
         *
         * </script><pre>
         *
         */
        TheilSenRegression: function (coords) {
            var i, j,
                slopes = [],
                tmpslopes = [],
                yintercepts = [];

            for (i = 0; i < coords.length; i++) {
                tmpslopes.length = 0;

                for (j = 0; j < coords.length; j++) {
                    if (Math.abs(coords[j].usrCoords[1] - coords[i].usrCoords[1]) > Mat.eps) {
                        tmpslopes[j] = (coords[j].usrCoords[2] - coords[i].usrCoords[2]) /
                            (coords[j].usrCoords[1] - coords[i].usrCoords[1]);
                    }
                }

                slopes[i] = this.median(tmpslopes);
                yintercepts.push(coords[i].usrCoords[2] - slopes[i] * coords[i].usrCoords[1]);
            }

            return [this.median(yintercepts), this.median(slopes), -1];
        },

        /**
         * Generate values of a standard normal random variable with the Marsaglia polar method, see
         * https://en.wikipedia.org/wiki/Marsaglia_polar_method .
         *
         * @param {Number} mean mean value of the normal distribution
         * @param {Number} stdDev standard deviation of the normal distribution
         * @returns {Number} value of a standard normal random variable
         */
        generateGaussian: function (mean, stdDev) {
            var u, v, s;

            if (this.hasSpare) {
                this.hasSpare = false;
                return this.spare * stdDev + mean;
            }

            do {
                u = Math.random() * 2 - 1;
                v = Math.random() * 2 - 1;
                s = u * u + v * v;
            } while (s >= 1 || s === 0);

            s = Math.sqrt(-2.0 * Math.log(s) / s);

            this.spare = v * s;
            this.hasSpare = true;
            return mean + stdDev * u * s;
        }
    };

    return Mat.Statistics;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Andreas Walter,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 base/coords
 math/math
 math/numerics
 utils/type
 */

/**
 * @fileoverview This file contains the Math.Geometry namespace for calculating algebraic/geometric
 * stuff like intersection points, angles, midpoint, and so on.
 */

define('math/geometry',[
    'jxg', 'base/constants', 'base/coords', 'math/math', 'math/numerics', 'utils/type', 'utils/expect'
], function (JXG, Const, Coords, Mat, Numerics, Type, Expect) {

    "use strict";

    /**
     * Math.Geometry namespace definition. This namespace holds geometrical algorithms,
     * especially intersection algorithms.
     * @name JXG.Math.Geometry
     * @namespace
     */
    Mat.Geometry = {};

// the splitting is necessary due to the shortcut for the circumcircleMidpoint method to circumcenter.

    JXG.extend(Mat.Geometry, /** @lends JXG.Math.Geometry */ {
        /* ***************************************/
        /* *** GENERAL GEOMETRIC CALCULATIONS ****/
        /* ***************************************/

        /**
         * Calculates the angle defined by the points A, B, C.
         * @param {JXG.Point,Array} A A point  or [x,y] array.
         * @param {JXG.Point,Array} B Another point or [x,y] array.
         * @param {JXG.Point,Array} C A circle - no, of course the third point or [x,y] array.
         * @deprecated Use {@link JXG.Math.Geometry.rad} instead.
         * @see #rad
         * @see #trueAngle
         * @returns {Number} The angle in radian measure.
         */
        angle: function (A, B, C) {
            var u, v, s, t,
                a = [],
                b = [],
                c = [];

            JXG.deprecated('Geometry.angle()', 'Geometry.rad()');
            if (A.coords) {
                a[0] = A.coords.usrCoords[1];
                a[1] = A.coords.usrCoords[2];
            } else {
                a[0] = A[0];
                a[1] = A[1];
            }

            if (B.coords) {
                b[0] = B.coords.usrCoords[1];
                b[1] = B.coords.usrCoords[2];
            } else {
                b[0] = B[0];
                b[1] = B[1];
            }

            if (C.coords) {
                c[0] = C.coords.usrCoords[1];
                c[1] = C.coords.usrCoords[2];
            } else {
                c[0] = C[0];
                c[1] = C[1];
            }

            u = a[0] - b[0];
            v = a[1] - b[1];
            s = c[0] - b[0];
            t = c[1] - b[1];

            return Math.atan2(u * t - v * s, u * s + v * t);
        },

        /**
         * Calculates the angle defined by the three points A, B, C if you're going from A to C around B counterclockwise.
         * @param {JXG.Point,Array} A Point or [x,y] array
         * @param {JXG.Point,Array} B Point or [x,y] array
         * @param {JXG.Point,Array} C Point or [x,y] array
         * @see #rad
         * @returns {Number} The angle in degrees.
         */
        trueAngle: function (A, B, C) {
            return this.rad(A, B, C) * 57.295779513082323; // *180.0/Math.PI;
        },

        /**
         * Calculates the internal angle defined by the three points A, B, C if you're going from A to C around B counterclockwise.
         * @param {JXG.Point,Array} A Point or [x,y] array
         * @param {JXG.Point,Array} B Point or [x,y] array
         * @param {JXG.Point,Array} C Point or [x,y] array
         * @see #trueAngle
         * @returns {Number} Angle in radians.
         */
        rad: function (A, B, C) {
            var ax, ay, bx, by, cx, cy, phi;

            if (A.coords) {
                ax = A.coords.usrCoords[1];
                ay = A.coords.usrCoords[2];
            } else {
                ax = A[0];
                ay = A[1];
            }

            if (B.coords) {
                bx = B.coords.usrCoords[1];
                by = B.coords.usrCoords[2];
            } else {
                bx = B[0];
                by = B[1];
            }

            if (C.coords) {
                cx = C.coords.usrCoords[1];
                cy = C.coords.usrCoords[2];
            } else {
                cx = C[0];
                cy = C[1];
            }

            phi = Math.atan2(cy - by, cx - bx) - Math.atan2(ay - by, ax - bx);

            if (phi < 0) {
                phi += 6.2831853071795862;
            }

            return phi;
        },

        /**
         * Calculates a point on the bisection line between the three points A, B, C.
         * As a result, the bisection line is defined by two points:
         * Parameter B and the point with the coordinates calculated in this function.
         * Does not work for ideal points.
         * @param {JXG.Point} A Point
         * @param {JXG.Point} B Point
         * @param {JXG.Point} C Point
         * @param [board=A.board] Reference to the board
         * @returns {JXG.Coords} Coordinates of the second point defining the bisection.
         */
        angleBisector: function (A, B, C, board) {
            var phiA, phiC, phi,
                Ac = A.coords.usrCoords,
                Bc = B.coords.usrCoords,
                Cc = C.coords.usrCoords,
                x, y;

            if (!Type.exists(board)) {
                board = A.board;
            }

            // Parallel lines
            if (Bc[0] === 0) {
                return new Coords(Const.COORDS_BY_USER,
                    [1, (Ac[1] + Cc[1]) * 0.5, (Ac[2] + Cc[2]) * 0.5], board);
            }

            // Non-parallel lines
            x = Ac[1] - Bc[1];
            y = Ac[2] - Bc[2];
            phiA =  Math.atan2(y, x);

            x = Cc[1] - Bc[1];
            y = Cc[2] - Bc[2];
            phiC =  Math.atan2(y, x);

            phi = (phiA + phiC) * 0.5;

            if (phiA > phiC) {
                phi += Math.PI;
            }

            x = Math.cos(phi) + Bc[1];
            y = Math.sin(phi) + Bc[2];

            return new Coords(Const.COORDS_BY_USER, [1, x, y], board);
        },

        // /**
        //  * Calculates a point on the m-section line between the three points A, B, C.
        //  * As a result, the m-section line is defined by two points:
        //  * Parameter B and the point with the coordinates calculated in this function.
        //  * The m-section generalizes the bisector to any real number.
        //  * For example, the trisectors of an angle are simply the 1/3-sector and the 2/3-sector.
        //  * Does not work for ideal points.
        //  * @param {JXG.Point} A Point
        //  * @param {JXG.Point} B Point
        //  * @param {JXG.Point} C Point
        //  * @param {Number} m Number
        //  * @param [board=A.board] Reference to the board
        //  * @returns {JXG.Coords} Coordinates of the second point defining the bisection.
        //  */
        // angleMsector: function (A, B, C, m, board) {
        //     var phiA, phiC, phi,
        //         Ac = A.coords.usrCoords,
        //         Bc = B.coords.usrCoords,
        //         Cc = C.coords.usrCoords,
        //         x, y;

        //     if (!Type.exists(board)) {
        //         board = A.board;
        //     }

        //     // Parallel lines
        //     if (Bc[0] === 0) {
        //         return new Coords(Const.COORDS_BY_USER,
        //             [1, (Ac[1] + Cc[1]) * m, (Ac[2] + Cc[2]) * m], board);
        //     }

        //     // Non-parallel lines
        //     x = Ac[1] - Bc[1];
        //     y = Ac[2] - Bc[2];
        //     phiA =  Math.atan2(y, x);

        //     x = Cc[1] - Bc[1];
        //     y = Cc[2] - Bc[2];
        //     phiC =  Math.atan2(y, x);

        //     phi = phiA + ((phiC - phiA) * m);

        //     if (phiA - phiC > Math.PI) {
        //         phi += 2*m*Math.PI;
        //     }

        //     x = Math.cos(phi) + Bc[1];
        //     y = Math.sin(phi) + Bc[2];

        //     return new Coords(Const.COORDS_BY_USER, [1, x, y], board);
        // },

        /**
         * Reflects the point along the line.
         * @param {JXG.Line} line Axis of reflection.
         * @param {JXG.Point} point Point to reflect.
         * @param [board=point.board] Reference to the board
         * @returns {JXG.Coords} Coordinates of the reflected point.
         */
        reflection: function (line, point, board) {
            // (v,w) defines the slope of the line
            var x0, y0, x1, y1, v, w, mu,
                pc = point.coords.usrCoords,
                p1c = line.point1.coords.usrCoords,
                p2c = line.point2.coords.usrCoords;

            if (!Type.exists(board)) {
                board = point.board;
            }

            v = p2c[1] - p1c[1];
            w = p2c[2] - p1c[2];

            x0 = pc[1] - p1c[1];
            y0 = pc[2] - p1c[2];

            mu = (v * y0 - w * x0) / (v * v + w * w);

            // point + mu*(-y,x) is the perpendicular foot
            x1 = pc[1] + 2 * mu * w;
            y1 = pc[2] - 2 * mu * v;

            return new Coords(Const.COORDS_BY_USER, [x1, y1], board);
        },

        /**
         * Computes the new position of a point which is rotated
         * around a second point (called rotpoint) by the angle phi.
         * @param {JXG.Point} rotpoint Center of the rotation
         * @param {JXG.Point} point point to be rotated
         * @param {Number} phi rotation angle in arc length
         * @param {JXG.Board} [board=point.board] Reference to the board
         * @returns {JXG.Coords} Coordinates of the new position.
         */
        rotation: function (rotpoint, point, phi, board) {
            var x0, y0, c, s, x1, y1,
                pc = point.coords.usrCoords,
                rotpc = rotpoint.coords.usrCoords;

            if (!Type.exists(board)) {
                board = point.board;
            }

            x0 = pc[1] - rotpc[1];
            y0 = pc[2] - rotpc[2];

            c = Math.cos(phi);
            s = Math.sin(phi);

            x1 = x0 * c - y0 * s + rotpc[1];
            y1 = x0 * s + y0 * c + rotpc[2];

            return new Coords(Const.COORDS_BY_USER, [x1, y1], board);
        },

        /**
         * Calculates the coordinates of a point on the perpendicular to the given line through
         * the given point.
         * @param {JXG.Line} line A line.
         * @param {JXG.Point} point Point which is projected to the line.
         * @param {JXG.Board} [board=point.board] Reference to the board
         * @returns {Array} Array of length two containing coordinates of a point on the perpendicular to the given line
         *                  through the given point and boolean flag "change".
         */
        perpendicular: function (line, point, board) {
            var x, y, change,
                c, z,
                A = line.point1.coords.usrCoords,
                B = line.point2.coords.usrCoords,
                C = point.coords.usrCoords;

            if (!Type.exists(board)) {
                board = point.board;
            }

            // special case: point is the first point of the line
            if (point === line.point1) {
                x = A[1] + B[2] - A[2];
                y = A[2] - B[1] + A[1];
                z = A[0] * B[0];

                if (Math.abs(z) < Mat.eps) {
                    x =  B[2];
                    y = -B[1];
                }
                c = [z, x, y];
                change = true;

            // special case: point is the second point of the line
            } else if (point === line.point2) {
                x = B[1] + A[2] - B[2];
                y = B[2] - A[1] + B[1];
                z = A[0] * B[0];

                if (Math.abs(z) < Mat.eps) {
                    x =  A[2];
                    y = -A[1];
                }
                c = [z, x, y];
                change = false;

            // special case: point lies somewhere else on the line
            } else if (Math.abs(Mat.innerProduct(C, line.stdform, 3)) < Mat.eps) {
                x = C[1] + B[2] - C[2];
                y = C[2] - B[1] + C[1];
                z = B[0];

                if (Math.abs(z) < Mat.eps) {
                    x =  B[2];
                    y = -B[1];
                }

                change = true;
                if (Math.abs(z) > Mat.eps && Math.abs(x - C[1]) < Mat.eps && Math.abs(y - C[2]) < Mat.eps) {
                    x = C[1] + A[2] - C[2];
                    y = C[2] - A[1] + C[1];
                    change = false;
                }
                c = [z, x, y];

            // general case: point does not lie on the line
            // -> calculate the foot of the dropped perpendicular
            } else {
                c = [0, line.stdform[1], line.stdform[2]];
                c = Mat.crossProduct(c, C);                  // perpendicuar to line
                c = Mat.crossProduct(c, line.stdform);       // intersection of line and perpendicular
                change = true;
            }

            return [new Coords(Const.COORDS_BY_USER, c, board), change];
        },

        /**
         * @deprecated Please use {@link JXG.Math.Geometry.circumcenter} instead.
         */
        circumcenterMidpoint: function () {
            JXG.deprecated('Geometry.circumcenterMidpoint()', 'Geometry.circumcenter()');
            this.circumcenter.apply(this, arguments);
        },

        /**
         * Calculates the center of the circumcircle of the three given points.
         * @param {JXG.Point} point1 Point
         * @param {JXG.Point} point2 Point
         * @param {JXG.Point} point3 Point
         * @param {JXG.Board} [board=point1.board] Reference to the board
         * @returns {JXG.Coords} Coordinates of the center of the circumcircle of the given points.
         */
        circumcenter: function (point1, point2, point3, board) {
            var u, v, m1, m2,
                A = point1.coords.usrCoords,
                B = point2.coords.usrCoords,
                C = point3.coords.usrCoords;

            if (!Type.exists(board)) {
                board = point1.board;
            }

            u = [B[0] - A[0], -B[2] + A[2], B[1] - A[1]];
            v = [(A[0] + B[0])  * 0.5, (A[1] + B[1]) * 0.5, (A[2] + B[2]) * 0.5];
            m1 = Mat.crossProduct(u, v);

            u = [C[0] - B[0], -C[2] + B[2], C[1] - B[1]];
            v = [(B[0] + C[0]) * 0.5, (B[1] + C[1]) * 0.5, (B[2] + C[2]) * 0.5];
            m2 = Mat.crossProduct(u, v);

            return new Coords(Const.COORDS_BY_USER, Mat.crossProduct(m1, m2), board);
        },

        /**
         * Calculates the Euclidean distance for two given arrays of the same length.
         * @param {Array} array1 Array of Number
         * @param {Array} array2 Array of Number
         * @param {Number} [n] Length of the arrays. Default is the minimum length of the given arrays.
         * @returns {Number} Euclidean distance of the given vectors.
         */
        distance: function (array1, array2, n) {
            var i,
                sum = 0;

            if (!n) {
                n = Math.min(array1.length, array2.length);
            }

            for (i = 0; i < n; i++) {
                sum += (array1[i] - array2[i]) * (array1[i] - array2[i]);
            }

            return Math.sqrt(sum);
        },

        /**
         * Calculates Euclidean distance for two given arrays of the same length.
         * If one of the arrays contains a zero in the first coordinate, and the Euclidean distance
         * is different from zero it is a point at infinity and we return Infinity.
         * @param {Array} array1 Array containing elements of type number.
         * @param {Array} array2 Array containing elements of type number.
         * @param {Number} [n] Length of the arrays. Default is the minimum length of the given arrays.
         * @returns {Number} Euclidean (affine) distance of the given vectors.
         */
        affineDistance: function (array1, array2, n) {
            var d;

            d = this.distance(array1, array2, n);

            if (d > Mat.eps && (Math.abs(array1[0]) < Mat.eps || Math.abs(array2[0]) < Mat.eps)) {
                return Infinity;
            }

            return d;
        },

        /**
         * Affine ratio of three collinear points a, b, c: (c - a) / (b - a).
         * If r > 1 or r < 0 then c is outside of the segment ab.
         *
         * @param {Array|JXG.Coords} a
         * @param {Array|JXG.Coords} b
         * @param {Array|JXG.Coords} c
         * @returns {Number} affine ratio (c - a) / (b - a)
         */
        affineRatio: function(a, b, c) {
            var r = 0.0, dx;

            if (Type.exists(a.usrCoords)) {
                a = a.usrCoords;
            }
            if (Type.exists(b.usrCoords)) {
                b = b.usrCoords;
            }
            if (Type.exists(c.usrCoords)) {
                c = c.usrCoords;
            }

            dx =  b[1] - a[1];

            if (Math.abs(dx) > Mat.eps) {
                r = (c[1] - a[1]) / dx;
            } else {
                r = (c[2] - a[2]) / (b[2] - a[2]);
            }
            return r;
        },

        /**
         * Sort vertices counter clockwise starting with the first point.
         *
         * @param {Array} p An array containing {@link JXG.Point}, {@link JXG.Coords}, and/or arrays.
         *
         * @returns {Array}
         */
        sortVertices: function (p) {
            var ll,
                ps = Expect.each(p, Expect.coordsArray),
                N = ps.length,
                lastPoint = null;

            // If the last point equals the first point, we take the last point out of the array.
            // It may be that the several points at the end of the array are equal to the first point.
            // The polygonal chain is been closed by JSXGraph, but this may also have been done by the user.
            // Therefore, we use a while lopp to pop the last points.
            while (ps[0][0] === ps[N - 1][0] && ps[0][1] === ps[N - 1][1] && ps[0][2] === ps[N - 1][2]) {
                lastPoint = ps.pop();
                N--;
            }
            // Find the point with the lowest y value
            // for (i = 1; i < N; i++) {
            //     if ((ps[i][2] < ps[0][2]) ||
            //         // if the current and the lowest point have the same y value, pick the one with
            //         // the lowest x value.
            //         (Math.abs(ps[i][2] - ps[0][2]) < Mat.eps && ps[i][1] < ps[0][1])) {
            //         console.log(i, 0);
            //         ps = Type.swap(ps, i, 0);
            //     }
            // }

            ll = ps[0];
            // Sort ps in increasing order of the angle between a point and the first point ll.
            // If a point is equal to the first point ll, the angle is defined to be -Infinity.
            // Otherwise, atan2 would return zero, which is a value which also attained by points
            // on the same horizontal line.
            ps.sort(function (a, b) {
                var rad1 = (a[2] === ll[2] && a[1] === ll[1]) ? -Infinity : Math.atan2(a[2] - ll[2], a[1] - ll[1]),
                    rad2 = (b[2] === ll[2] && b[1] === ll[1]) ? -Infinity : Math.atan2(b[2] - ll[2], b[1] - ll[1]);

                return rad1 - rad2;
            });

            // If the last point has been taken out of the array, we put it in again.
            if (lastPoint !== null) {
                ps.push(lastPoint);
            }

            return ps;
        },

        /**
         * Signed triangle area of the three points given.
         *
         * @param {JXG.Point|JXG.Coords|Array} p1
         * @param {JXG.Point|JXG.Coords|Array} p2
         * @param {JXG.Point|JXG.Coords|Array} p3
         *
         * @returns {Number}
         */
        signedTriangle: function (p1, p2, p3) {
            var A = Expect.coordsArray(p1),
                B = Expect.coordsArray(p2),
                C = Expect.coordsArray(p3);

            return 0.5 * ((B[1] - A[1]) * (C[2] - A[2]) - (B[2] - A[2]) * (C[1] - A[1]));
        },

        /**
         * Determine the signed area of a non-selfintersecting polygon.
         * Surveyor's Formula
         *
         * @param {Array} p An array containing {@link JXG.Point}, {@link JXG.Coords}, and/or arrays.
         * @param {Boolean} [sort=true]
         *
         * @returns {Number}
         */
        signedPolygon: function (p, sort) {
            var i, N,
                A = 0,
                ps = Expect.each(p, Expect.coordsArray);

            if (sort === undefined) {
                sort = true;
            }

            if (!sort) {
                ps = this.sortVertices(ps);
            } else {
                // Make sure the polygon is closed. If it is already closed this won't change the sum because the last
                // summand will be 0.
                ps.unshift(ps[ps.length - 1]);
            }

            N = ps.length;

            for (i = 1; i < N; i++) {
                A += ps[i - 1][1] * ps[i][2] - ps[i][1] * ps[i - 1][2];
            }

            return 0.5 * A;
        },

        /**
         * Calculate the complex hull of a point cloud.
         *
         * @param {Array} points An array containing {@link JXG.Point}, {@link JXG.Coords}, and/or arrays.
         *
         * @returns {Array}
         */
        GrahamScan: function (points) {
            var i,
                M = 1,
                ps = Expect.each(points, Expect.coordsArray),
                N = ps.length;

            ps = this.sortVertices(ps);
            N = ps.length;

            for (i = 2; i < N; i++) {
                while (this.signedTriangle(ps[M - 1], ps[M], ps[i]) <= 0) {
                    if (M > 1) {
                        M -= 1;
                    } else if (i === N - 1) {
                        break;
                    }
                    i += 1;
                }

                M += 1;
                ps = Type.swap(ps, M, i);
            }

            return ps.slice(0, M);
        },

        /**
         * A line can be a segment, a straight, or a ray. So it is not always delimited by point1 and point2
         * calcStraight determines the visual start point and end point of the line. A segment is only drawn
         * from start to end point, a straight line is drawn until it meets the boards boundaries.
         * @param {JXG.Line} el Reference to a line object, that needs calculation of start and end point.
         * @param {JXG.Coords} point1 Coordinates of the point where line drawing begins. This value is calculated and
         * set by this method.
         * @param {JXG.Coords} point2 Coordinates of the point where line drawing ends. This value is calculated and set
         * by this method.
         * @param {Number} margin Optional margin, to avoid the display of the small sides of lines.
         * @returns null
         * @see Line
         * @see JXG.Line
         */
        calcStraight: function (el, point1, point2, margin) {
            var takePoint1, takePoint2, intersection, intersect1, intersect2, straightFirst, straightLast,
                c, p1, p2;

            if (!Type.exists(margin)) {
                // Enlarge the drawable region slightly. This hides the small sides
                // of thick lines in most cases.
                margin = 10;
            }

            straightFirst = Type.evaluate(el.visProp.straightfirst);
            straightLast = Type.evaluate(el.visProp.straightlast);

            // If one of the point is an ideal point in homogeneous coordinates
            // drawing of line segments or rays are not possible.
            if (Math.abs(point1.scrCoords[0]) < Mat.eps) {
                straightFirst = true;
            }
            if (Math.abs(point2.scrCoords[0]) < Mat.eps) {
                straightLast = true;
            }

            // Do nothing in case of line segments (inside or outside of the board)
            if (!straightFirst && !straightLast) {
                return;
            }

            // Compute the stdform of the line in screen coordinates.
            c = [];
            c[0] = el.stdform[0] -
                el.stdform[1] * el.board.origin.scrCoords[1] / el.board.unitX +
                el.stdform[2] * el.board.origin.scrCoords[2] / el.board.unitY;
            c[1] =  el.stdform[1] / el.board.unitX;
            c[2] = -el.stdform[2] / el.board.unitY;

            // p1=p2
            if (isNaN(c[0] + c[1] + c[2])) {
                return;
            }

            takePoint1 = false;
            takePoint2 = false;

            // Line starts at point1 and point1 is inside the board
            takePoint1 = !straightFirst &&
                Math.abs(point1.usrCoords[0]) >= Mat.eps &&
                point1.scrCoords[1] >= 0.0 && point1.scrCoords[1] <= el.board.canvasWidth &&
                point1.scrCoords[2] >= 0.0 && point1.scrCoords[2] <= el.board.canvasHeight;

            // Line ends at point2 and point2 is inside the board
            takePoint2 = !straightLast &&
                Math.abs(point2.usrCoords[0]) >= Mat.eps &&
                point2.scrCoords[1] >= 0.0 && point2.scrCoords[1] <= el.board.canvasWidth &&
                point2.scrCoords[2] >= 0.0 && point2.scrCoords[2] <= el.board.canvasHeight;

            // Intersect the line with the four borders of the board.
            intersection = this.meetLineBoard(c, el.board, margin);
            intersect1 = intersection[0];
            intersect2 = intersection[1];

            /**
             * At this point we have four points:
             * point1 and point2 are the first and the second defining point on the line,
             * intersect1, intersect2 are the intersections of the line with border around the board.
             */

            /*
             * Here we handle rays where both defining points are outside of the board.
             */
            // If both points are outside and the complete ray is outside we do nothing
            if (!takePoint1 && !takePoint2) {
                // Ray starting at point 1
                if (!straightFirst && straightLast &&
                        !this.isSameDirection(point1, point2, intersect1) && !this.isSameDirection(point1, point2, intersect2)) {
                    return;
                }

                // Ray starting at point 2
                if (straightFirst && !straightLast &&
                        !this.isSameDirection(point2, point1, intersect1) && !this.isSameDirection(point2, point1, intersect2)) {
                    return;
                }
            }

            /*
             * If at least one of the defining points is outside of the board
             * we take intersect1 or intersect2 as one of the end points
             * The order is also important for arrows of axes
             */
            if (!takePoint1) {
                if (!takePoint2) {
                    // Two border intersection points are used
                    if (this.isSameDir(point1, point2, intersect1, intersect2)) {
                        p1 = intersect1;
                        p2 = intersect2;
                    } else {
                        p2 = intersect1;
                        p1 = intersect2;
                    }
                } else {
                    // One border intersection points is used
                    if (this.isSameDir(point1, point2, intersect1, intersect2)) {
                        p1 = intersect1;
                    } else {
                        p1 = intersect2;
                    }
                }
            } else {
                if (!takePoint2) {
                    // One border intersection points is used
                    if (this.isSameDir(point1, point2, intersect1, intersect2)) {
                        p2 = intersect2;
                    } else {
                        p2 = intersect1;
                    }
                }
            }

            if (p1) {
                //point1.setCoordinates(Const.COORDS_BY_USER, p1.usrCoords.slice(1));
                point1.setCoordinates(Const.COORDS_BY_USER, p1.usrCoords);
            }

            if (p2) {
                //point2.setCoordinates(Const.COORDS_BY_USER, p2.usrCoords.slice(1));
                point2.setCoordinates(Const.COORDS_BY_USER, p2.usrCoords);
            }
        },

        /**
         * A line can be a segment, a straight, or a ray. so it is not always delimited by point1 and point2.
         *
         * This method adjusts the line's delimiting points taking into account its nature, the viewport defined
         * by the board.
         *
         * A segment is delimited by start and end point, a straight line or ray is delimited until it meets the
         * boards boundaries. However, if the line has infinite ticks, it will be delimited by the projection of
         * the boards vertices onto itself.
         *
         * @param {JXG.Line} el Reference to a line object, that needs calculation of start and end point.
         * @param {JXG.Coords} point1 Coordinates of the point where line drawing begins. This value is calculated and
         * set by this method.
         * @param {JXG.Coords} point2 Coordinates of the point where line drawing ends. This value is calculated and set
         * by this method.
         * @see Line
         * @see JXG.Line
         */
        calcLineDelimitingPoints: function (el, point1, point2) {
            var distP1P2, boundingBox, lineSlope,
                intersect1, intersect2, straightFirst, straightLast,
                c, p1, p2,
                takePoint1 = false,
                takePoint2 = false;

            straightFirst = Type.evaluate(el.visProp.straightfirst);
            straightLast = Type.evaluate(el.visProp.straightlast);

            // If one of the point is an ideal point in homogeneous coordinates
            // drawing of line segments or rays are not possible.
            if (Math.abs(point1.scrCoords[0]) < Mat.eps) {
                straightFirst = true;
            }
            if (Math.abs(point2.scrCoords[0]) < Mat.eps) {
                straightLast = true;
            }

            // Compute the stdform of the line in screen coordinates.
            c = [];
            c[0] = el.stdform[0] -
                el.stdform[1] * el.board.origin.scrCoords[1] / el.board.unitX +
                el.stdform[2] * el.board.origin.scrCoords[2] / el.board.unitY;
            c[1] =  el.stdform[1] / el.board.unitX;
            c[2] = -el.stdform[2] / el.board.unitY;

            // p1=p2
            if (isNaN(c[0] + c[1] + c[2])) {
                return;
            }

            takePoint1 = !straightFirst;
            takePoint2 = !straightLast;
            // Intersect the board vertices on the line to establish the available visual space for the infinite ticks
            // Based on the slope of the line we can optimise and only project the two outer vertices

            // boundingBox = [x1, y1, x2, y2] upper left, lower right vertices
            boundingBox = el.board.getBoundingBox();
            lineSlope = el.getSlope();
            if (lineSlope >= 0) {
                // project vertices (x2,y1) (x1, y2)
                intersect1 = this.projectPointToLine({ coords: { usrCoords: [1, boundingBox[2], boundingBox[1]] } }, el, el.board);
                intersect2 = this.projectPointToLine({ coords: { usrCoords: [1, boundingBox[0], boundingBox[3]] } }, el, el.board);
            } else {
                // project vertices (x1, y1) (x2, y2)
                intersect1 = this.projectPointToLine({ coords: { usrCoords: [1, boundingBox[0], boundingBox[1]] } }, el, el.board);
                intersect2 = this.projectPointToLine({ coords: { usrCoords: [1, boundingBox[2], boundingBox[3]] } }, el, el.board);
            }

            /**
             * we have four points:
             * point1 and point2 are the first and the second defining point on the line,
             * intersect1, intersect2 are the intersections of the line with border around the board.
             */

            /*
             * Here we handle rays/segments where both defining points are outside of the board.
             */
            if (!takePoint1 && !takePoint2) {
                // Segment, if segment does not cross the board, do nothing
                if (!straightFirst && !straightLast) {
                    distP1P2 = point1.distance(Const.COORDS_BY_USER, point2);
                    // if  intersect1 not between point1 and point2
                    if (Math.abs(point1.distance(Const.COORDS_BY_USER, intersect1) +
                            intersect1.distance(Const.COORDS_BY_USER, point2) - distP1P2) > Mat.eps) {
                        return;
                    }
                    // if insersect2 not between point1 and point2
                    if (Math.abs(point1.distance(Const.COORDS_BY_USER, intersect2) +
                            intersect2.distance(Const.COORDS_BY_USER, point2) - distP1P2) > Mat.eps) {
                        return;
                    }
                }

                // If both points are outside and the complete ray is outside we do nothing
                // Ray starting at point 1
                if (!straightFirst && straightLast &&
                        !this.isSameDirection(point1, point2, intersect1) && !this.isSameDirection(point1, point2, intersect2)) {
                    return;
                }

                // Ray starting at point 2
                if (straightFirst && !straightLast &&
                        !this.isSameDirection(point2, point1, intersect1) && !this.isSameDirection(point2, point1, intersect2)) {
                    return;
                }
            }

            /*
             * If at least one of the defining points is outside of the board
             * we take intersect1 or intersect2 as one of the end points
             * The order is also important for arrows of axes
             */
            if (!takePoint1) {
                if (!takePoint2) {
                    // Two border intersection points are used
                    if (this.isSameDir(point1, point2, intersect1, intersect2)) {
                        p1 = intersect1;
                        p2 = intersect2;
                    } else {
                        p2 = intersect1;
                        p1 = intersect2;
                    }
                } else {
                    // One border intersection points is used
                    if (this.isSameDir(point1, point2, intersect1, intersect2)) {
                        p1 = intersect1;
                    } else {
                        p1 = intersect2;
                    }
                }
            } else {
                if (!takePoint2) {
                    // One border intersection points is used
                    if (this.isSameDir(point1, point2, intersect1, intersect2)) {
                        p2 = intersect2;
                    } else {
                        p2 = intersect1;
                    }
                }
            }

            if (p1) {
                //point1.setCoordinates(Const.COORDS_BY_USER, p1.usrCoords.slice(1));
                point1.setCoordinates(Const.COORDS_BY_USER, p1.usrCoords);
            }

            if (p2) {
                //point2.setCoordinates(Const.COORDS_BY_USER, p2.usrCoords.slice(1));
                point2.setCoordinates(Const.COORDS_BY_USER, p2.usrCoords);
            }
        },

        /**
         * Calculates the visProp.position corresponding to a given angle.
         * @param {number} angle angle in radians. Must be in range (-2pi,2pi).
         */
        calcLabelQuadrant: function(angle) {
            var q;
            if (angle < 0) {
                angle += 2*Math.PI;
            }
            q = Math.floor((angle+Math.PI/8)/(Math.PI/4))%8;
            return ['rt','urt','top','ulft','lft','llft','lrt'][q];
        },

        /**
         * The vectors <tt>p2-p1</tt> and <tt>i2-i1</tt> are supposed to be collinear. If their cosine is positive
         * they point into the same direction otherwise they point in opposite direction.
         * @param {JXG.Coords} p1
         * @param {JXG.Coords} p2
         * @param {JXG.Coords} i1
         * @param {JXG.Coords} i2
         * @returns {Boolean} True, if <tt>p2-p1</tt> and <tt>i2-i1</tt> point into the same direction
         */
        isSameDir: function (p1, p2, i1, i2) {
            var dpx = p2.usrCoords[1] - p1.usrCoords[1],
                dpy = p2.usrCoords[2] - p1.usrCoords[2],
                dix = i2.usrCoords[1] - i1.usrCoords[1],
                diy = i2.usrCoords[2] - i1.usrCoords[2];

            if (Math.abs(p2.usrCoords[0]) < Mat.eps) {
                dpx = p2.usrCoords[1];
                dpy = p2.usrCoords[2];
            }

            if (Math.abs(p1.usrCoords[0]) < Mat.eps) {
                dpx = -p1.usrCoords[1];
                dpy = -p1.usrCoords[2];
            }

            return dpx * dix + dpy * diy >= 0;
        },

        /**
         * If you're looking from point "start" towards point "s" and you can see the point "p", return true.
         * Otherwise return false.
         * @param {JXG.Coords} start The point you're standing on.
         * @param {JXG.Coords} p The point in which direction you're looking.
         * @param {JXG.Coords} s The point that should be visible.
         * @returns {Boolean} True, if from start the point p is in the same direction as s is, that means s-start = k*(p-start) with k>=0.
         */
        isSameDirection: function (start, p, s) {
            var dx, dy, sx, sy, r = false;

            dx = p.usrCoords[1] - start.usrCoords[1];
            dy = p.usrCoords[2] - start.usrCoords[2];

            sx = s.usrCoords[1] - start.usrCoords[1];
            sy = s.usrCoords[2] - start.usrCoords[2];

            if (Math.abs(dx) < Mat.eps) {
                dx = 0;
            }

            if (Math.abs(dy) < Mat.eps) {
                dy = 0;
            }

            if (Math.abs(sx) < Mat.eps) {
                sx = 0;
            }

            if (Math.abs(sy) < Mat.eps) {
                sy = 0;
            }

            if (dx >= 0 && sx >= 0) {
                r = (dy >= 0 && sy >= 0) || (dy <= 0 && sy <= 0);
            } else if (dx <= 0 && sx <= 0) {
                r = (dy >= 0 && sy >= 0) || (dy <= 0 && sy <= 0);
            }

            return r;
        },

        /****************************************/
        /****          INTERSECTIONS         ****/
        /****************************************/

        /**
         * Generate the function which computes the coordinates of the intersection point.
         * Primarily used in {@link JXG.Point#createIntersectionPoint}.
         * @param {JXG.Board} board object
         * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_Number} el1,el2,i The result will be a intersection point on el1 and el2.
         * i determines the intersection point if two points are available: <ul>
         *   <li>i==0: use the positive square root,</li>
         *   <li>i==1: use the negative square root.</li></ul>
         * See further {@link JXG.Point#createIntersectionPoint}.
         * @param {Boolean} alwaysintersect. Flag that determines if segments and arc can have an outer intersection point
         * on their defining line or circle.
         * @returns {Function} Function returning a {@link JXG.Coords} object that determines
         * the intersection point.
         */
        intersectionFunction: function (board, el1, el2, i, j, alwaysintersect) {
            var func, that = this,
                el1_isArcType = false,
                el2_isArcType = false;

            el1_isArcType = (el1.elementClass === Const.OBJECT_CLASS_CURVE &&
                (el1.type === Const.OBJECT_TYPE_ARC || el1.type === Const.OBJECT_TYPE_SECTOR)
                ) ? true : false;
            el2_isArcType = (el2.elementClass === Const.OBJECT_CLASS_CURVE &&
                (el2.type === Const.OBJECT_TYPE_ARC || el2.type === Const.OBJECT_TYPE_SECTOR)
                ) ? true : false;

            if ((el1.elementClass === Const.OBJECT_CLASS_CURVE || el2.elementClass === Const.OBJECT_CLASS_CURVE) &&
                (el1.elementClass === Const.OBJECT_CLASS_CURVE || el1.elementClass === Const.OBJECT_CLASS_CIRCLE) &&
                (el2.elementClass === Const.OBJECT_CLASS_CURVE || el2.elementClass === Const.OBJECT_CLASS_CIRCLE) /*&&
                !(el1_isArcType && el2_isArcType)*/ ) {
                // curve - curve
                // with the exception that both elements are arc types
                /** @ignore */
                func = function () {
                    return that.meetCurveCurve(el1, el2, i, j, el1.board);
                };

            } else if ((
                        el1.elementClass === Const.OBJECT_CLASS_CURVE &&
                        !el1_isArcType &&
                        el2.elementClass === Const.OBJECT_CLASS_LINE
                       ) ||
                       (
                        el2.elementClass === Const.OBJECT_CLASS_CURVE &&
                        !el2_isArcType &&
                        el1.elementClass === Const.OBJECT_CLASS_LINE
                       )
                    ) {
                // curve - line (this includes intersections between conic sections and lines)
                // with the exception that the curve is of arc type
                /** @ignore */
                func = function () {
                    return that.meetCurveLine(el1, el2, i, el1.board, alwaysintersect);
                };

            } else if (el1.elementClass === Const.OBJECT_CLASS_LINE && el2.elementClass === Const.OBJECT_CLASS_LINE) {
                // line - line, lines may also be segments.
                /** @ignore */
                func = function () {
                    var res, c,
                        first1 = Type.evaluate(el1.visProp.straightfirst),
                        last1 = Type.evaluate(el1.visProp.straightlast),
                        first2 = Type.evaluate(el2.visProp.straightfirst),
                        last2 = Type.evaluate(el2.visProp.straightlast);

                    /**
                     * If one of the lines is a segment or ray and
                     * the intersection point should disappear if outside
                     * of the segment or ray we call
                     * meetSegmentSegment
                     */
                    if (!Type.evaluate(alwaysintersect) && (!first1 || !last1 || !first2 || !last2)) {
                        res = that.meetSegmentSegment(
                            el1.point1.coords.usrCoords,
                            el1.point2.coords.usrCoords,
                            el2.point1.coords.usrCoords,
                            el2.point2.coords.usrCoords
                        );

                        if ((!first1 && res[1] < 0) || (!last1 && res[1] > 1) ||
                                (!first2 && res[2] < 0) || (!last2 && res[2] > 1)) {
                            // Non-existent
                            c = [0, NaN, NaN];
                        } else {
                            c = res[0];
                        }

                        return (new Coords(Const.COORDS_BY_USER, c, el1.board));
                    }

                    return that.meet(el1.stdform, el2.stdform, i, el1.board);
                };
            } else {
                // All other combinations of circles and lines,
                // Arc types are treated as circles.
                /** @ignore */
                func = function () {
                    var res = that.meet(el1.stdform, el2.stdform, i, el1.board),
                        has = true,
                        first, last, r, dx;

                    if (alwaysintersect) {
                        return res;
                    }
                    if (el1.elementClass === Const.OBJECT_CLASS_LINE) {
                        first = Type.evaluate(el1.visProp.straightfirst);
                        last  = Type.evaluate(el1.visProp.straightlast);
                        if (!first || !last) {
                            r = that.affineRatio(el1.point1.coords, el1.point2.coords, res);
                            if ( (!last && r > 1 + Mat.eps) || (!first && r < 0 - Mat.eps) ) {
                                return (new Coords(JXG.COORDS_BY_USER, [0, NaN, NaN], el1.board));
                            }
                        }
                    }
                    if (el2.elementClass === Const.OBJECT_CLASS_LINE) {
                        first = Type.evaluate(el2.visProp.straightfirst);
                        last  = Type.evaluate(el2.visProp.straightlast);
                        if (!first || !last) {
                            r = that.affineRatio(el2.point1.coords, el2.point2.coords, res);
                            if ( (!last && r > 1 + Mat.eps) || (!first && r < 0 - Mat.eps) ) {
                                return (new Coords(JXG.COORDS_BY_USER, [0, NaN, NaN], el1.board));
                            }
                        }
                    }
                    if (el1_isArcType) {
                        has = that.coordsOnArc(el1, res);
                        if (has && el2_isArcType) {
                            has = that.coordsOnArc(el2, res);
                        }
                        if (!has) {
                            return (new Coords(JXG.COORDS_BY_USER, [0, NaN, NaN], el1.board));
                        }
                    }
                    return res;
                };
            }

            return func;
        },

        /**
         * Returns true if the coordinates are on the arc element,
         * false otherwise. Usually, coords is an intersection
         * on the circle line. Now it is decided if coords are on the
         * circle restricted to the arc line.
         * @param  {Arc} arc arc or sector element
         * @param  {JXG.Coords} coords Coords object of an intersection
         * @returns {Boolean}
         * @private
         */
        coordsOnArc: function(arc, coords) {
            var angle = this.rad(arc.radiuspoint, arc.center, coords.usrCoords.slice(1)),
                alpha = 0.0,
                beta = this.rad(arc.radiuspoint, arc.center, arc.anglepoint),
                ev_s = Type.evaluate(arc.visProp.selection);

            if ((ev_s === 'minor' && beta > Math.PI) ||
                (ev_s === 'major' && beta < Math.PI)) {
                alpha = beta;
                beta = 2 * Math.PI;
            }
            if (angle < alpha || angle > beta) {
                return false;
            }
            return true;
        },

        /**
         * Computes the intersection of a pair of lines, circles or both.
         * It uses the internal data array stdform of these elements.
         * @param {Array} el1 stdform of the first element (line or circle)
         * @param {Array} el2 stdform of the second element (line or circle)
         * @param {Number} i Index of the intersection point that should be returned.
         * @param board Reference to the board.
         * @returns {JXG.Coords} Coordinates of one of the possible two or more intersection points.
         * Which point will be returned is determined by i.
         */
        meet: function (el1, el2, i, board) {
            var result,
                eps = Mat.eps;

            // line line
            if (Math.abs(el1[3]) < eps && Math.abs(el2[3]) < eps) {
                result = this.meetLineLine(el1, el2, i, board);
            // circle line
            } else if (Math.abs(el1[3]) >= eps && Math.abs(el2[3]) < eps) {
                result = this.meetLineCircle(el2, el1, i, board);
            // line circle
            } else if (Math.abs(el1[3]) < eps && Math.abs(el2[3]) >= eps) {
                result = this.meetLineCircle(el1, el2, i, board);
            // circle circle
            } else {
                result = this.meetCircleCircle(el1, el2, i, board);
            }

            return result;
        },

        /**
         * Intersection of the line with the board
         * @param  {Array}     line   stdform of the line in screen coordinates
         * @param  {JXG.Board} board  reference to a board.
         * @param  {Number}    margin optional margin, to avoid the display of the small sides of lines.
         * @returns {Array}            [intersection coords 1, intersection coords 2]
         */
        meetLineBoard: function (line, board, margin) {
             // Intersect the line with the four borders of the board.
            var s = [], intersect1, intersect2, i, j;

            if (!Type.exists(margin)) {
                margin = 0;
            }

            // top
            s[0] = Mat.crossProduct(line, [margin, 0, 1]);
            // left
            s[1] = Mat.crossProduct(line, [margin, 1, 0]);
            // bottom
            s[2] = Mat.crossProduct(line, [-margin - board.canvasHeight, 0, 1]);
            // right
            s[3] = Mat.crossProduct(line, [-margin - board.canvasWidth, 1, 0]);

            // Normalize the intersections
            for (i = 0; i < 4; i++) {
                if (Math.abs(s[i][0]) > Mat.eps) {
                    for (j = 2; j > 0; j--) {
                        s[i][j] /= s[i][0];
                    }
                    s[i][0] = 1.0;
                }
            }

            // line is parallel to "left", take "top" and "bottom"
            if (Math.abs(s[1][0]) < Mat.eps) {
                intersect1 = s[0];                          // top
                intersect2 = s[2];                          // bottom
            // line is parallel to "top", take "left" and "right"
            } else if (Math.abs(s[0][0]) < Mat.eps) {
                intersect1 = s[1];                          // left
                intersect2 = s[3];                          // right
            // left intersection out of board (above)
            } else if (s[1][2] < 0) {
                intersect1 = s[0];                          // top

                // right intersection out of board (below)
                if (s[3][2] > board.canvasHeight) {
                    intersect2 = s[2];                      // bottom
                } else {
                    intersect2 = s[3];                      // right
                }
            // left intersection out of board (below)
            } else if (s[1][2] > board.canvasHeight) {
                intersect1 = s[2];                          // bottom

                // right intersection out of board (above)
                if (s[3][2] < 0) {
                    intersect2 = s[0];                      // top
                } else {
                    intersect2 = s[3];                      // right
                }
            } else {
                intersect1 = s[1];                          // left

                // right intersection out of board (above)
                if (s[3][2] < 0) {
                    intersect2 = s[0];                      // top
                // right intersection out of board (below)
                } else if (s[3][2] > board.canvasHeight) {
                    intersect2 = s[2];                      // bottom
                } else {
                    intersect2 = s[3];                      // right
                }
            }

            intersect1 = new Coords(Const.COORDS_BY_SCREEN, intersect1.slice(1), board);
            intersect2 = new Coords(Const.COORDS_BY_SCREEN, intersect2.slice(1), board);
            return [intersect1, intersect2];
        },

        /**
         * Intersection of two lines.
         * @param {Array} l1 stdform of the first line
         * @param {Array} l2 stdform of the second line
         * @param {number} i unused
         * @param {JXG.Board} board Reference to the board.
         * @returns {JXG.Coords} Coordinates of the intersection point.
         */
        meetLineLine: function (l1, l2, i, board) {
            /*
            var s = Mat.crossProduct(l1, l2);

            if (Math.abs(s[0]) > Mat.eps) {
                s[1] /= s[0];
                s[2] /= s[0];
                s[0] = 1.0;
            }
            */
            var s = isNaN(l1[5] + l2[5]) ? [0, 0, 0] : Mat.crossProduct(l1, l2);
            return new Coords(Const.COORDS_BY_USER, s, board);
        },

        /**
         * Intersection of line and circle.
         * @param {Array} lin stdform of the line
         * @param {Array} circ stdform of the circle
         * @param {number} i number of the returned intersection point.
         *   i==0: use the positive square root,
         *   i==1: use the negative square root.
         * @param {JXG.Board} board Reference to a board.
         * @returns {JXG.Coords} Coordinates of the intersection point
         */
        meetLineCircle: function (lin, circ, i, board) {
            var a, b, c, d, n,
                A, B, C, k, t;

            // Radius is zero, return center of circle
            if (circ[4] < Mat.eps) {
                if (Math.abs(Mat.innerProduct([1, circ[6], circ[7]], lin, 3)) < Mat.eps) {
                    return new Coords(Const.COORDS_BY_USER, circ.slice(6, 8), board);
                }

                return new Coords(Const.COORDS_BY_USER, [NaN, NaN], board);
            }
            c = circ[0];
            b = circ.slice(1, 3);
            a = circ[3];
            d = lin[0];
            n = lin.slice(1, 3);

            // Line is assumed to be normalized. Therefore, nn==1 and we can skip some operations:
            /*
             var nn = n[0]*n[0]+n[1]*n[1];
             A = a*nn;
             B = (b[0]*n[1]-b[1]*n[0])*nn;
             C = a*d*d - (b[0]*n[0]+b[1]*n[1])*d + c*nn;
             */
            A = a;
            B = (b[0] * n[1] - b[1] * n[0]);
            C = a * d * d - (b[0] * n[0] + b[1] * n[1]) * d + c;

            k = B * B - 4 * A * C;
            if (k > -Mat.eps * Mat.eps) {
                k = Math.sqrt(Math.abs(k));
                t = [(-B + k) / (2 * A), (-B - k) / (2 * A)];

                return ((i === 0) ?
                        new Coords(Const.COORDS_BY_USER, [-t[0] * (-n[1]) - d * n[0], -t[0] * n[0] - d * n[1]], board) :
                        new Coords(Const.COORDS_BY_USER, [-t[1] * (-n[1]) - d * n[0], -t[1] * n[0] - d * n[1]], board)
                    );
            }

            return new Coords(Const.COORDS_BY_USER, [0, 0, 0], board);
        },

        /**
         * Intersection of two circles.
         * @param {Array} circ1 stdform of the first circle
         * @param {Array} circ2 stdform of the second circle
         * @param {number} i number of the returned intersection point.
         *   i==0: use the positive square root,
         *   i==1: use the negative square root.
         * @param {JXG.Board} board Reference to the board.
         * @returns {JXG.Coords} Coordinates of the intersection point
         */
        meetCircleCircle: function (circ1, circ2, i, board) {
            var radicalAxis;

            // Radius is zero, return center of circle, if on other circle
            if (circ1[4] < Mat.eps) {
                if (Math.abs(this.distance(circ1.slice(6, 2), circ2.slice(6, 8)) - circ2[4]) < Mat.eps) {
                    return new Coords(Const.COORDS_BY_USER, circ1.slice(6, 8), board);
                }

                return new Coords(Const.COORDS_BY_USER, [0, 0, 0], board);
            }

            // Radius is zero, return center of circle, if on other circle
            if (circ2[4] < Mat.eps) {
                if (Math.abs(this.distance(circ2.slice(6, 2), circ1.slice(6, 8)) - circ1[4]) < Mat.eps) {
                    return new Coords(Const.COORDS_BY_USER, circ2.slice(6, 8), board);
                }

                return new Coords(Const.COORDS_BY_USER, [0, 0, 0], board);
            }

            radicalAxis = [circ2[3] * circ1[0] - circ1[3] * circ2[0],
                circ2[3] * circ1[1] - circ1[3] * circ2[1],
                circ2[3] * circ1[2] - circ1[3] * circ2[2],
                0, 1, Infinity, Infinity, Infinity];
            radicalAxis = Mat.normalize(radicalAxis);

            return this.meetLineCircle(radicalAxis, circ1, i, board);
        },

        /**
         * Compute an intersection of the curves c1 and c2.
         * We want to find values t1, t2 such that
         * c1(t1) = c2(t2), i.e. (c1_x(t1)-c2_x(t2),c1_y(t1)-c2_y(t2)) = (0,0).
         *
         * Methods: segment-wise intersections (default) or generalized Newton method.
         * @param {JXG.Curve} c1 Curve, Line or Circle
         * @param {JXG.Curve} c2 Curve, Line or Circle
         * @param {Number} nr the nr-th intersection point will be returned.
         * @param {Number} t2ini not longer used.
         * @param {JXG.Board} [board=c1.board] Reference to a board object.
         * @param {String} [method='segment'] Intersection method, possible values are 'newton' and 'segment'.
         * @returns {JXG.Coords} intersection point
         */
        meetCurveCurve: function (c1, c2, nr, t2ini, board, method) {
            var co;

            if (Type.exists(method) && method === 'newton') {
                co = Numerics.generalizedNewton(c1, c2, nr, t2ini);
            } else {
                if (c1.bezierDegree === 3 || c2.bezierDegree === 3) {
                    co = this.meetBezierCurveRedBlueSegments(c1, c2, nr);
                } else {
                    co = this.meetCurveRedBlueSegments(c1, c2, nr);
                }
            }

            return (new Coords(Const.COORDS_BY_USER, co, board));
        },

        /**
         * Intersection of curve with line,
         * Order of input does not matter for el1 and el2.
         * From version 0.99.7 on this method calls
         * {@link JXG.Math.Geometry.meetCurveLineDiscrete}.
         * If higher precision is needed, {@link JXG.Math.Geometry.meetCurveLineContinuous}
         * has to be used.
         *
         * @param {JXG.Curve,JXG.Line} el1 Curve or Line
         * @param {JXG.Curve,JXG.Line} el2 Curve or Line
         * @param {Number} nr the nr-th intersection point will be returned.
         * @param {JXG.Board} [board=el1.board] Reference to a board object.
         * @param {Boolean} alwaysIntersect If false just the segment between the two defining points are tested for intersection
         * @returns {JXG.Coords} Intersection point. In case no intersection point is detected,
         * the ideal point [0,1,0] is returned.
         */
        meetCurveLine: function (el1, el2, nr, board, alwaysIntersect) {
            var v = [0, NaN, NaN], cu, li;

            if (!Type.exists(board)) {
                board = el1.board;
            }

            if (el1.elementClass === Const.OBJECT_CLASS_CURVE) {
                cu = el1;
                li = el2;
            } else {
                cu = el2;
                li = el1;
            }

            v = this.meetCurveLineDiscrete(cu, li, nr, board, !alwaysIntersect);

            return v;
        },

        /**
         * Intersection of line and curve, continuous case.
         * Finds the nr-the intersection point
         * Uses {@link JXG.Math.Geometry.meetCurveLineDiscrete} as a first approximation.
         * A more exact solution is then found with {@link JXG.Math.Numerics.root}.
         *
         * @param {JXG.Curve} cu Curve
         * @param {JXG.Line} li Line
         * @param {Number} nr Will return the nr-th intersection point.
         * @param {JXG.Board} board
         * @returns {JXG.Coords} Coords object containing the intersection.
         */
        meetCurveLineContinuous: function (cu, li, nr, board, testSegment) {
            var t, func0, func1, v, x, y, z,
                eps = Mat.eps,
                epsLow = Mat.eps,
                steps, delta, tnew, i,
                tmin, fmin, ft;

            v = this.meetCurveLineDiscrete(cu, li, nr, board, testSegment);
            x = v.usrCoords[1];
            y = v.usrCoords[2];

            func0 = function (t) {
                var c1, c2;

                if (t > cu.maxX() || t < cu.minX()) {
                    return Infinity;
                }
                c1 = x - cu.X(t);
                c2 = y - cu.Y(t);
                return c1 * c1 + c2 * c2;
            };

            func1 = function (t) {
                var v = li.stdform[0] + li.stdform[1] * cu.X(t) + li.stdform[2] * cu.Y(t);
                return v * v;
            };

            // Find t
            steps = 50;
            delta = (cu.maxX() - cu.minX()) / steps;
            tnew = cu.minX();

            fmin = 0.0001; //eps;
            tmin = NaN;
            for (i = 0; i < steps; i++) {
                t = Numerics.root(func0, [Math.max(tnew, cu.minX()), Math.min(tnew + delta, cu.maxX())]);
                ft = Math.abs(func0(t));
                if (ft <= fmin) {
                    fmin = ft;
                    tmin = t;
                    if (fmin < eps) {
                        break;
                    }
                }

                tnew += delta;
            }
            t = tmin;
            // Compute "exact" t
            t = Numerics.root(func1, [Math.max(t - delta, cu.minX()), Math.min(t + delta, cu.maxX())]);

            ft = func1(t);
            // Is the point on the line?
            if (isNaN(ft) || Math.abs(ft) > epsLow) {
                z = 0.0; //NaN;
            } else {
                z = 1.0;
            }

            return (new Coords(Const.COORDS_BY_USER, [z, cu.X(t), cu.Y(t)], board));
        },


        /**
         * Intersection of line and curve, discrete case.
         * Segments are treated as lines.
         * Finding the nr-th intersection point should work for all nr.
         * @param {JXG.Curve} cu
         * @param {JXG.Line} li
         * @param {Number} nr
         * @param {JXG.Board} board
         * @param {Boolean} testSegment Test if intersection has to be inside of the segment or somewhere on the
         * line defined by the segment
         *
         * @returns {JXG.Coords} Intersection point. In case no intersection point is detected,
         * the ideal point [0,1,0] is returned.
         */
        meetCurveLineDiscrete: function (cu, li, nr, board, testSegment) {
            var i, j,
                p1, p2, p, q,
                lip1 = li.point1.coords.usrCoords,
                lip2 = li.point2.coords.usrCoords,
                d, res,
                cnt = 0,
                len = cu.numberPoints,
                ev_sf = Type.evaluate(li.visProp.straightfirst),
                ev_sl = Type.evaluate(li.visProp.straightlast);

            // In case, no intersection will be found we will take this
            q = new Coords(Const.COORDS_BY_USER, [0, NaN, NaN], board);

            if (lip1[0] === 0.0) {
                lip1 = [1, lip2[1] + li.stdform[2], lip2[2] - li.stdform[1]];
            } else if (lip2[0] === 0.0) {
                lip2 = [1, lip1[1] + li.stdform[2], lip1[2] - li.stdform[1]];
            }

            p2 = cu.points[0].usrCoords;
            for (i = 1; i < len; i += cu.bezierDegree) {
                p1 = p2.slice(0);
                p2 = cu.points[i].usrCoords;
                d = this.distance(p1, p2);

                // The defining points are not identical
                if (d > Mat.eps) {
                    if (cu.bezierDegree === 3) {
                        res = this.meetBeziersegmentBeziersegment([
                            cu.points[i - 1].usrCoords.slice(1),
                            cu.points[i].usrCoords.slice(1),
                            cu.points[i + 1].usrCoords.slice(1),
                            cu.points[i + 2].usrCoords.slice(1)
                        ], [
                            lip1.slice(1),
                            lip2.slice(1)
                        ], testSegment);
                    } else {
                        res = [this.meetSegmentSegment(p1, p2, lip1, lip2)];
                    }

                    for (j = 0; j < res.length; j++) {
                        p = res[j];
                        if (0 <= p[1] && p[1] <= 1) {
                            if (cnt === nr) {
                                /**
                                * If the intersection point is not part of the segment,
                                * this intersection point is set to non-existent.
                                * This prevents jumping behavior of the intersection points.
                                * But it may be discussed if it is the desired behavior.
                                */
                                if (testSegment &&
                                        ((!ev_sf && p[2] < 0) || (!ev_sl && p[2] > 1))) {
                                    return q;  // break;
                                }

                                q = new Coords(Const.COORDS_BY_USER, p[0], board);
                                return q;      // break;
                            }
                            cnt += 1;
                        }
                    }
                }
            }

            return q;
        },

        /**
         * Find the n-th intersection point of two curves named red (first parameter) and blue (second parameter).
         * We go through each segment of the red curve and search if there is an intersection with a segemnt of the blue curve.
         * This double loop, i.e. the outer loop runs along the red curve and the inner loop runs along the blue curve, defines
         * the n-th intersection point. The segments are either line segments or Bezier curves of degree 3. This depends on
         * the property bezierDegree of the curves.
         * <p>
         * This method works also for transformed curves, since only the already
         * transformed points are used.
         *
         * @param {JXG.Curve} red
         * @param {JXG.Curve} blue
         * @param {Number} nr
         */
        meetCurveRedBlueSegments: function (red, blue, nr) {
            var i, j,
                red1, red2, blue1, blue2, m,
                minX, maxX,
                iFound = 0,
                lenBlue = blue.numberPoints, //points.length,
                lenRed = red.numberPoints; //points.length;

            if (lenBlue <= 1 || lenRed <= 1) {
                return [0, NaN, NaN];
            }

            for (i = 1; i < lenRed; i++) {
                red1 = red.points[i - 1].usrCoords;
                red2 = red.points[i].usrCoords;
                minX = Math.min(red1[1], red2[1]);
                maxX = Math.max(red1[1], red2[1]);

                blue2 = blue.points[0].usrCoords;
                for (j = 1; j < lenBlue; j++) {
                    blue1 = blue2;
                    blue2 = blue.points[j].usrCoords;

                    if (Math.min(blue1[1], blue2[1]) < maxX && Math.max(blue1[1], blue2[1]) > minX) {
                        m = this.meetSegmentSegment(red1, red2, blue1, blue2);
                        if (m[1] >= 0.0 && m[2] >= 0.0 &&
                                // The two segments meet in the interior or at the start points
                                ((m[1] < 1.0 && m[2] < 1.0) ||
                                // One of the curve is intersected in the very last point
                                (i === lenRed - 1 && m[1] === 1.0) ||
                                (j === lenBlue - 1 && m[2] === 1.0))) {
                            if (iFound === nr) {
                                return m[0];
                            }

                            iFound++;
                        }
                    }
                }
            }

            return [0, NaN, NaN];
        },

        /**
         * (Virtual) Intersection of two segments.
         * @param {Array} p1 First point of segment 1 using normalized homogeneous coordinates [1,x,y]
         * @param {Array} p2 Second point or direction of segment 1 using normalized homogeneous coordinates [1,x,y] or point at infinity [0,x,y], respectively
         * @param {Array} q1 First point of segment 2 using normalized homogeneous coordinates [1,x,y]
         * @param {Array} q2 Second point or direction of segment 2 using normalized homogeneous coordinates [1,x,y] or point at infinity [0,x,y], respectively
         * @returns {Array} [Intersection point, t, u] The first entry contains the homogeneous coordinates
         * of the intersection point. The second and third entry give the position of the intersection with respect
         * to the definiting parameters. For example, the second entry t is defined by: intersection point = p1 + t * deltaP, where
         * deltaP = (p2 - p1) when both parameters are coordinates, and deltaP = p2 if p2 is a point at infinity.
         * If the two segments are collinear, [[0,0,0], Infinity, Infinity] is returned.
         **/
        meetSegmentSegment: function (p1, p2, q1, q2) {
            var t, u, i, d,
                li1 = Mat.crossProduct(p1, p2),
                li2 = Mat.crossProduct(q1, q2),
                c = Mat.crossProduct(li1, li2);

            if (Math.abs(c[0]) < Mat.eps) {
                return [c, Infinity, Infinity];
            }

            // Normalize the intersection coordinates
            c[1] /= c[0];
            c[2] /= c[0];
            c[0] /= c[0];

            // Now compute in principle:
            //    t = dist(c - p1) / dist(p2 - p1) and
            //    u = dist(c - q1) / dist(q2 - q1)
            // However: the points q1, q2, p1, p2 might be ideal points - or in general - the
            // coordinates might be not normalized.
            // Note that the z-coordinates of p2 and q2 are used to determine whether it should be interpreted
            // as a segment coordinate or a direction.
            i = (Math.abs(p2[1] - p2[0] * p1[1]) < Mat.eps) ? 2 : 1;
            d = p1[i] / p1[0];
            t = (c[i] - d) / ( (p2[0] !== 0) ? (p2[i] / p2[0] - d) : p2[i] );

            i = (Math.abs(q2[1] - q2[0] * q1[1]) < Mat.eps) ? 2 : 1;
            d = q1[i] / q1[0];
            u = (c[i] - d) / ( (q2[0] !== 0) ? (q2[i] / q2[0] - d) : q2[i] );

            return [c, t, u];
        },

        /****************************************/
        /****   BEZIER CURVE ALGORITHMS      ****/
        /****************************************/

        /**
         * Splits a Bezier curve segment defined by four points into
         * two Bezier curve segments. Dissection point is t=1/2.
         * @param {Array} curve Array of four coordinate arrays of length 2 defining a
         * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]].
         * @returns {Array} Array consisting of two coordinate arrays for Bezier curves.
         */
        _bezierSplit: function (curve) {
            var p0, p1, p2, p00, p22, p000;

            p0 = [(curve[0][0] + curve[1][0]) * 0.5, (curve[0][1] + curve[1][1]) * 0.5];
            p1 = [(curve[1][0] + curve[2][0]) * 0.5, (curve[1][1] + curve[2][1]) * 0.5];
            p2 = [(curve[2][0] + curve[3][0]) * 0.5, (curve[2][1] + curve[3][1]) * 0.5];

            p00 = [(p0[0] + p1[0]) * 0.5, (p0[1] + p1[1]) * 0.5];
            p22 = [(p1[0] + p2[0]) * 0.5, (p1[1] + p2[1]) * 0.5];

            p000 = [(p00[0] + p22[0]) * 0.5, (p00[1] + p22[1]) * 0.5];

            return [[curve[0], p0, p00, p000], [p000, p22, p2, curve[3]]];
        },

        /**
         * Computes the bounding box [minX, maxY, maxX, minY] of a Bezier curve segment
         * from its control points.
         * @param {Array} curve Array of four coordinate arrays of length 2 defining a
         * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]].
         * @returns {Array} Bounding box [minX, maxY, maxX, minY]
         */
        _bezierBbox: function (curve) {
            var bb = [];

            if (curve.length === 4) {   // bezierDegree == 3
                bb[0] = Math.min(curve[0][0], curve[1][0], curve[2][0], curve[3][0]); // minX
                bb[1] = Math.max(curve[0][1], curve[1][1], curve[2][1], curve[3][1]); // maxY
                bb[2] = Math.max(curve[0][0], curve[1][0], curve[2][0], curve[3][0]); // maxX
                bb[3] = Math.min(curve[0][1], curve[1][1], curve[2][1], curve[3][1]); // minY
            } else {                   // bezierDegree == 1
                bb[0] = Math.min(curve[0][0], curve[1][0]); // minX
                bb[1] = Math.max(curve[0][1], curve[1][1]); // maxY
                bb[2] = Math.max(curve[0][0], curve[1][0]); // maxX
                bb[3] = Math.min(curve[0][1], curve[1][1]); // minY
            }

            return bb;
        },

        /**
         * Decide if two Bezier curve segments overlap by comparing their bounding boxes.
         * @param {Array} bb1 Bounding box of the first Bezier curve segment
         * @param {Array} bb2 Bounding box of the second Bezier curve segment
         * @returns {Boolean} true if the bounding boxes overlap, false otherwise.
         */
        _bezierOverlap: function (bb1, bb2) {
            return bb1[2] >= bb2[0] && bb1[0] <= bb2[2] && bb1[1] >= bb2[3] && bb1[3] <= bb2[1];
        },

        /**
         * Append list of intersection points to a list.
         * @private
         */
        _bezierListConcat: function (L, Lnew, t1, t2) {
            var i,
                t2exists = Type.exists(t2),
                start = 0,
                len = Lnew.length,
                le = L.length;

            if (le > 0 && len > 0 &&
                    ((L[le - 1][1] === 1 && Lnew[0][1] === 0) ||
                    (t2exists && L[le - 1][2] === 1 && Lnew[0][2] === 0))) {
                start = 1;
            }

            for (i = start; i < len; i++) {
                if (t2exists) {
                    Lnew[i][2] *= 0.5;
                    Lnew[i][2] += t2;
                }

                Lnew[i][1] *= 0.5;
                Lnew[i][1] += t1;

                L.push(Lnew[i]);
            }
        },

        /**
         * Find intersections of two Bezier curve segments by recursive subdivision.
         * Below maxlevel determine intersections by intersection line segments.
         * @param {Array} red Array of four coordinate arrays of length 2 defining the first
         * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]].
         * @param {Array} blue Array of four coordinate arrays of length 2 defining the second
         * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]].
         * @param {Number} level Recursion level
         * @returns {Array} List of intersection points (up to nine). Each intersection point is an
         * array of length three (homogeneous coordinates) plus preimages.
         */
        _bezierMeetSubdivision: function (red, blue, level) {
            var bbb, bbr,
                ar, b0, b1, r0, r1, m,
                p0, p1, q0, q1,
                L = [],
                maxLev = 5;      // Maximum recursion level

            bbr = this._bezierBbox(blue);
            bbb = this._bezierBbox(red);

            if (!this._bezierOverlap(bbr, bbb)) {
                return [];
            }

            if (level < maxLev) {
                ar = this._bezierSplit(red);
                r0 = ar[0];
                r1 = ar[1];

                ar = this._bezierSplit(blue);
                b0 = ar[0];
                b1 = ar[1];

                this._bezierListConcat(L, this._bezierMeetSubdivision(r0, b0, level + 1), 0.0, 0.0);
                this._bezierListConcat(L, this._bezierMeetSubdivision(r0, b1, level + 1), 0, 0.5);
                this._bezierListConcat(L, this._bezierMeetSubdivision(r1, b0, level + 1), 0.5, 0.0);
                this._bezierListConcat(L, this._bezierMeetSubdivision(r1, b1, level + 1), 0.5, 0.5);

                return L;
            }

            // Make homogeneous coordinates
            q0 = [1].concat(red[0]);
            q1 = [1].concat(red[3]);
            p0 = [1].concat(blue[0]);
            p1 = [1].concat(blue[3]);

            m = this.meetSegmentSegment(q0, q1, p0, p1);

            if (m[1] >= 0.0 && m[2] >= 0.0 && m[1] <= 1.0 && m[2] <= 1.0) {
                return [m];
            }

            return [];
        },

        /**
         * @param {Boolean} testSegment Test if intersection has to be inside of the segment or somewhere on the line defined by the segment
         */
        _bezierLineMeetSubdivision: function (red, blue, level, testSegment) {
            var bbb, bbr,
                ar, r0, r1, m,
                p0, p1, q0, q1,
                L = [],
                maxLev = 5;      // Maximum recursion level

            bbb = this._bezierBbox(blue);
            bbr = this._bezierBbox(red);

            if (testSegment && !this._bezierOverlap(bbr, bbb)) {
                return [];
            }

            if (level < maxLev) {
                ar = this._bezierSplit(red);
                r0 = ar[0];
                r1 = ar[1];

                this._bezierListConcat(L, this._bezierLineMeetSubdivision(r0, blue, level + 1), 0.0);
                this._bezierListConcat(L, this._bezierLineMeetSubdivision(r1, blue, level + 1), 0.5);

                return L;
            }

            // Make homogeneous coordinates
            q0 = [1].concat(red[0]);
            q1 = [1].concat(red[3]);
            p0 = [1].concat(blue[0]);
            p1 = [1].concat(blue[1]);

            m = this.meetSegmentSegment(q0, q1, p0, p1);

            if (m[1] >= 0.0 && m[1] <= 1.0) {
                if (!testSegment || (m[2] >= 0.0 && m[2] <= 1.0)) {
                    return [m];
                }
            }

            return [];
        },

        /**
         * Find the nr-th intersection point of two Bezier curve segments.
         * @param {Array} red Array of four coordinate arrays of length 2 defining the first
         * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]].
         * @param {Array} blue Array of four coordinate arrays of length 2 defining the second
         * Bezier curve segment, i.e. [[x0,y0], [x1,y1], [x2,y2], [x3,y3]].
         * @param {Boolean} testSegment Test if intersection has to be inside of the segment or somewhere on the line defined by the segment
         * @returns {Array} Array containing the list of all intersection points as homogeneous coordinate arrays plus
         * preimages [x,y], t_1, t_2] of the two Bezier curve segments.
         *
         */
        meetBeziersegmentBeziersegment: function (red, blue, testSegment) {
            var L, L2, i;

            if (red.length === 4 && blue.length === 4) {
                L = this._bezierMeetSubdivision(red, blue, 0);
            } else {
                L = this._bezierLineMeetSubdivision(red, blue, 0, testSegment);
            }

            L.sort(function (a, b) {
                return (a[1] - b[1]) * 10000000.0 + (a[2] - b[2]);
            });

            L2 = [];
            for (i = 0; i < L.length; i++) {
                // Only push entries different from their predecessor
                if (i === 0 || (L[i][1] !== L[i - 1][1] || L[i][2] !== L[i - 1][2])) {
                    L2.push(L[i]);
                }
            }
            return L2;
        },

        /**
         * Find the nr-th intersection point of two Bezier curves, i.e. curves with bezierDegree == 3.
         * @param {JXG.Curve} red Curve with bezierDegree == 3
         * @param {JXG.Curve} blue Curve with bezierDegree == 3
         * @param {Number} nr The number of the intersection point which should be returned.
         * @returns {Array} The homogeneous coordinates of the nr-th intersection point.
         */
        meetBezierCurveRedBlueSegments: function (red, blue, nr) {
            var p, i, j, k, po,
                redArr, blueArr,
                bbr, bbb, intersections,
                startRed = 0,
                startBlue = 0,
                lenBlue = blue.numberPoints,
                lenRed = red.numberPoints,
                L = [];

            if (lenBlue < blue.bezierDegree + 1 || lenRed < red.bezierDegree + 1) {
                return [0, NaN, NaN];
            }
            lenBlue -= blue.bezierDegree;
            lenRed  -= red.bezierDegree;

            // For sectors, we ignore the "legs"
            if (red.type === Const.OBJECT_TYPE_SECTOR) {
                startRed = 3;
                lenRed  -= 3;
            }
            if (blue.type === Const.OBJECT_TYPE_SECTOR) {
                startBlue = 3;
                lenBlue  -= 3;
            }

            for (i = startRed; i < lenRed; i += red.bezierDegree) {
                p = red.points;
                redArr = [
                    p[i].usrCoords.slice(1),
                    p[i + 1].usrCoords.slice(1)
                ];
                if (red.bezierDegree === 3) {
                    redArr[2] = p[i + 2].usrCoords.slice(1);
                    redArr[3] = p[i + 3].usrCoords.slice(1);
                }

                bbr = this._bezierBbox(redArr);

                for (j = startBlue; j < lenBlue; j += blue.bezierDegree) {
                    p = blue.points;
                    blueArr = [
                        p[j].usrCoords.slice(1),
                        p[j + 1].usrCoords.slice(1)
                    ];
                    if (blue.bezierDegree === 3) {
                        blueArr[2] = p[j + 2].usrCoords.slice(1);
                        blueArr[3] = p[j + 3].usrCoords.slice(1);
                    }

                    bbb = this._bezierBbox(blueArr);
                    if (this._bezierOverlap(bbr, bbb)) {
                        intersections = this.meetBeziersegmentBeziersegment(redArr, blueArr);
                        if (intersections.length === 0) {
                            continue;
                        }
                        for (k = 0; k < intersections.length; k++) {
                            po = intersections[k];
                            if (po[1] < -Mat.eps ||
                                po[1] > 1 + Mat.eps ||
                                po[2] < -Mat.eps ||
                                po[2] > 1 + Mat.eps) {
                                continue;
                            }
                            L.push(po);
                        }
                        if (L.length > nr) {
                            return L[nr][0];
                        }
                    }
                }
            }
            if (L.length > nr) {
                return L[nr][0];
            }

            return [0, NaN, NaN];
        },

        bezierSegmentEval: function (t, curve) {
            var f, x, y,
                t1 = 1.0 - t;

            x = 0;
            y = 0;

            f = t1 * t1 * t1;
            x += f * curve[0][0];
            y += f * curve[0][1];

            f = 3.0 * t * t1 * t1;
            x += f * curve[1][0];
            y += f * curve[1][1];

            f = 3.0 * t * t * t1;
            x += f * curve[2][0];
            y += f * curve[2][1];

            f = t * t * t;
            x += f * curve[3][0];
            y += f * curve[3][1];

            return [1.0, x, y];
        },

        /**
         * Generate the defining points of a 3rd degree bezier curve that approximates
         * a circle sector defined by three coordinate points A, B, C, each defined by an array of length three.
         * The coordinate arrays are given in homogeneous coordinates.
         * @param {Array} A First point
         * @param {Array} B Second point (intersection point)
         * @param {Array} C Third point
         * @param {Boolean} withLegs Flag. If true the legs to the intersection point are part of the curve.
         * @param {Number} sgn Wither 1 or -1. Needed for minor and major arcs. In case of doubt, use 1.
         */
        bezierArc: function (A, B, C, withLegs, sgn) {
            var p1, p2, p3, p4,
                r, phi, beta,
                PI2 = Math.PI * 0.5,
                x = B[1],
                y = B[2],
                z = B[0],
                dataX = [], dataY = [],
                co, si, ax, ay, bx, by, k, v, d, matrix;

            r = this.distance(B, A);

            // x,y, z is intersection point. Normalize it.
            x /= z;
            y /= z;

            phi = this.rad(A.slice(1), B.slice(1), C.slice(1));
            if (sgn === -1) {
                phi = 2 * Math.PI - phi;
            }

            p1 = A;
            p1[1] /= p1[0];
            p1[2] /= p1[0];
            p1[0] /= p1[0];

            p4 = p1.slice(0);

            if (withLegs) {
                dataX = [x, x + 0.333 * (p1[1] - x), x + 0.666 * (p1[1] - x), p1[1]];
                dataY = [y, y + 0.333 * (p1[2] - y), y + 0.666 * (p1[2] - y), p1[2]];
            } else {
                dataX = [p1[1]];
                dataY = [p1[2]];
            }

            while (phi > Mat.eps) {
                if (phi > PI2) {
                    beta = PI2;
                    phi -= PI2;
                } else {
                    beta = phi;
                    phi = 0;
                }

                co = Math.cos(sgn * beta);
                si = Math.sin(sgn * beta);

                matrix = [
                    [1, 0, 0],
                    [x * (1 - co) + y * si, co, -si],
                    [y * (1 - co) - x * si, si,  co]
                ];
                v = Mat.matVecMult(matrix, p1);
                p4 = [v[0] / v[0], v[1] / v[0], v[2] / v[0]];

                ax = p1[1] - x;
                ay = p1[2] - y;
                bx = p4[1] - x;
                by = p4[2] - y;

                d = Math.sqrt((ax + bx) * (ax + bx) + (ay + by) * (ay + by));

                if (Math.abs(by - ay) > Mat.eps) {
                    k = (ax + bx) * (r / d - 0.5) / (by - ay) * 8 / 3;
                } else {
                    k = (ay + by) * (r / d - 0.5) / (ax - bx) * 8 / 3;
                }

                p2 = [1, p1[1] - k * ay, p1[2] + k * ax];
                p3 = [1, p4[1] + k * by, p4[2] - k * bx];

                dataX = dataX.concat([p2[1], p3[1], p4[1]]);
                dataY = dataY.concat([p2[2], p3[2], p4[2]]);
                p1 = p4.slice(0);
            }

            if (withLegs) {
                dataX = dataX.concat([ p4[1] + 0.333 * (x - p4[1]), p4[1] + 0.666 * (x - p4[1]), x]);
                dataY = dataY.concat([ p4[2] + 0.333 * (y - p4[2]), p4[2] + 0.666 * (y - p4[2]), y]);
            }

            return [dataX, dataY];
        },

        /****************************************/
        /****           PROJECTIONS          ****/
        /****************************************/

        /**
         * Calculates the coordinates of the projection of a given point on a given circle. I.o.w. the
         * nearest one of the two intersection points of the line through the given point and the circles
         * center.
         * @param {JXG.Point,JXG.Coords} point Point to project or coords object to project.
         * @param {JXG.Circle} circle Circle on that the point is projected.
         * @param {JXG.Board} [board=point.board] Reference to the board
         * @returns {JXG.Coords} The coordinates of the projection of the given point on the given circle.
         */
        projectPointToCircle: function (point, circle, board) {
            var dist, P, x, y, factor,
                M = circle.center.coords.usrCoords;

            if (!Type.exists(board)) {
                board = point.board;
            }

            // gave us a point
            if (Type.isPoint(point)) {
                dist = point.coords.distance(Const.COORDS_BY_USER, circle.center.coords);
                P = point.coords.usrCoords;
            // gave us coords
            } else {
                dist = point.distance(Const.COORDS_BY_USER, circle.center.coords);
                P = point.usrCoords;
            }

            if (Math.abs(dist) < Mat.eps) {
                dist = Mat.eps;
            }

            factor = circle.Radius() / dist;
            x = M[1] + factor * (P[1] - M[1]);
            y = M[2] + factor * (P[2] - M[2]);

            return new Coords(Const.COORDS_BY_USER, [x, y], board);
        },

        /**
         * Calculates the coordinates of the orthogonal projection of a given point on a given line. I.o.w. the
         * intersection point of the given line and its perpendicular through the given point.
         * @param {JXG.Point|JXG.Coords} point Point to project.
         * @param {JXG.Line} line Line on that the point is projected.
         * @param {JXG.Board} [board=point.board|board=line.board] Reference to a board.
         * @returns {JXG.Coords} The coordinates of the projection of the given point on the given line.
         */
        projectPointToLine: function (point, line, board) {
            var v = [0, line.stdform[1], line.stdform[2]],
                coords;

            if (!Type.exists(board)) {
                if (Type.exists(point.coords)) {
                    board = point.board;
                } else {
                    board = line.board;
                }
            }

            if (Type.exists(point.coords)) {
                coords = point.coords.usrCoords;
            } else {
                coords = point.usrCoords;
            }

            v = Mat.crossProduct(v, coords);
            return new Coords(Const.COORDS_BY_USER, Mat.crossProduct(v, line.stdform), board);
        },

        /**
         * Calculates the coordinates of the orthogonal projection of a given coordinate array on a given line
         * segment defined by two coordinate arrays.
         * @param {Array} p Point to project.
         * @param {Array} q1 Start point of the line segment on that the point is projected.
         * @param {Array} q2 End point of the line segment on that the point is projected.
         * @returns {Array} The coordinates of the projection of the given point on the given segment
         * and the factor that determines the projected point as a convex combination of the
         * two endpoints q1 and q2 of the segment.
         */
        projectCoordsToSegment: function (p, q1, q2) {
            var t, denom,
                s = [q2[1] - q1[1], q2[2] - q1[2]],
                v = [p[1] - q1[1], p[2] - q1[2]];

            /**
             * If the segment has length 0, i.e. is a point,
             * the projection is equal to that point.
             */
            if (Math.abs(s[0]) < Mat.eps && Math.abs(s[1]) < Mat.eps) {
                return [q1, 0];
            }

            t = Mat.innerProduct(v, s);
            denom = Mat.innerProduct(s, s);
            t /= denom;

            return [ [1, t * s[0] + q1[1], t * s[1] + q1[2]], t];
        },

        /**
         * Finds the coordinates of the closest point on a Bezier segment of a
         * {@link JXG.Curve} to a given coordinate array.
         * @param {Array} pos Point to project in homogeneous coordinates.
         * @param {JXG.Curve} curve Curve of type "plot" having Bezier degree 3.
         * @param {Number} start Number of the Bezier segment of the curve.
         * @returns {Array} The coordinates of the projection of the given point
         * on the given Bezier segment and the preimage of the curve which
         * determines the closest point.
         */
        projectCoordsToBeziersegment: function (pos, curve, start) {
            var t0,
                /** @ignore */
                minfunc = function (t) {
                    var z = [1, curve.X(start + t), curve.Y(start + t)];

                    z[1] -= pos[1];
                    z[2] -= pos[2];

                    return z[1] * z[1] + z[2] * z[2];
                };

            t0 = JXG.Math.Numerics.fminbr(minfunc, [0.0, 1.0]);

            return [[1, curve.X(t0 + start), curve.Y(t0 + start)], t0];
        },

        /**
         * Calculates the coordinates of the projection of a given point on a given curve.
         * Uses {@link JXG.Math.Geometry.projectCoordsToCurve}.
         *
         * @param {JXG.Point} point Point to project.
         * @param {JXG.Curve} curve Curve on that the point is projected.
         * @param {JXG.Board} [board=point.board] Reference to a board.
         * @see #projectCoordsToCurve
         * @returns {Array} [JXG.Coords, position] The coordinates of the projection of the given
         * point on the given graph and the relative position on the curve (real number).
         */
        projectPointToCurve: function (point, curve, board) {
            if (!Type.exists(board)) {
                board = point.board;
            }

            var x = point.X(),
                y = point.Y(),
                t = point.position || 0.0,
                result = this.projectCoordsToCurve(x, y, t, curve, board);

            // point.position = result[1];

            return result;
        },

        /**
         * Calculates the coordinates of the projection of a coordinates pair on a given curve. In case of
         * function graphs this is the
         * intersection point of the curve and the parallel to y-axis through the given point.
         * @param {Number} x coordinate to project.
         * @param {Number} y coordinate to project.
         * @param {Number} t start value for newtons method
         * @param {JXG.Curve} curve Curve on that the point is projected.
         * @param {JXG.Board} [board=curve.board] Reference to a board.
         * @see #projectPointToCurve
         * @returns {JXG.Coords} Array containing the coordinates of the projection of the given point on the given curve and
         * the position on the curve.
         */
        projectCoordsToCurve: function (x, y, t, curve, board) {
            var newCoords, newCoordsObj, i, j,
                mindist, dist, lbda, v, coords, d,
                p1, p2, res,
                minfunc, t_new, f_new, f_old, delta, steps,
                minX, maxX,
                infty = Number.POSITIVE_INFINITY;

            if (!Type.exists(board)) {
                board = curve.board;
            }


            if (Type.evaluate(curve.visProp.curvetype) === 'plot') {
                t = 0;
                mindist = infty;
                if (curve.numberPoints === 0) {
                    newCoords = [0, 1, 1];
                } else {
                    newCoords = [curve.Z(0), curve.X(0), curve.Y(0)];
                }

                if (curve.numberPoints > 1) {
                    v = [1, x, y];
                    if (curve.bezierDegree === 3) {
                        j = 0;
                    } else {
                        p1 = [curve.Z(0), curve.X(0), curve.Y(0)];
                    }
                    for (i = 0; i < curve.numberPoints - 1; i++) {
                        if (curve.bezierDegree === 3) {
                            res = this.projectCoordsToBeziersegment(v, curve, j);
                        } else {
                            p2 = [curve.Z(i + 1), curve.X(i + 1), curve.Y(i + 1)];
                            res = this.projectCoordsToSegment(v, p1, p2);
                        }
                        lbda = res[1];
                        coords = res[0];

                        if (0.0 <= lbda && lbda <= 1.0) {
                            dist = this.distance(coords, v);
                            d = i + lbda;
                        } else if (lbda < 0.0) {
                            coords = p1;
                            dist = this.distance(p1, v);
                            d = i;
                        } else if (lbda > 1.0 && i === curve.numberPoints - 2) {
                            coords = p2;
                            dist = this.distance(coords, v);
                            d = curve.numberPoints - 1;
                        }

                        if (dist < mindist) {
                            mindist = dist;
                            t = d;
                            newCoords = coords;
                        }

                        if (curve.bezierDegree === 3) {
                            j++;
                            i += 2;
                        } else {
                            p1 = p2;
                        }
                    }
                }

                newCoordsObj = new Coords(Const.COORDS_BY_USER, newCoords, board);
            } else {   // 'parameter', 'polar', 'functiongraph'
                /** @ignore */
                minfunc = function (t) {
                    var dx, dy;
                    if (t < curve.minX() || t > curve.maxX()) {
                        return Infinity;
                    }
                    dx = x - curve.X(t);
                    dy = y - curve.Y(t);
                    return dx * dx + dy * dy;
                };

                f_old = minfunc(t);
                steps = 50;
                minX = curve.minX();
                maxX = curve.maxX();

                delta = (maxX - minX) / steps;
                t_new = minX;

                for (i = 0; i < steps; i++) {
                    f_new = minfunc(t_new);

                    if (f_new < f_old || f_old === Infinity) {
                        t = t_new;
                        f_old = f_new;
                    }

                    t_new += delta;
                }

                //t = Numerics.root(Numerics.D(minfunc), t);
                t = Numerics.fminbr(minfunc, [Math.max(t - delta, minX), Math.min(t + delta, maxX)]);

                // Distinction between closed and open curves is not necessary.
                // If closed, the cyclic projection shift will work anyhow
                // if (Math.abs(curve.X(minX) - curve.X(maxX)) < Mat.eps &&
                //     Math.abs(curve.Y(minX) - curve.Y(maxX)) < Mat.eps) {
                //     // Cyclically
                //     if (t < minX) {
                //         t = maxX + t - minX;
                //     }
                //     if (t > maxX) {
                //         t = minX + t - maxX;
                //     }
                // } else {
                    t = (t < minX) ? minX : t;
                    t = (t > maxX) ? maxX : t;
                // }

                newCoordsObj = new Coords(Const.COORDS_BY_USER, [curve.X(t), curve.Y(t)], board);
            }

            return [curve.updateTransform(newCoordsObj), t];
        },

        /**
         * Calculates the coordinates of the closest orthogonal projection of a given coordinate array onto the
         * border of a polygon.
         * @param {Array} p Point to project.
         * @param {JXG.Polygon} pol Polygon element
         * @returns {Array} The coordinates of the closest projection of the given point to the border of the polygon.
         */
        projectCoordsToPolygon: function (p, pol) {
            var i,
                len = pol.vertices.length,
                d_best = Infinity,
                d,
                projection, proj,
                bestprojection;

            for (i = 0; i < len - 1; i++) {
                projection = JXG.Math.Geometry.projectCoordsToSegment(
                    p,
                    pol.vertices[i].coords.usrCoords,
                    pol.vertices[i + 1].coords.usrCoords
                );

                if (0 <= projection[1] && projection[1] <= 1) {
                    d = JXG.Math.Geometry.distance(projection[0], p, 3);
                    proj = projection[0];
                } else if (projection[1] < 0) {
                    d = JXG.Math.Geometry.distance(pol.vertices[i].coords.usrCoords, p, 3);
                    proj = pol.vertices[i].coords.usrCoords;
                } else {
                    d = JXG.Math.Geometry.distance(pol.vertices[i + 1].coords.usrCoords, p, 3);
                    proj = pol.vertices[i + 1].coords.usrCoords;
                }
                if (d < d_best) {
                    bestprojection = proj.slice(0);
                    d_best = d;
                }
            }
            return bestprojection;
        },

        /**
         * Calculates the coordinates of the projection of a given point on a given turtle. A turtle consists of
         * one or more curves of curveType 'plot'. Uses {@link JXG.Math.Geometry.projectPointToCurve}.
         * @param {JXG.Point} point Point to project.
         * @param {JXG.Turtle} turtle on that the point is projected.
         * @param {JXG.Board} [board=point.board] Reference to a board.
         * @returns {Array} [JXG.Coords, position] Array containing the coordinates of the projection of the given point on the turtle and
         * the position on the turtle.
         */
        projectPointToTurtle: function (point, turtle, board) {
            var newCoords, t, x, y, i, dist, el, minEl,
                res, newPos,
                np = 0,
                npmin = 0,
                mindist = Number.POSITIVE_INFINITY,
                len = turtle.objects.length;

            if (!Type.exists(board)) {
                board = point.board;
            }

            // run through all curves of this turtle
            for (i = 0; i < len; i++) {
                el = turtle.objects[i];

                if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
                    res = this.projectPointToCurve(point, el);
                    newCoords = res[0];
                    newPos = res[1];
                    dist = this.distance(newCoords.usrCoords, point.coords.usrCoords);

                    if (dist < mindist) {
                        x = newCoords.usrCoords[1];
                        y = newCoords.usrCoords[2];
                        t = newPos;
                        mindist = dist;
                        minEl = el;
                        npmin = np;
                    }
                    np += el.numberPoints;
                }
            }

            newCoords = new Coords(Const.COORDS_BY_USER, [x, y], board);
            // point.position = t + npmin;
            // return minEl.updateTransform(newCoords);
            return [minEl.updateTransform(newCoords), t + npmin];
        },

        /**
         * Trivial projection of a point to another point.
         * @param {JXG.Point} point Point to project (not used).
         * @param {JXG.Point} dest Point on that the point is projected.
         * @returns {JXG.Coords} The coordinates of the projection of the given point on the given circle.
         */
        projectPointToPoint: function (point, dest) {
            return dest.coords;
        },

        /**
         *
         * @param {JXG.Point|JXG.Coords} point
         * @param {JXG.Board} [board]
         */
        projectPointToBoard: function (point, board) {
            var i, l, c,
                brd = board || point.board,
                // comparison factor, point coord idx, bbox idx, 1st bbox corner x & y idx, 2nd bbox corner x & y idx
                config = [
                    // left
                    [1, 1, 0, 0, 3, 0, 1],
                    // top
                    [-1, 2, 1, 0, 1, 2, 1],
                    // right
                    [-1, 1, 2, 2, 1, 2, 3],
                    // bottom
                    [1, 2, 3, 0, 3, 2, 3]
                ],
                coords = point.coords || point,
                bbox = brd.getBoundingBox();

            for (i = 0; i < 4; i++) {
                c = config[i];
                if (c[0] * coords.usrCoords[c[1]] < c[0] * bbox[c[2]]) {
                    // define border
                    l = Mat.crossProduct([1, bbox[c[3]], bbox[c[4]]], [1, bbox[c[5]], bbox[c[6]]]);
                    l[3] = 0;
                    l = Mat.normalize(l);

                    // project point
                    coords = this.projectPointToLine({coords: coords}, {stdform: l}, brd);
                }
            }

            return coords;
        },

        /**
         * Calculates the distance of a point to a line. The point and the line are given by homogeneous
         * coordinates. For lines this can be line.stdform.
         * @param {Array} point Homogeneous coordinates of a point.
         * @param {Array} line Homogeneous coordinates of a line ([C,A,B] where A*x+B*y+C*z=0).
         * @returns {Number} Distance of the point to the line.
         */
        distPointLine: function (point, line) {
            var a = line[1],
                b = line[2],
                c = line[0],
                nom;

            if (Math.abs(a) + Math.abs(b) < Mat.eps) {
                return Number.POSITIVE_INFINITY;
            }

            nom = a * point[1] + b * point[2] + c;
            a *= a;
            b *= b;

            return Math.abs(nom) / Math.sqrt(a + b);
        },

        /**
         * Helper function to create curve which displays a Reuleaux polygons.
         * @param {Array} points Array of points which should be the vertices of the Reuleaux polygon. Typically,
         * these point list is the array vertices of a regular polygon.
         * @param {Number} nr Number of vertices
         * @returns {Array} An array containing the two functions defining the Reuleaux polygon and the two values
         * for the start and the end of the paramtric curve. array may be used as parent array of a
         * {@link JXG.Curve}.
         *
         * @example
         * var A = brd.create('point',[-2,-2]);
         * var B = brd.create('point',[0,1]);
         * var pol = brd.create('regularpolygon',[A,B,3], {withLines:false, fillColor:'none', highlightFillColor:'none', fillOpacity:0.0});
         * var reuleauxTriangle = brd.create('curve', JXG.Math.Geometry.reuleauxPolygon(pol.vertices, 3),
         *                          {strokeWidth:6, strokeColor:'#d66d55', fillColor:'#ad5544', highlightFillColor:'#ad5544'});
         *
         * </pre><div class="jxgbox" id="JXG2543a843-46a9-4372-abc1-94d9ad2db7ac" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         * var brd = JXG.JSXGraph.initBoard('JXG2543a843-46a9-4372-abc1-94d9ad2db7ac', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright:false, shownavigation: false});
         * var A = brd.create('point',[-2,-2]);
         * var B = brd.create('point',[0,1]);
         * var pol = brd.create('regularpolygon',[A,B,3], {withLines:false, fillColor:'none', highlightFillColor:'none', fillOpacity:0.0});
         * var reuleauxTriangle = brd.create('curve', JXG.Math.Geometry.reuleauxPolygon(pol.vertices, 3),
         *                          {strokeWidth:6, strokeColor:'#d66d55', fillColor:'#ad5544', highlightFillColor:'#ad5544'});
         * </script><pre>
         */
        reuleauxPolygon: function (points, nr) {
            var beta,
                pi2 = Math.PI * 2,
                pi2_n = pi2 / nr,
                diag = (nr - 1) / 2,
                d = 0,
                makeFct = function (which, trig) {
                    return function (t, suspendUpdate) {
                        var t1 = (t % pi2 + pi2) % pi2,
                            j = Math.floor(t1 / pi2_n) % nr;

                        if (!suspendUpdate) {
                            d = points[0].Dist(points[diag]);
                            beta = Mat.Geometry.rad([points[0].X() + 1, points[0].Y()], points[0], points[diag % nr]);
                        }

                        if (isNaN(j)) {
                            return j;
                        }

                        t1 = t1 * 0.5 + j * pi2_n * 0.5 + beta;

                        return points[j][which]() + d * Math[trig](t1);
                    };
                };

            return [makeFct('X', 'cos'), makeFct('Y', 'sin'), 0, pi2];
        }
    });

    return Mat.Geometry;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Alfred Wassermann

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/math
 utils/type
 */

define('math/plot',['jxg', 'base/constants', 'base/coords', 'math/math', 'math/extrapolate', 'math/numerics',
        'math/statistics', 'math/geometry', 'math/ia', 'utils/type'],
        function (JXG, Const, Coords, Mat, Extrapolate, Numerics, Statistics, Geometry, IntervalArithmetic, Type) {

    "use strict";

    /**
     * Functions for plotting of curves.
     * @name JXG.Math.Plot
     * @exports Mat.Plot as JXG.Math.Plot
     * @namespace
     */
    Mat.Plot = {

        /**
         * Check if at least one point on the curve is finite and real.
         **/
        checkReal: function (points) {
            var b = false,
                i, p,
                len = points.length;

            for (i = 0; i < len; i++) {
                p = points[i].usrCoords;
                if (!isNaN(p[1]) && !isNaN(p[2]) && Math.abs(p[0]) > Mat.eps) {
                    b = true;
                    break;
                }
            }
            return b;
        },

        //----------------------------------------------------------------------
        // Plot algorithm v0
        //----------------------------------------------------------------------
        /**
         * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>false</tt>.
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Number} mi Left bound of curve
         * @param {Number} ma Right bound of curve
         * @param {Number} len Number of data points
         * @returns {JXG.Curve} Reference to the curve object.
         */
        updateParametricCurveNaive: function (curve, mi, ma, len) {
            var i, t,
                suspendUpdate = false,
                stepSize = (ma - mi) / len;

            for (i = 0; i < len; i++) {
                t = mi + i * stepSize;
                // The last parameter prevents rounding in usr2screen().
                curve.points[i].setCoordinates(Const.COORDS_BY_USER, [curve.X(t, suspendUpdate), curve.Y(t, suspendUpdate)], false);
                curve.points[i]._t = t;
                suspendUpdate = true;
            }
            return curve;
        },

        //----------------------------------------------------------------------
        // Plot algorithm v1
        //----------------------------------------------------------------------
        /**
         * Crude and cheap test if the segment defined by the two points <tt>(x0, y0)</tt> and <tt>(x1, y1)</tt> is
         * outside the viewport of the board. All parameters have to be given in screen coordinates.
         *
         * @private
         * @deprecated
         * @param {Number} x0
         * @param {Number} y0
         * @param {Number} x1
         * @param {Number} y1
         * @param {JXG.Board} board
         * @returns {Boolean} <tt>true</tt> if the given segment is outside the visible area.
         */
        isSegmentOutside: function (x0, y0, x1, y1, board) {
            return (y0 < 0 && y1 < 0) || (y0 > board.canvasHeight && y1 > board.canvasHeight) ||
                (x0 < 0 && x1 < 0) || (x0 > board.canvasWidth && x1 > board.canvasWidth);
        },

        /**
         * Compares the absolute value of <tt>dx</tt> with <tt>MAXX</tt> and the absolute value of <tt>dy</tt>
         * with <tt>MAXY</tt>.
         *
         * @private
         * @deprecated
         * @param {Number} dx
         * @param {Number} dy
         * @param {Number} MAXX
         * @param {Number} MAXY
         * @returns {Boolean} <tt>true</tt>, if <tt>|dx| &lt; MAXX</tt> and <tt>|dy| &lt; MAXY</tt>.
         */
        isDistOK: function (dx, dy, MAXX, MAXY) {
            return (Math.abs(dx) < MAXX && Math.abs(dy) < MAXY) && !isNaN(dx + dy);
        },

         /**
         * @private
         * @deprecated
         */
        isSegmentDefined: function (x0, y0, x1, y1) {
            return !(isNaN(x0 + y0) && isNaN(x1 + y1));
        },

        /**
         * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#doadvancedplot} is <tt>true</tt>.
         * Since 0.99 this algorithm is deprecated. It still can be used if {@link JXG.Curve#doadvancedplotold} is <tt>true</tt>.
         *
         * @deprecated
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Number} mi Left bound of curve
         * @param {Number} ma Right bound of curve
         * @returns {JXG.Curve} Reference to the curve object.
         */
        updateParametricCurveOld: function (curve, mi, ma) {
            var i, t, d,
                x, y, t0, x0, y0, top, depth,
                MAX_DEPTH, MAX_XDIST, MAX_YDIST,
                suspendUpdate = false,
                po = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false),
                dyadicStack = [],
                depthStack = [],
                pointStack = [],
                divisors = [],
                distOK = false,
                j = 0,
                distFromLine = function (p1, p2, p0) {
                    var lbda, d,
                        x0 = p0[1] - p1[1],
                        y0 = p0[2] - p1[2],
                        x1 = p2[0] - p1[1],
                        y1 = p2[1] - p1[2],
                        den = x1 * x1 + y1 * y1;

                    if (den >= Mat.eps) {
                        lbda = (x0 * x1 + y0 * y1) / den;
                        if (lbda > 0) {
                            if (lbda <= 1) {
                                x0 -= lbda * x1;
                                y0 -= lbda * y1;
                            // lbda = 1.0;
                            } else {
                                x0 -= x1;
                                y0 -= y1;
                            }
                        }
                    }
                    d = x0 * x0 + y0 * y0;
                    return Math.sqrt(d);
                };

            JXG.deprecated('Curve.updateParametricCurveOld()');

            if (curve.board.updateQuality === curve.board.BOARD_QUALITY_LOW) {
                MAX_DEPTH = 15;
                MAX_XDIST = 10; // 10
                MAX_YDIST = 10; // 10
            } else {
                MAX_DEPTH = 21;
                MAX_XDIST = 0.7; // 0.7
                MAX_YDIST = 0.7; // 0.7
            }

            divisors[0] = ma - mi;
            for (i = 1; i < MAX_DEPTH; i++) {
                divisors[i] = divisors[i - 1] * 0.5;
            }

            i = 1;
            dyadicStack[0] = 1;
            depthStack[0] = 0;

            t = mi;
            po.setCoordinates(Const.COORDS_BY_USER, [curve.X(t, suspendUpdate), curve.Y(t, suspendUpdate)], false);

            // Now, there was a first call to the functions defining the curve.
            // Defining elements like sliders have been evaluated.
            // Therefore, we can set suspendUpdate to false, so that these defining elements
            // need not be evaluated anymore for the rest of the plotting.
            suspendUpdate = true;
            x0 = po.scrCoords[1];
            y0 = po.scrCoords[2];
            t0 = t;

            t = ma;
            po.setCoordinates(Const.COORDS_BY_USER, [curve.X(t, suspendUpdate), curve.Y(t, suspendUpdate)], false);
            x = po.scrCoords[1];
            y = po.scrCoords[2];

            pointStack[0] = [x, y];

            top = 1;
            depth = 0;

            curve.points = [];
            curve.points[j++] = new Coords(Const.COORDS_BY_SCREEN, [x0, y0], curve.board, false);

            do {
                distOK = this.isDistOK(x - x0, y - y0, MAX_XDIST, MAX_YDIST) || this.isSegmentOutside(x0, y0, x, y, curve.board);
                while (depth < MAX_DEPTH && (!distOK || depth < 6) && (depth <= 7 || this.isSegmentDefined(x0, y0, x, y))) {
                    // We jump out of the loop if
                    // * depth>=MAX_DEPTH or
                    // * (depth>=6 and distOK) or
                    // * (depth>7 and segment is not defined)

                    dyadicStack[top] = i;
                    depthStack[top] = depth;
                    pointStack[top] = [x, y];
                    top += 1;

                    i = 2 * i - 1;
                    // Here, depth is increased and may reach MAX_DEPTH
                    depth++;
                    // In that case, t is undefined and we will see a jump in the curve.
                    t = mi + i * divisors[depth];

                    po.setCoordinates(Const.COORDS_BY_USER, [curve.X(t, suspendUpdate), curve.Y(t, suspendUpdate)], false, true);
                    x = po.scrCoords[1];
                    y = po.scrCoords[2];
                    distOK = this.isDistOK(x - x0, y - y0, MAX_XDIST, MAX_YDIST) || this.isSegmentOutside(x0, y0, x, y, curve.board);
                }

                if (j > 1) {
                    d = distFromLine(curve.points[j - 2].scrCoords, [x, y], curve.points[j - 1].scrCoords);
                    if (d < 0.015) {
                        j -= 1;
                    }
                }

                curve.points[j] = new Coords(Const.COORDS_BY_SCREEN, [x, y], curve.board, false);
                curve.points[j]._t = t;
                j += 1;

                x0 = x;
                y0 = y;
                t0 = t;

                top -= 1;
                x = pointStack[top][0];
                y = pointStack[top][1];
                depth = depthStack[top] + 1;
                i = dyadicStack[top] * 2;

            } while (top > 0 && j < 500000);

            curve.numberPoints = curve.points.length;

            return curve;
        },

        //----------------------------------------------------------------------
        // Plot algorithm v2
        //----------------------------------------------------------------------

        /**
         * Add a point to the curve plot. If the new point is too close to the previously inserted point,
         * it is skipped.
         * Used in {@link JXG.Curve._plotRecursive}.
         *
         * @private
         * @param {JXG.Coords} pnt Coords to add to the list of points
         */
        _insertPoint_v2: function (curve, pnt, t) {
            var lastReal = !isNaN(this._lastCrds[1] + this._lastCrds[2]),     // The last point was real
                newReal = !isNaN(pnt.scrCoords[1] + pnt.scrCoords[2]),        // New point is real point
                cw = curve.board.canvasWidth,
                ch = curve.board.canvasHeight,
                off = 500;

            newReal = newReal &&
                        (pnt.scrCoords[1] > -off && pnt.scrCoords[2] > -off &&
                         pnt.scrCoords[1] < cw + off && pnt.scrCoords[2] < ch + off);

            /*
             * Prevents two consecutive NaNs or points wich are too close
             */
            if ((!newReal && lastReal) ||
                    (newReal && (!lastReal ||
                        Math.abs(pnt.scrCoords[1] - this._lastCrds[1]) > 0.7 ||
                        Math.abs(pnt.scrCoords[2] - this._lastCrds[2]) > 0.7))) {
                pnt._t = t;
                curve.points.push(pnt);
                this._lastCrds = pnt.copy('scrCoords');
            }
        },

        /**
         * Check if there is a single NaN function value at t0.
         * @param {*} curve
         * @param {*} t0
         * @returns {Boolean} true if there is a second NaN point close by, false otherwise
         */
        neighborhood_isNaN_v2: function(curve, t0) {
            var is_undef,
                pnt = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false),
                t, p;

            t = t0 + Mat.eps;
            pnt.setCoordinates(Const.COORDS_BY_USER, [curve.X(t, true), curve.Y(t, true)], false);
            p = pnt.usrCoords;
            is_undef = isNaN(p[1] + p[2]);
            if (!is_undef) {
                t = t0 - Mat.eps;
                pnt.setCoordinates(Const.COORDS_BY_USER, [curve.X(t, true), curve.Y(t, true)], false);
                p = pnt.usrCoords;
                is_undef = isNaN(p[1] + p[2]);
                if (!is_undef) {
                    return false;
                }
            }
            return true;
        },

        /**
         * Investigate a function term at the bounds of intervals where
         * the function is not defined, e.g. log(x) at x = 0.
         *
         * c is inbetween a and b
         * @private
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Array} a Screen coordinates of the left interval bound
         * @param {Array} b Screen coordinates of the right interval bound
         * @param {Array} c Screen coordinates of the bisection point at (ta + tb) / 2
         * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates
         * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates
         * @param {Number} tc (ta + tb) / 2 = tc. Parameter which evaluates to b, i.e. [1, X(tc), Y(tc)] = c in screen coordinates
         * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0.
         * @returns {JXG.Boolean} true if the point is inserted and the recursion should stop, false otherwise.
         */
        _borderCase: function (curve, a, b, c, ta, tb, tc, depth) {
            var t, pnt, p,
                p_good = null,
                j,
                max_it = 30,
                is_undef = false,
                t_nan, t_real, t_real2,
                vx, vy, vx2, vy2, dx, dy;
                // asymptote;

            if (depth <= 1) {
                pnt = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false);
                // Test if there is a single undefined point.
                // If yes, we ignore it.
                if (isNaN(a[1] + a[2]) && !isNaN(c[1] + c[2]) && !this.neighborhood_isNaN_v2(curve, ta)) {
                    return false;
                }
                if (isNaN(b[1] + b[2]) && !isNaN(c[1] + c[2]) && !this.neighborhood_isNaN_v2(curve, tb)) {
                    return false;
                }
                if (isNaN(c[1] + c[2]) && (!isNaN(a[1] + a[2]) || !isNaN(b[1] + b[2])) &&
                    !this.neighborhood_isNaN_v2(curve, tc)) {
                    return false;
                }

                j = 0;
                // Bisect a, b and c until the point t_real is inside of the definition interval
                // and as close as possible at the boundary.
                // t_real2 is the second closest point.
                do {
                    // There are four cases:
                    //  a  |  c  |  b
                    // ---------------
                    // inf | R   | R
                    // R   | R   | inf
                    // inf | inf | R
                    // R   | inf | inf
                    //
                    if (isNaN(a[1] + a[2]) && !isNaN(c[1] + c[2])) {
                        t_nan = ta;
                        t_real = tc;
                        t_real2 = tb;
                    } else if (isNaN(b[1] + b[2]) && !isNaN(c[1] + c[2])) {
                        t_nan = tb;
                        t_real = tc;
                        t_real2 = ta;
                    } else if (isNaN(c[1] + c[2]) && !isNaN(b[1] + b[2])) {
                        t_nan = tc;
                        t_real = tb;
                        t_real2 = tb + (tb - tc);
                    } else if (isNaN(c[1] + c[2]) && !isNaN(a[1] + a[2])) {
                        t_nan = tc;
                        t_real = ta;
                        t_real2 = ta - (tc - ta);
                    } else {
                        return false;
                    }
                    t = 0.5 * (t_nan + t_real);
                    pnt.setCoordinates(Const.COORDS_BY_USER, [curve.X(t, true), curve.Y(t, true)], false);
                    p = pnt.usrCoords;

                    is_undef = isNaN(p[1] + p[2]);
                    if (is_undef) {
                        t_nan = t;
                    } else {
                        t_real2 = t_real;
                        t_real = t;
                    }
                    ++j;
                } while (is_undef && j < max_it);

                // If bisection was successful, take this point.
                // Useful only for general curves, for function graph
                // the code below overwrite p_good from here.
                if (j < max_it) {
                    p_good = p.slice();
                    c = p.slice();
                    t_real = t;
                }

                // OK, bisection has been done now.
                // t_real contains the closest inner point to the border of the interval we could find.
                // t_real2 is the second nearest point to this boundary.
                // Now we approximate the derivative by computing the slope of the line through these two points
                // and test if it is "infinite", i.e larger than 400 in absolute values.
                //
                vx = curve.X(t_real, true) ;
                vx2 = curve.X(t_real2, true) ;
                dx = (vx - vx2) / (t_real - t_real2);
                vy = curve.Y(t_real, true) ;
                vy2 = curve.Y(t_real2, true) ;
                dy = (vy - vy2) / (t_real - t_real2);

                if (p_good !== null) {
                    this._insertPoint_v2(curve, new Coords(Const.COORDS_BY_USER, p_good, curve.board, false));
                    return true;
                }
           }
           return false;
       },

        /**
         * Recursive interval bisection algorithm for curve plotting.
         * Used in {@link JXG.Curve.updateParametricCurve}.
         * @private
         * @deprecated
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Array} a Screen coordinates of the left interval bound
         * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates
         * @param {Array} b Screen coordinates of the right interval bound
         * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates
         * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0.
         * @param {Number} delta If the distance of the bisection point at (ta + tb) / 2 from the point (a + b) / 2 is less then delta,
         *                 the segment [a,b] is regarded as straight line.
         * @returns {JXG.Curve} Reference to the curve object.
         */
        _plotRecursive_v2: function (curve, a, ta, b, tb, depth, delta) {
            var tc, c,
                ds, mindepth = 0,
                isSmooth, isJump, isCusp,
                cusp_threshold = 0.5,
                jump_threshold = 0.99,
                pnt = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false);

            if (curve.numberPoints > 65536) {
                return;
            }

            // Test if the function is undefined in an interval
            if (depth < this.nanLevel && this._isUndefined(curve, a, ta, b, tb)) {
                return this;
            }

            if (depth < this.nanLevel && this._isOutside(a, ta, b, tb, curve.board)) {
                return this;
            }

            tc = (ta  + tb) * 0.5;
            pnt.setCoordinates(Const.COORDS_BY_USER, [curve.X(tc, true), curve.Y(tc, true)], false);
            c = pnt.scrCoords;

            if (this._borderCase(curve, a, b, c, ta, tb, tc, depth)) {
                return this;
            }

            ds = this._triangleDists(a, b, c);           // returns [d_ab, d_ac, d_cb, d_cd]

            isSmooth = (depth < this.smoothLevel) && (ds[3] < delta);

            isJump = (depth < this.jumpLevel) &&
                        ((ds[2] > jump_threshold * ds[0]) ||
                         (ds[1] > jump_threshold * ds[0]) ||
                        ds[0] === Infinity || ds[1] === Infinity || ds[2] === Infinity);

            isCusp = (depth < this.smoothLevel + 2) && (ds[0] < cusp_threshold * (ds[1] + ds[2]));

            if (isCusp) {
                mindepth = 0;
                isSmooth = false;
            }

            --depth;

            if (isJump) {
                this._insertPoint_v2(curve, new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], curve.board, false), tc);
            } else if (depth <= mindepth || isSmooth) {
                this._insertPoint_v2(curve, pnt, tc);
                //if (this._borderCase(a, b, c, ta, tb, tc, depth)) {}
            } else {
                this._plotRecursive_v2(curve, a, ta, c, tc, depth, delta);

                if (!isNaN(pnt.scrCoords[1] + pnt.scrCoords[2])) {
                    this._insertPoint_v2(curve, pnt, tc);
                }

                this._plotRecursive_v2(curve, c, tc, b, tb, depth, delta);
            }

            return this;
        },

        /**
         * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#plotVersion} is <tt>3</tt>.
         *
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Number} mi Left bound of curve
         * @param {Number} ma Right bound of curve
         * @returns {JXG.Curve} Reference to the curve object.
         */
        updateParametricCurve_v2: function (curve, mi, ma) {
            var ta, tb, a, b,
                suspendUpdate = false,
                pa = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false),
                pb = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false),
                depth, delta,
                w2, h2, bbox,
                ret_arr;

            //console.time("plot");
            if (curve.board.updateQuality === curve.board.BOARD_QUALITY_LOW) {
                depth = Type.evaluate(curve.visProp.recursiondepthlow) || 13;
                delta = 2;
                // this.smoothLevel = 5; //depth - 7;
                this.smoothLevel = depth - 6;
                this.jumpLevel = 3;
            } else {
                depth = Type.evaluate(curve.visProp.recursiondepthhigh) || 17;
                delta = 2;
                // smoothLevel has to be small for graphs in a huge interval.
                // this.smoothLevel = 3; //depth - 7; // 9
                this.smoothLevel = depth - 9; // 9
                this.jumpLevel = 2;
            }
            this.nanLevel = depth - 4;

            curve.points = [];

            if (this.xterm === 'x') {
                // For function graphs we can restrict the plot interval
                // to the visible area +plus margin
                bbox = curve.board.getBoundingBox();
                w2 = (bbox[2] - bbox[0]) * 0.3;
                h2 = (bbox[1] - bbox[3]) * 0.3;
                ta = Math.max(mi, bbox[0] - w2);
                tb = Math.min(ma, bbox[2] + w2);
            } else {
                ta = mi;
                tb = ma;
            }
            pa.setCoordinates(Const.COORDS_BY_USER, [curve.X(ta, suspendUpdate), curve.Y(ta, suspendUpdate)], false);

            // The first function calls of X() and Y() are done. We can now
            // switch `suspendUpdate` on. If supported by the functions, this
            // avoids for the rest of the plotting algorithm, evaluation of any
            // parent elements.
            suspendUpdate = true;

            pb.setCoordinates(Const.COORDS_BY_USER, [curve.X(tb, suspendUpdate), curve.Y(tb, suspendUpdate)], false);

            // Find start and end points of the visible area (plus a certain margin)
            ret_arr = this._findStartPoint(curve, pa.scrCoords, ta, pb.scrCoords, tb);
            pa.setCoordinates(Const.COORDS_BY_SCREEN, ret_arr[0], false);
            ta = ret_arr[1];
            ret_arr = this._findStartPoint(curve, pb.scrCoords, tb, pa.scrCoords, ta);
            pb.setCoordinates(Const.COORDS_BY_SCREEN, ret_arr[0], false);
            tb = ret_arr[1];

            // Save the visible area.
            // This can be used in Curve.hasPoint().
            this._visibleArea = [ta, tb];

            // Start recursive plotting algorithm
            a = pa.copy('scrCoords');
            b = pb.copy('scrCoords');
            pa._t = ta;
            curve.points.push(pa);
            this._lastCrds = pa.copy('scrCoords');   // Used in _insertPoint
            this._plotRecursive_v2(curve, a, ta, b, tb, depth, delta);
            pb._t = tb;
            curve.points.push(pb);

            curve.numberPoints = curve.points.length;
            //console.timeEnd("plot");

            return curve;
        },

        //----------------------------------------------------------------------
        // Plot algorithm v3
        //----------------------------------------------------------------------
        /**
         *
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {*} pnt
         * @param {*} t
         * @param {*} depth
         * @param {*} limes
         * @private
         */
        _insertLimesPoint: function(curve, pnt, t, depth, limes) {
            var p0, p1, p2;

            // Ignore jump point if it follows limes
            if ((Math.abs(this._lastUsrCrds[1]) === Infinity && Math.abs(limes.left_x) === Infinity) ||
                (Math.abs(this._lastUsrCrds[2]) === Infinity && Math.abs(limes.left_y) === Infinity)) {
                // console.log("SKIP:", pnt.usrCoords, this._lastUsrCrds, limes);
                return;
            }

            // // Ignore jump left from limes
            // if (Math.abs(limes.left_x) > 100 * Math.abs(this._lastUsrCrds[1])) {
            //     x = Math.sign(limes.left_x) * Infinity;
            // } else {
            //     x = limes.left_x;
            // }
            // if (Math.abs(limes.left_y) > 100 * Math.abs(this._lastUsrCrds[2])) {
            //     y = Math.sign(limes.left_y) * Infinity;
            // } else {
            //     y = limes.left_y;
            // }
            // //pnt.setCoordinates(Const.COORDS_BY_USER, [x, y], false);

            // Add points at a jump. pnt contains [NaN, NaN]
            //console.log("Add", t, pnt.usrCoords, limes, depth)
            p0 = new Coords(Const.COORDS_BY_USER, [limes.left_x, limes.left_y], curve.board);
            p0._t = t;
            curve.points.push(p0);

            if (!isNaN(limes.left_x) && !isNaN(limes.left_y) && !isNaN(limes.right_x) && !isNaN(limes.right_y) &&
                (Math.abs(limes.left_x - limes.right_x) > Mat.eps || Math.abs(limes.left_y - limes.right_y) > Mat.eps)) {
                p1 = new Coords(Const.COORDS_BY_SCREEN, pnt, curve.board);
                p1._t = t;
                curve.points.push(p1);
            }

            p2 = new Coords(Const.COORDS_BY_USER, [limes.right_x, limes.right_y], curve.board);
            p2._t = t;
            curve.points.push(p2);
            this._lastScrCrds = p2.copy('scrCoords');
            this._lastUsrCrds = p2.copy('usrCoords');

        },

        /**
         * Add a point to the curve plot. If the new point is too close to the previously inserted point,
         * it is skipped.
         * Used in {@link JXG.Curve._plotRecursive}.
         *
         * @private
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {JXG.Coords} pnt Coords to add to the list of points
         */
        _insertPoint: function (curve, pnt, t, depth, limes) {
            var last_is_real = !isNaN(this._lastScrCrds[1] + this._lastScrCrds[2]),     // The last point was real
                point_is_real  = !isNaN(pnt[1] + pnt[2]),                               // New point is real point
                cw = curve.board.canvasWidth,
                ch = curve.board.canvasHeight,
                p,
                near = 0.8,
                off = 500;

            if (Type.exists(limes)) {
                this._insertLimesPoint(curve, pnt, t, depth, limes);
                return;
            }

            // Check if point has real coordinates and
            // coordinates are not too far away from canvas.
            point_is_real = point_is_real &&
                        (pnt[1] > -off     && pnt[2] > -off &&
                         pnt[1] < cw + off && pnt[2] < ch + off);

            // Prevent two consecutive NaNs
            if (!last_is_real && !point_is_real) {
                return;
            }

            // Prevent two consecutive points which are too close
            if (point_is_real && last_is_real &&
                Math.abs(pnt[1] - this._lastScrCrds[1]) < near &&
                Math.abs(pnt[2] - this._lastScrCrds[2]) < near) {
                return;
            }

            // Prevent two consecutive points at infinity (either direction)
            if ((Math.abs(pnt[1]) === Infinity &&
                 Math.abs(this._lastUsrCrds[1]) === Infinity) ||
                (Math.abs(pnt[2]) === Infinity &&
                 Math.abs(this._lastUsrCrds[2]) === Infinity)) {
                return;
            }

            //console.log("add", t, pnt.usrCoords, depth)
            // Add regular point
            p = new Coords(Const.COORDS_BY_SCREEN, pnt, curve.board);
            p._t = t;
            curve.points.push(p);
            this._lastScrCrds = p.copy('scrCoords');
            this._lastUsrCrds = p.copy('usrCoords');
        },

        /**
         * Compute distances in screen coordinates between the points ab,
         * ac, cb, and cd, where d = (a + b)/2.
         * cd is used for the smoothness test, ab, ac, cb are used to detect jumps, cusps and poles.
         *
         * @private
         * @param {Array} a Screen coordinates of the left interval bound
         * @param {Array} b Screen coordinates of the right interval bound
         * @param {Array} c Screen coordinates of the bisection point at (ta + tb) / 2
         * @returns {Array} array of distances in screen coordinates between: ab, ac, cb, and cd.
         */
        _triangleDists: function (a, b, c) {
            var d, d_ab, d_ac, d_cb, d_cd;

            d = [a[0] * b[0], (a[1] + b[1]) * 0.5, (a[2] + b[2]) * 0.5];

            d_ab = Geometry.distance(a, b, 3);
            d_ac = Geometry.distance(a, c, 3);
            d_cb = Geometry.distance(c, b, 3);
            d_cd = Geometry.distance(c, d, 3);

            return [d_ab, d_ac, d_cb, d_cd];
        },

        /**
         * Test if the function is undefined on an interval:
         * If the interval borders a and b are undefined, 20 random values
         * are tested if they are undefined, too.
         * Only if all values are undefined, we declare the function to be undefined in this interval.
         *
         * @private
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Array} a Screen coordinates of the left interval bound
         * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates
         * @param {Array} b Screen coordinates of the right interval bound
         * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates
         */
        _isUndefined: function (curve, a, ta, b, tb) {
            var t, i, pnt;

            if (!isNaN(a[1] + a[2]) || !isNaN(b[1] + b[2])) {
                return false;
            }

            pnt = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false);

            for (i = 0; i < 20; ++i) {
                t = ta + Math.random() * (tb - ta);
                pnt.setCoordinates(Const.COORDS_BY_USER, [curve.X(t, true), curve.Y(t, true)], false);
                if (!isNaN(pnt.scrCoords[0] + pnt.scrCoords[1] + pnt.scrCoords[2])) {
                    return false;
                }
            }

            return true;
        },

        /**
         * Decide if a path segment is too far from the canvas that we do not need to draw it.
         * @private
         * @param  {Array}  a  Screen coordinates of the start point of the segment
         * @param  {Array}  ta Curve parameter of a  (unused).
         * @param  {Array}  b  Screen coordinates of the end point of the segment
         * @param  {Array}  tb Curve parameter of b (unused).
         * @param  {JXG.Board} board
         * @returns {Boolean}   True if the segment is too far away from the canvas, false otherwise.
         */
        _isOutside: function (a, ta, b, tb, board) {
            var off = 500,
                cw = board.canvasWidth,
                ch = board.canvasHeight;

            return !!((a[1] < -off && b[1] < -off) ||
                (a[2] < -off && b[2] < -off) ||
                (a[1] > cw + off && b[1] > cw + off) ||
                (a[2] > ch + off && b[2] > ch + off));
        },

        /**
         * Decide if a point of a curve is too far from the canvas that we do not need to draw it.
         * @private
         * @param {Array}  a  Screen coordinates of the point
         * @param {JXG.Board} board
         * @returns {Boolean}  True if the point is too far away from the canvas, false otherwise.
         */
        _isOutsidePoint: function (a, board) {
            var off = 500,
                cw = board.canvasWidth,
                ch = board.canvasHeight;

            return !!(a[1] < -off ||
                      a[2] < -off ||
                      a[1] > cw + off ||
                      a[2] > ch + off);
        },

        /**
         * For a curve c(t) defined on the interval [ta, tb] find the first point
         * which is in the visible area of the board (plus some outside margin).
         * <p>
         * This method is necessary to restrict the recursive plotting algorithm
         * {@link JXG.Curve._plotRecursive} to the visible area and not waste
         * recursion to areas far outside of the visible area.
         * <p>
         * This method can also be used to find the last visible point
         * by reversing the input parameters.
         *
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param  {Array}  ta Curve parameter of a.
         * @param  {Array}  b  Screen coordinates of the end point of the segment (unused)
         * @param  {Array}  tb Curve parameter of b
         * @return {Array}  Array of length two containing the screen ccordinates of
         * the starting point and the curve parameter at this point.
         * @private
         */
        _findStartPoint: function (curve, a, ta, b, tb) {
            var i, delta, tc,
                td, z, isFound,
                w2, h2,
                pnt =  new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false),
                steps = 40,
                eps = 0.01,
                fnX1, fnX2, fnY1, fnY2,
                bbox = curve.board.getBoundingBox();

            // The code below is too unstable.
            // E.g. [function(t) { return Math.pow(t, 2) * (t + 5) * Math.pow(t - 5, 2); }, -8, 8]
            // Therefore, we return here.
            if (true || !this._isOutsidePoint(a, curve.board)) {
                return [a, ta];
            }

            w2 = (bbox[2] - bbox[0]) * 0.3;
            h2 = (bbox[1] - bbox[3]) * 0.3;
            bbox[0] -= w2;
            bbox[1] += h2;
            bbox[2] += w2;
            bbox[3] -= h2;

            delta = (tb - ta) / steps;
            tc = ta + delta;
            isFound = false;

            fnX1 = function(t) { return curve.X(t, true) - bbox[0]; };
            fnY1 = function(t) { return curve.Y(t, true) - bbox[1]; };
            fnX2 = function(t) { return curve.X(t, true) - bbox[2]; };
            fnY2 = function(t) { return curve.Y(t, true) - bbox[3]; };
            for (i = 0; i < steps; ++i) {
                // Left border
                z = bbox[0];
                td = Numerics.root(fnX1, [tc - delta, tc], curve);
                // td = Numerics.fzero(fnX1, [tc - delta, tc], this);
                // console.log("A", tc - delta, tc, td, Math.abs(this.X(td, true) - z));
                if (Math.abs(curve.X(td, true) - z) < eps) { //} * Math.abs(z)) {
                    isFound = true;
                    break;
                }
                // Top border
                z = bbox[1];
                td = Numerics.root(fnY1, [tc - delta, tc], curve);
                // td = Numerics.fzero(fnY1, [tc - delta, tc], this);
                // console.log("B", tc - delta, tc, td, Math.abs(this.Y(td, true) - z));
                if (Math.abs(curve.Y(td, true) - z) < eps) { // * Math.abs(z)) {
                    isFound = true;
                    break;
                }
                // Right border
                z = bbox[2];
                td = Numerics.root(fnX2, [tc - delta, tc], curve);
                // td = Numerics.fzero(fnX2, [tc - delta, tc], this);
                // console.log("C", tc - delta, tc, td, Math.abs(this.X(td, true) - z));
                if (Math.abs(curve.X(td, true) - z) < eps) { // * Math.abs(z)) {
                    isFound = true;
                    break;
                }
                // Bottom border
                z = bbox[3];
                td = Numerics.root(fnY2, [tc - delta, tc], curve);
                // td = Numerics.fzero(fnY2, [tc - delta, tc], this);
                // console.log("D", tc - delta, tc, td, Math.abs(this.Y(td, true) - z));
                if (Math.abs(curve.Y(td, true) - z) < eps) { // * Math.abs(z)) {
                    isFound = true;
                    break;
                }
                tc += delta;
            }
            if (isFound) {
                pnt.setCoordinates(Const.COORDS_BY_USER, [curve.X(td, true), curve.Y(td, true)], false);
                return [pnt.scrCoords, td];
            }
            console.log("TODO _findStartPoint", curve.Y.toString(), tc);
            pnt.setCoordinates(Const.COORDS_BY_USER, [curve.X(ta, true), curve.Y(ta, true)], false);
            return [pnt.scrCoords, ta];
        },

        /**
         * Investigate a function term at the bounds of intervals where
         * the function is not defined, e.g. log(x) at x = 0.
         *
         * c is inbetween a and b
         *
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Array} a Screen coordinates of the left interval bound
         * @param {Array} b Screen coordinates of the right interval bound
         * @param {Array} c Screen coordinates of the bisection point at (ta + tb) / 2
         * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates
         * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates
         * @param {Number} tc (ta + tb) / 2 = tc. Parameter which evaluates to b, i.e. [1, X(tc), Y(tc)] = c in screen coordinates
         * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0.
         * @returns {JXG.Boolean} true if the point is inserted and the recursion should stop, false otherwise.
         *
         * @private
         */
        _getBorderPos: function(curve, ta, a, tc, c, tb, b) {
            var t, pnt, p,
                j,
                max_it = 30,
                is_undef = false,
                t_real2,
                t_good, t_bad;

            pnt = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false);
            j = 0;
            // Bisect a, b and c until the point t_real is inside of the definition interval
            // and as close as possible at the boundary.
            // t_real2 is the second closest point.
            // There are four cases:
            //  a  |  c  |  b
            // ---------------
            // inf | R   | R
            // R   | R   | inf
            // inf | inf | R
            // R   | inf | inf
            //
            if (isNaN(a[1] + a[2]) && !isNaN(c[1] + c[2])) {
                t_bad = ta;
                t_good = tc;
                t_real2 = tb;
            } else if (isNaN(b[1] + b[2]) && !isNaN(c[1] + c[2])) {
                t_bad = tb;
                t_good = tc;
                t_real2 = ta;
            } else if (isNaN(c[1] + c[2]) && !isNaN(b[1] + b[2])) {
                t_bad = tc;
                t_good = tb;
                t_real2 = tb + (tb - tc);
            } else if (isNaN(c[1] + c[2]) && !isNaN(a[1] + a[2])) {
                t_bad = tc;
                t_good = ta;
                t_real2 = ta - (tc - ta);
            } else {
                return false;
            }
            do {
                t = 0.5 * (t_good + t_bad);
                pnt.setCoordinates(Const.COORDS_BY_USER, [curve.X(t, true), curve.Y(t, true)], false);
                p = pnt.usrCoords;
                is_undef = isNaN(p[1] + p[2]);
                if (is_undef) {
                    t_bad = t;
                } else {
                    t_real2 = t_good;
                    t_good = t;
                }
                ++j;
            } while (j < max_it && Math.abs(t_good - t_bad) > Mat.eps);
            return t;
        },

        /**
         *
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Number} ta
         * @param {Number} tb
         */
        _getCuspPos: function(curve, ta, tb) {
            var a = [curve.X(ta, true), curve.Y(ta, true)],
                b = [curve.X(tb, true), curve.Y(tb, true)],
                max_func = function(t) {
                    var c = [curve.X(t, true), curve.Y(t, true)];
                    return -(Math.sqrt((a[0] - c[0]) * (a[0] - c[0]) + (a[1] - c[1]) * (a[1] - c[1])) +
                            Math.sqrt((b[0] - c[0]) * (b[0] - c[0]) + (b[1] - c[1]) * (b[1] - c[1])));
                };

            return Numerics.fminbr(max_func, [ta, tb], curve);
        },

        /**
         *
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Number} ta
         * @param {Number} tb
         */
        _getJumpPos: function(curve, ta, tb) {
            var max_func = function(t) {
                    var e = Mat.eps * Mat.eps,
                        c1 = [curve.X(t, true), curve.Y(t, true)],
                        c2 = [curve.X(t + e, true), curve.Y(t + e, true)];
                    return -Math.abs( (c2[1] - c1[1]) / (c2[0] - c1[0]) );
                };

            return Numerics.fminbr(max_func, [ta, tb], curve);
        },

        /**
         *
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Number} t
         * @private
         */
        _getLimits: function(curve, t) {
            var res,
                step = 2 / (curve.maxX() - curve.minX()),
                x_l, x_r, y_l, y_r;

            // From left
            res = Extrapolate.limit(t, -step, curve.X);
            x_l = res[0];
            if (res[1] === 'infinite') {
                x_l = Math.sign(x_l) * Infinity;
            }

            res = Extrapolate.limit(t, -step, curve.Y);
            y_l = res[0];
            if (res[1] === 'infinite') {
                y_l = Math.sign(y_l) * Infinity;
            }

            // From right
            res = Extrapolate.limit(t, step, curve.X);
            x_r = res[0];
            if (res[1] === 'infinite') {
                x_r = Math.sign(x_r) * Infinity;
            }

            res = Extrapolate.limit(t, step, curve.Y);
            y_r = res[0];
            if (res[1] === 'infinite') {
                y_r = Math.sign(y_r) * Infinity;
            }

            return {
                    left_x: x_l,
                    left_y: y_l,
                    right_x: x_r,
                    right_y: y_r,
                    t: t
                };
        },

        /**
         *
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Array} a
         * @param {Number} tc
         * @param {Array} c
         * @param {Number} tb
         * @param {Array} b
         * @param {String} may_be_special
         * @param {Number} depth
         * @private
         */
        _getLimes: function(curve, ta, a, tc, c, tb, b, may_be_special, depth) {
            var t;

            if (may_be_special === 'border') {
                t = this._getBorderPos(curve, ta, a, tc, c, tb, b);
            } else if (may_be_special === 'cusp') {
                t = this._getCuspPos(curve, ta, tb);
            } else if (may_be_special === 'jump') {
                t = this._getJumpPos(curve, ta, tb);
            }
            return this._getLimits(curve, t);
        },

        /**
         * Recursive interval bisection algorithm for curve plotting.
         * Used in {@link JXG.Curve.updateParametricCurve}.
         * @private
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Array} a Screen coordinates of the left interval bound
         * @param {Number} ta Parameter which evaluates to a, i.e. [1, X(ta), Y(ta)] = a in screen coordinates
         * @param {Array} b Screen coordinates of the right interval bound
         * @param {Number} tb Parameter which evaluates to b, i.e. [1, X(tb), Y(tb)] = b in screen coordinates
         * @param {Number} depth Actual recursion depth. The recursion stops if depth is equal to 0.
         * @param {Number} delta If the distance of the bisection point at (ta + tb) / 2 from the point (a + b) / 2 is less then delta,
         *                 the segment [a,b] is regarded as straight line.
         * @returns {JXG.Curve} Reference to the curve object.
         */
        _plotNonRecursive: function (curve, a, ta, b, tb, d) {
            var tc, c, ds,
                mindepth = 0,
                limes = null,
                a_nan, b_nan,
                isSmooth = false,
                may_be_special = '',
                x, y, oc, depth, ds0,
                stack = [],
                stack_length = 0,
                item;

            oc = curve.board.origin.scrCoords;
            stack[stack_length++] = [a, ta, b, tb, d, Infinity];
            while (stack_length > 0) {
                // item = stack.pop();
                item = stack[--stack_length];
                a = item[0];
                ta = item[1];
                b = item[2];
                tb = item[3];
                depth = item[4];
                ds0 = item[5];

                isSmooth = false;
                may_be_special = '';
                limes = null;
                //console.log(stack.length, item)

                if (curve.points.length > 65536) {
                    return;
                }

                if (depth < this.nanLevel) {
                    // Test if the function is undefined in the whole interval [ta, tb]
                    if (this._isUndefined(curve, a, ta, b, tb)) {
                        continue;
                    }
                    // Test if the graph is far outside the visible are for the interval [ta, tb]
                    if (this._isOutside(a, ta, b, tb, curve.board)) {
                        continue;
                    }
                }

                tc = (ta  + tb) * 0.5;

                // Screen coordinates of point at tc
                x = curve.X(tc, true);
                y = curve.Y(tc, true);
                c = [1, oc[1] + x * curve.board.unitX, oc[2] - y * curve.board.unitY];
                ds = this._triangleDists(a, b, c);           // returns [d_ab, d_ac, d_cb, d_cd]

                a_nan = isNaN(a[1] + a[2]);
                b_nan = isNaN(b[1] + b[2]);
                if ((a_nan && !b_nan) || (!a_nan && b_nan)) {
                    may_be_special = 'border';
                } else if (ds[0] > 0.66 * ds0 ||
                            ds[0] < this.cusp_threshold * (ds[1] + ds[2]) ||
                            ds[1] > 5 * ds[2] ||
                            ds[2] > 5 * ds[1]) {
                    may_be_special = 'cusp';
                } else if ((ds[2] > this.jump_threshold * ds[0]) ||
                           (ds[1] > this.jump_threshold * ds[0]) ||
                            ds[0] === Infinity || ds[1] === Infinity || ds[2] === Infinity) {
                    may_be_special = 'jump';
                }
                isSmooth = (may_be_special === '' && depth < this.smoothLevel && ds[3] < this.smooth_threshold);

                if (depth < this.testLevel && !isSmooth) {
                    if (may_be_special === '') {
                        isSmooth = true;
                    } else {
                        limes = this._getLimes(curve, ta, a, tc, c, tb, b, may_be_special, depth);
                    }
                }

                if (limes !== null) {
                    c = [1, NaN, NaN];
                    this._insertPoint(curve, c, tc, depth, limes);
                } else if (depth <= mindepth || isSmooth) {
                    this._insertPoint(curve, c, tc, depth, null);
                } else {
                    stack[stack_length++] = [c, tc, b, tb, depth - 1, ds[0]];
                    stack[stack_length++] = [a, ta, c, tc, depth - 1, ds[0]];
                }
            }

            return this;
        },

        /**
         * Updates the data points of a parametric curve. This version is used if {@link JXG.Curve#plotVersion} is <tt>3</tt>.
         * This is an experimental plot version, <b>not recommended</b> to be used.
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Number} mi Left bound of curve
         * @param {Number} ma Right bound of curve
         * @returns {JXG.Curve} Reference to the curve object.
         */
        updateParametricCurve_v3: function (curve, mi, ma) {
            var ta, tb, a, b,
                suspendUpdate = false,
                pa = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false),
                pb = new Coords(Const.COORDS_BY_USER, [0, 0], curve.board, false),
                depth,
                w2, // h2,
                bbox,
                ret_arr;

            // console.log("-----------------------------------------------------------");
            // console.time("plot");
            if (curve.board.updateQuality === curve.board.BOARD_QUALITY_LOW) {
                depth = Type.evaluate(curve.visProp.recursiondepthlow) || 14;
            } else {
                depth = Type.evaluate(curve.visProp.recursiondepthhigh) || 17;
            }

            // smoothLevel has to be small for graphs in a huge interval.
            this.smoothLevel = 7; //depth - 10;
            this.nanLevel = depth - 4;
            this.testLevel = 4;
            this.cusp_threshold = 0.5;
            this.jump_threshold = 0.99;
            this.smooth_threshold = 2;

            curve.points = [];

            if (curve.xterm === 'x') {
                // For function graphs we can restrict the plot interval
                // to the visible area +plus margin
                bbox = curve.board.getBoundingBox();
                w2 = (bbox[2] - bbox[0]) * 0.3;
                //h2 = (bbox[1] - bbox[3]) * 0.3;
                ta = Math.max(mi, bbox[0] - w2);
                tb = Math.min(ma, bbox[2] + w2);
            } else {
                ta = mi;
                tb = ma;
            }
            pa.setCoordinates(Const.COORDS_BY_USER, [curve.X(ta, suspendUpdate), curve.Y(ta, suspendUpdate)], false);

            // The first function calls of X() and Y() are done. We can now
            // switch `suspendUpdate` on. If supported by the functions, this
            // avoids for the rest of the plotting algorithm, evaluation of any
            // parent elements.
            suspendUpdate = true;

            pb.setCoordinates(Const.COORDS_BY_USER, [curve.X(tb, suspendUpdate), curve.Y(tb, suspendUpdate)], false);

            // Find start and end points of the visible area (plus a certain margin)
            ret_arr = this._findStartPoint(curve, pa.scrCoords, ta, pb.scrCoords, tb);
            pa.setCoordinates(Const.COORDS_BY_SCREEN, ret_arr[0], false);
            ta = ret_arr[1];
            ret_arr = this._findStartPoint(curve, pb.scrCoords, tb, pa.scrCoords, ta);
            pb.setCoordinates(Const.COORDS_BY_SCREEN, ret_arr[0], false);
            tb = ret_arr[1];

            // Save the visible area.
            // This can be used in Curve.hasPoint().
            this._visibleArea = [ta, tb];

            // Start recursive plotting algorithm
            a = pa.copy('scrCoords');
            b = pb.copy('scrCoords');
            pa._t = ta;
            curve.points.push(pa);
            this._lastScrCrds = pa.copy('scrCoords');   // Used in _insertPoint
            this._lastUsrCrds = pa.copy('usrCoords');   // Used in _insertPoint

            this._plotNonRecursive(curve, a, ta, b, tb, depth);

            pb._t = tb;
            curve.points.push(pb);

            curve.numberPoints = curve.points.length;
            // console.timeEnd("plot");
            // console.log("number of points:", this.numberPoints);

            return curve;
        },

        //----------------------------------------------------------------------
        // Plot algorithm v4
        //----------------------------------------------------------------------

        _criticalInterval: function(vec, le, level) {
            var i, j, le1, med,
                sgn, sgnChange,
                isGroup   = false,
                abs_vec,
                last = -Infinity,
                very_small = false,
                smooth    = false,
                group     = 0,
                groups    = [],
                types     = [],
                positions = [];

            abs_vec = Statistics.abs(vec);
            med = Statistics.median(abs_vec);

            if (med < 1.0e-7) {
                med = 1.0e-7;
                very_small = true;
            } else {
                med *= this.criticalThreshold;
            }

            //console.log("Median", med);
            for (i = 0; i < le; i++) {
                // Start a group if not yet done and
                // add position to group
                if (abs_vec[i] > med /*&& abs_vec[i] > 0.01*/)  {
                    positions.push({i: i, v: vec[i], group: group});
                    last = i;
                    if (!isGroup) {
                        isGroup = true;
                    }
                } else {
                    if (isGroup && i > last + 4) {
                        // End the group
                        if (positions.length > 0) {
                            groups.push(positions.slice(0));
                        }
                        positions = [];
                        isGroup = false;
                        group++;
                    }
                }
            }
            if (isGroup) {
                if (positions.length > 1) {
                    groups.push(positions.slice(0));
                }
            }

            if (very_small && groups.length === 0) {
                smooth = true;
            }

            // Decide if there is a singular critical point
            // or if a whole interval is problematic.
            // The latter is the case if the differences have many sign changes.
            for (j = 0; j < groups.length; j++) {
                types[j] = 'point';
                le1 = groups[j].length;
                if (le1 < 64) {
                    continue;
                }
                sgnChange = 0;
                sgn = Math.sign(groups[j][0].v);
                for (i = 1; i < le1; i++) {
                    if (Math.sign(groups[j][i].v) !== sgn) {
                        sgnChange++;
                        sgn = Math.sign(groups[j][i].v);
                    }
                }
                if (sgnChange * 6 > le1) {
                    types[j] = 'interval';
                }
            }

            return {smooth: smooth, groups: groups, types: types};
        },

        Component: function() {
            this.left_isNaN =  false;
            this.right_isNaN = false;
            this.left_t = null;
            this.right_t = null;
            this.t_values = [];
            this.x_values = [];
            this.y_values = [];
            this.len = 0;
        },

        findComponents: function(curve, mi, ma, steps) {
            var i, t, le, h, x, y,
                components = [],
                comp,
                comp_nr = 0,
                cnt = 0,
                cntNaNs = 0,
                comp_started = false,
                suspended = false;

            h = (ma - mi) / steps;
            components[comp_nr] = new this.Component();
            comp = components[comp_nr];

            for (i = 0, t = mi; i <= steps; i++, t += h) {
                x = curve.X(t, suspended);
                y = curve.Y(t, suspended);

                if (isNaN(x) || isNaN(y)) {
                    cntNaNs++;
                    // Wait for - at least - two consecutive NaNs
                    // This avoids starting a new component if
                    // the function value has infinity as intermediate value.
                    if (cntNaNs > 1 && comp_started) {
                        // Finalize a component
                        comp.right_isNaN = true;
                        comp.right_t = t - h;
                        comp.len = cnt;

                        // Prepare a new component
                        comp_started = false;
                        comp_nr++;
                        components[comp_nr] =  new this.Component();
                        comp = components[comp_nr];
                        cntNaNs = 0;
                    }
                } else {
                    // Now there is a non-NaN entry.
                    if (!comp_started) {
                        // Start the component
                        comp_started = true;
                        cnt = 0;
                        if (cntNaNs > 0) {
                            comp.left_t = t - h;
                            comp.left_isNaN = true;
                        }
                    }
                    cntNaNs = 0;
                    // Add the value to the component
                    comp.t_values[cnt] = t;
                    comp.x_values[cnt] = x;
                    comp.y_values[cnt] = y;
                    cnt++;
                }
                if (i === 0) {
                    suspended = true;
                }
            }
            if (comp_started) {
                comp.len = cnt;
            } else {
                components.pop();
            }

            return components;
        },

        getPointType: function(curve, pos, t_approx, t_values, x_table, y_table, len) {
            var x_values = x_table[0],
                y_values = y_table[0],
                full_len = t_values.length,
                result = {
                    idx: pos,
                    t: t_approx, //t_values[pos],
                    x: x_values[pos],
                    y: y_values[pos],
                    type: 'other'
                };

            if (pos < 5) {
                result.type = 'borderleft';
                result.idx = 0;
                result.t = t_values[0];
                result.x = x_values[0];
                result.y = y_values[0];

                // console.log('Border left', result.t);
                return result;
            }
            if (pos > len - 6) {
                result.type = 'borderright';
                result.idx = full_len - 1;
                result.t = t_values[full_len - 1];
                result.x = x_values[full_len - 1];
                result.y = y_values[full_len - 1];

                // console.log('Border right', result.t, full_len - 1);
                return result;
            }

            return result;
        },

        newtonApprox: function(idx, t, h, level, table) {
            var i, s = 0.0;
            for (i = level; i > 0; i--) {
                s = (s + table[i][idx]) * (t - (i - 1) * h) / i;
            }
            return s + table[0][idx];
        },

        thiele: function(t, recip, t_values, idx, degree) {
            var i, v = 0.0;
            for (i = degree; i > 1; i--) {
                v = (t - t_values[idx + i]) / (recip[i][idx + 1] - recip[i - 2][idx + 1] + v);
            }
            return recip[0][idx + 1] + (t - t_values[idx + 1]) / (recip[1][idx + 1] + v);
        },

        differenceMethodExperiments: function(component, curve) {
            var i, level, le, up,
                t_values = component.t_values,
                x_values = component.x_values,
                y_values = component.y_values,
                x_diffs = [],
                y_diffs = [],
                x_slopes = [],
                y_slopes = [],
                x_table = [],
                y_table = [],
                x_recip = [],
                y_recip = [],
                h, numerator,
                // x_med, y_med,
                foundCriticalPoint = 0,
                pos, ma, j, v,
                groups,
                criticalPoints = [];

            h = t_values[1] - t_values[0];
            x_table.push([]);
            y_table.push([]);
            x_recip.push([]);
            y_recip.push([]);
            le = y_values.length;
            for (i = 0; i < le; i++) {
                x_table[0][i] = x_values[i];
                y_table[0][i] = y_values[i];
                x_recip[0][i] = x_values[i];
                y_recip[0][i] = y_values[i];
            }

            x_table.push([]);
            y_table.push([]);
            x_recip.push([]);
            y_recip.push([]);
            numerator = h;
            le = y_values.length - 1;
            for (i = 0; i < le; i++) {
                x_diffs[i] = x_values[i + 1] - x_values[i];
                y_diffs[i] = y_values[i + 1] - y_values[i];
                x_slopes[i] = x_diffs[i];
                y_slopes[i] = y_diffs[i];
                x_table[1][i] = x_diffs[i];
                y_table[1][i] = y_diffs[i];
                x_recip[1][i] = numerator / x_diffs[i];
                y_recip[1][i] = numerator / y_diffs[i];
            }
            le--;

            up = Math.min(8, y_values.length - 1);
            for (level = 1; level < up; level++) {
                x_table.push([]);
                y_table.push([]);
                x_recip.push([]);
                y_recip.push([]);
                numerator *= h;
                for (i = 0; i < le; i++) {
                    x_diffs[i] = x_diffs[i + 1] - x_diffs[i];
                    y_diffs[i] = y_diffs[i + 1] - y_diffs[i];
                    x_table[level + 1][i] = x_diffs[i];
                    y_table[level + 1][i] = y_diffs[i];
                    x_recip[level + 1][i] = numerator / (x_recip[level][i + 1] - x_recip[level][i]) + x_recip[level - 1][i + 1];
                    y_recip[level + 1][i] = numerator / (y_recip[level][i + 1] - y_recip[level][i]) + y_recip[level - 1][i + 1];
                }

                // if (level == 1) {
                //     console.log("bends level=", level, y_diffs.toString());
                // }

                // Store point location which may be centered around
                // critical points.
                // If the lebvel is suitable, step out of the loop.
                groups = this._criticalPoints(y_diffs, le, level);
                if (groups === false) {
                    // Its seems, the degree of the polynomial is equal to level
console.log("Polynomial of degree", level);
                    groups = [];
                    break;
                }
                if (groups.length > 0) {
                    foundCriticalPoint++;
                    if (foundCriticalPoint > 1 && level % 2 === 0) {
                        break;
                    }
                }
                le--;
            }

            // console.log("Last diffs", y_diffs, "level", level);

            // Analyze the groups which have been found.
            for (i = 0; i < groups.length; i++) {
                // console.log("Group", i, groups[i])
                // Identify the maximum difference, i.e. the center of the "problem"
                ma = -Infinity;
                for (j = 0; j < groups[i].length; j++) {
                    v = Math.abs(groups[i][j].v);
                    if (v > ma) {
                        ma = v;
                        pos = j;
                    }
                }
                pos = Math.floor(groups[i][pos].i + level / 2);
                // Analyze the critical point
                criticalPoints.push(this.getPointType(curve, pos, t_values, x_values, y_values, x_slopes, y_slopes, le + 1));
            }

            return [criticalPoints, x_table, y_table, x_recip, y_recip];

        },

        getCenterOfCriticalInterval: function(group, degree, t_values) {
            var ma, j, pos, v,
                num = 0.0,
                den = 0.0,
                h = t_values[1] - t_values[0],
                pos_mean,
                range = [];

            // Identify the maximum difference, i.e. the center of the "problem"
            // If there are several equal maxima, store the positions
            // in the array range and determine the center of the array.

            ma = -Infinity;
            range = [];
            for (j = 0; j < group.length; j++) {
                v = Math.abs(group[j].v);
                if (v > ma) {
                    range = [j];
                    ma = v;
                    pos = j;
                } else if (ma === v) {
                    range.push(j);
                }
            }
            if (range.length > 0) {
                pos_mean = range.reduce(function(total, val) { return total + val; }, 0) / range.length;
                pos = Math.floor(pos_mean);
                pos_mean += group[0].i;
            }

            if (ma < Infinity) {
                for (j = 0; j < group.length; j++) {
                    num += Math.abs(group[j].v) * group[j].i;
                    den += Math.abs(group[j].v);
                }
                pos_mean = num / den;
            }
            pos_mean += degree / 2;
            return [group[pos].i + degree / 2, pos_mean, t_values[Math.floor(pos_mean)] + h * (pos_mean - Math.floor(pos_mean))];
        },

        differenceMethod: function(component, curve) {
            var i, level, le, up,
                t_values = component.t_values,
                x_values = component.x_values,
                y_values = component.y_values,
                x_table = [],
                y_table = [],
                foundCriticalPoint = 0,
                degree_x = -1,
                degree_y = -1,
                pos, res, res_x, res_y, t_approx,
                groups = [],
                types,
                criticalPoints = [];

            le = y_values.length;
            // x_table.push([]);
            // y_table.push([]);
            // for (i = 0; i < le; i++) {
            //     x_table[0][i] = x_values[i];
            //     y_table[0][i] = y_values[i];
            // }
            x_table.push(new Float64Array(x_values));
            y_table.push(new Float64Array(y_values));

            le--;
            up = Math.min(12, le);
            for (level = 0; level < up; level++) {
                // Old style method:
                // x_table.push([]);
                // y_table.push([]);
                // for (i = 0; i < le; i++) {
                //     x_table[level + 1][i] = x_table[level][i + 1] - x_table[level][i];
                //     y_table[level + 1][i] = y_table[level][i + 1] - y_table[level][i];
                // }
                // New method:
                x_table.push(new Float64Array(le));
                y_table.push(new Float64Array(le));
                x_table[level + 1] = x_table[level].map(function(v, idx, arr) { return arr[idx + 1] - v;});
                y_table[level + 1] = y_table[level].map(function(v, idx, arr) { return arr[idx + 1] - v;});

                // Store point location which may be centered around critical points.
                // If the level is suitable, step out of the loop.
                res_y = this._criticalInterval(y_table[level + 1], le, level);
                if (res_y.smooth === true) {
                    // Its seems, the degree of the polynomial is equal to level
                    // If the values in level + 1 are zero, it might be a polynomial of degree level.
                    // Seems to work numerically stable until degree 6.
                    degree_y = level;
                    groups = [];
                }
                res_x = this._criticalInterval(x_table[level + 1], le, level);
                if (degree_x === -1 && res_x.smooth === true) {
                    // Its seems, the degree of the polynomial is equal to level
                    // If the values in level + 1 are zero, it might be a polynomial of degree level.
                    // Seems to work numerically stable until degree 6.
                    degree_x = level;
                }
                if (degree_y >= 0) {
                    break;
                }

                if (res_y.groups.length > 0) {
                    foundCriticalPoint++;
                    if (foundCriticalPoint > 2 && (level + 1) % 2 === 0) {
                        groups = res_y.groups;
                        types = res_y.types;
                        break;
                    }
                }
                le--;
            }

            // console.log("Last diffs", y_table[Math.min(level + 1, up)], "level", level + 1);
            // Analyze the groups which have been found.
            for (i = 0; i < groups.length; i++) {
                if (types[i] === 'interval') {
                    continue;
                }
                // console.log("Group", i, groups[i], types[i], level + 1)
                res = this.getCenterOfCriticalInterval(groups[i], level + 1, t_values);
                pos = res_y[0];
                pos = Math.floor(res[1]);
                t_approx = res[2];
                // console.log("Critical points:", groups, res, pos)

                // Analyze the type of the critical point
                // Result is of type 'borderleft', borderright', 'other'
                criticalPoints.push(this.getPointType(curve, pos, t_approx, t_values, x_table, y_table, le + 1));
            }

            // if (level === up) {
            //     console.log("No convergence!");
            // } else {
            //     console.log("Convergence level", level);
            // }
            return [criticalPoints, x_table, y_table, degree_x, degree_y];

        },

        _insertPoint_v4: function (curve, crds, t, doLog) {
            var p,
                prev = null,
                x, y,
                near = 0.8;

            if (curve.points.length > 0) {
                prev = curve.points[curve.points.length - 1].scrCoords;
            }

            // Add regular point
            p = new Coords(Const.COORDS_BY_USER, crds, curve.board);

            if (prev !== null) {
                x = p.scrCoords[1] - prev[1];
                y = p.scrCoords[2] - prev[2];
                if (x * x + y * y < near * near) {
                // Math.abs(p.scrCoords[1] - prev[1]) < near &&
                // Math.abs(p.scrCoords[2] - prev[2]) < near) {
                    return;
                }
            }

            p._t = t;
            curve.points.push(p);
        },

        getInterval: function(curve, ta, tb) {
            var t_int, x_int, y_int;

            //console.log('critical point', ta, tb);
            IntervalArithmetic.disable();

            t_int = IntervalArithmetic.Interval(ta, tb);
            curve.board.mathLib = IntervalArithmetic;
            curve.board.mathLibJXG = IntervalArithmetic;
            x_int = curve.X(t_int, true);
            y_int = curve.Y(t_int, true);
            curve.board.mathLib = Math;
            curve.board.mathLibJXG = JXG.Math;

            //console.log(x_int, y_int);
            return y_int;
        },

        sign: function (v) {
            if (v < 0) { return -1; }
            if (v > 0) { return 1; }
            return 0;
        },

        handleBorder: function(curve, comp, group, x_table, y_table) {
            var idx = group.idx,
                t, t1, t2,
                size = 32,
                y_int,
                x, y,
                lo, hi, i,
                components2, le, h;

            // console.log("HandleBorder at t =", t_approx);
            // console.log("component:", comp)
            // console.log("Group:", group);

            h = comp.t_values[1] - comp.t_values[0];
            if (group.type === 'borderleft') {
                t = comp.left_isNaN ? comp.left_t : group.t - h;
                t1 = t;
                t2 = t1 + h;
            } else if (group.type === 'borderright') {
                t = comp.right_isNaN ? comp.right_t : group.t + h;
                t2 = t;
                t1 = t2 - h;
            } else {
                console.log("No bordercase!!!");
            }

            components2 = this.findComponents(curve, t1, t2, size);
            if (components2.length === 0) {
                return;
            }
            if (group.type === 'borderleft') {
                t1 = components2[0].left_t;
                t2 = components2[0].t_values[0];
                h = components2[0].t_values[1] - components2[0].t_values[0];
                t1 = (t1 === null) ? t2- h : t1;
                t = t1;
                y_int = this.getInterval(curve, t1, t2);
                if (Type.isObject(y_int)) {
                    lo = y_int.lo;
                    hi = y_int.hi;

                    x = curve.X(t, true);
                    y = (y_table[1][idx] < 0) ? hi : lo;
                    this._insertPoint_v4(curve, [1, x, y], t);
                }
            }

            le = components2[0].t_values.length;
            for (i = 0; i < le; i++) {
                t = components2[0].t_values[i];
                x = components2[0].x_values[i];
                y = components2[0].y_values[i];
                this._insertPoint_v4(curve, [1, x, y], t);
            }

            if (group.type === 'borderright') {
                t1 = components2[0].t_values[le - 1];
                t2 = components2[0].right_t;
                h = components2[0].t_values[1] - components2[0].t_values[0];
                t2 = (t2 === null) ? t1 + h : t2;

                t = t2;
                y_int = this.getInterval(curve, t1, t2);
                if (Type.isObject(y_int)) {
                    lo = y_int.lo;
                    hi = y_int.hi;
                    x = curve.X(t, true);
                    y = (y_table[1][idx] > 0) ? hi : lo;
                    this._insertPoint_v4(curve, [1, x, y], t);
                }
            }

        },

        _seconditeration_v4: function(curve, comp, group, x_table, y_table) {
            var i, t1, t2, ret,
                components2, comp2, idx, groups2, g,
                x_table2, y_table2, start, le;

            // Look at two points, hopefully left and right from the critical point
            t1 = comp.t_values[group.idx - 2];
            t2 = comp.t_values[group.idx + 2];
            components2 = this.findComponents(curve, t1, t2, 64);
            for (idx = 0; idx < components2.length; idx++) {
                comp2 = components2[idx];
                ret = this.differenceMethod(comp2, curve);
                groups2 = ret[0];
                x_table2 = ret[1];
                y_table2 = ret[2];
                start = 0;
                for (g = 0; g <= groups2.length; g++) {
                    if (g === groups2.length) {
                        le = comp2.len;
                    } else {
                        le = groups2[g].idx;
                    }

                    // Insert all uncritical points until next critical point
                    for (i = start; i < le; i++) {
                        if (!isNaN(comp2.x_values[i]) && !isNaN(comp2.y_values[i])) {
                            this._insertPoint_v4(curve, [1, comp2.x_values[i], comp2.y_values[i]], comp2.t_values[i]);
                        }
                    }
                    // Handle next critical point
                    if (g < groups2.length) {
                        this.handleSingularity(curve, comp2, groups2[g], x_table2, y_table2);
                        start = groups2[g].idx + 1;
                    }
                }
                le = comp2.len;
                if (idx < components2.length - 1) {
                    this._insertPoint_v4(curve, [1, NaN, NaN], comp2.right_t);
                }
            }
            return this;
        },

        _recurse_v4: function(curve, t1, t2, x1, y1, x2, y2, level) {
            var tol = 2,
                t = (t1 + t2) * 0.5,
                x = curve.X(t, true),
                y = curve.Y(t, true),
                dx, dy;

            //console.log("Level", level)
            if (level === 0) {
                this._insertPoint_v4(curve, [1, NaN, NaN], t);
                return;
            }
            // console.log("R", t1, t2)
            dx = (x - x1) * curve.board.unitX;
            dy = (y - y1) * curve.board.unitY;
            // console.log("D1", Math.sqrt(dx * dx + dy * dy))
            if (Math.sqrt(dx * dx + dy * dy) > tol) {
                this._recurse_v4(curve, t1, t, x1, y1, x, y, level - 1);
            } else {
                this._insertPoint_v4(curve, [1, x, y], t);
            }
            dx = (x - x2) * curve.board.unitX;
            dy = (y - y2) * curve.board.unitY;
            // console.log("D2", Math.sqrt(dx * dx + dy * dy), x-x2, y-y2)
            if (Math.sqrt(dx * dx + dy * dy) > tol) {
                this._recurse_v4(curve, t, t2, x, y, x2, y2, level - 1);
            } else {
                this._insertPoint_v4(curve, [1, x, y], t);
            }
        },

        handleSingularity: function(curve, comp, group, x_table, y_table) {
            var idx = group.idx,
                t, t1, t2, y_int,
                i1, i2,
                x, y, lo, hi,
                d_lft, d_rgt,
                d_thresh = 100,
                di1 = 5,
                di2 = 3,
                d1, d2;

            t = group.t;
            console.log("HandleSingularity at t =", t);
            // console.log(comp.t_values[idx - 1], comp.y_values[idx - 1], comp.t_values[idx + 1], comp.y_values[idx + 1]);
            // console.log(group);

            // Look at two points, hopefully left and right from the critical point
            t1 = comp.t_values[idx - di1];
            t2 = comp.t_values[idx + di1];

            y_int = this.getInterval(curve, t1, t2);
            if (Type.isObject(y_int)) {
                lo = y_int.lo;
                hi = y_int.hi;
            } else {
                if (y_table[0][idx - 1] < y_table[0][idx + 1]) {
                    lo = y_table[0][idx - 1];
                    hi = y_table[0][idx + 1];
                } else {
                    lo = y_table[0][idx + 1];
                    hi = y_table[0][idx - 1];
                }
            }

            x = curve.X(t, true);

            d_lft = (y_table[0][idx - di2] - y_table[0][idx - di1]) / (comp.t_values[idx - di2] - comp.t_values[idx - di1]);
            d_rgt = (y_table[0][idx + di2] - y_table[0][idx + di1]) / (comp.t_values[idx + di2] - comp.t_values[idx + di1]);

            console.log(":::", d_lft, d_rgt);

            //this._insertPoint_v4(curve, [1, NaN, NaN], 0);

            if (d_lft < -d_thresh) {
                // Left branch very steep downwards -> add the minimum
                this._insertPoint_v4(curve, [1, x, lo], t, true);
                if (d_rgt <= d_thresh) {
                    // Right branch not very steep upwards -> interrupt the curve
                    // I.e. it looks like -infty / (finite or infty) and not like -infty / -infty
                    this._insertPoint_v4(curve, [1, NaN, NaN], t);
                }
            } else if (d_lft > d_thresh) {
                // Left branch very steep upwards -> add the maximum
                this._insertPoint_v4(curve, [1, x, hi], t);
                if (d_rgt >= -d_thresh) {
                    // Right branch not very steep downwards -> interrupt the curve
                    // I.e. it looks like infty / (finite or -infty) and not like infty / infty
                    this._insertPoint_v4(curve, [1, NaN, NaN], t);
                }
            } else {
                if (lo === -Infinity) {
                    this._insertPoint_v4(curve, [1, x, lo], t, true);
                    this._insertPoint_v4(curve, [1, NaN, NaN], t);
                }
                if (hi === Infinity) {
                    this._insertPoint_v4(curve, [1, NaN, NaN], t);
                    this._insertPoint_v4(curve, [1, x, hi], t, true);
                }

                if (group.t < comp.t_values[idx]) {
                    i1 = idx - 1;
                    i2 = idx;
                } else {
                    i1 = idx;
                    i2 = idx + 1;
                }
                t1 = comp.t_values[i1];
                t2 = comp.t_values[i2];
                this._recurse_v4(curve, t1, t2,
                        x_table[0][i1],
                        y_table[0][i1],
                        x_table[0][i2],
                        y_table[0][i2],
                        10
                    );

                // x = (x_table[0][idx] - x_table[0][idx - 1]) * curve.board.unitX;
                // y = (y_table[0][idx] - y_table[0][idx - 1]) * curve.board.unitY;
                // d1 = Math.sqrt(x * x + y * y);
                // x = (x_table[0][idx + 1] - x_table[0][idx]) * curve.board.unitX;
                // y = (y_table[0][idx + 1] - y_table[0][idx]) * curve.board.unitY;
                // d2 = Math.sqrt(x * x + y * y);

                // console.log("end", t1, t2, t);
                // if (true || (d1 > 2 || d2 > 2)) {

// console.log(d1, d2, y_table[0][idx])
//                     // Finite jump
//                     this._insertPoint_v4(curve, [1, NaN, NaN], t);
//                 } else {
//                     if (lo !== -Infinity && hi !== Infinity) {
//                         // Critical point which can be ignored
//                         this._insertPoint_v4(curve, [1, x_table[0][idx], y_table[0][idx]], comp.t_values[idx]);
//                     } else {
//                         if (lo === -Infinity) {
//                             this._insertPoint_v4(curve, [1, x, lo], t, true);
//                             this._insertPoint_v4(curve, [1, NaN, NaN], t);
//                         }
//                         if (hi === Infinity) {
//                             this._insertPoint_v4(curve, [1, NaN, NaN], t);
//                             this._insertPoint_v4(curve, [1, x, hi], t, true);
//                         }
//                     }
                // }
            }
            if (d_rgt < -d_thresh) {
                // Right branch very steep downwards -> add the maximum
                this._insertPoint_v4(curve, [1, x, hi], t);
            } else if (d_rgt > d_thresh) {
                // Right branch very steep upwards -> add the minimum
                this._insertPoint_v4(curve, [1, x, lo], t);
            }

        },

        /**
         * Number of equidistant points where the function is evaluated
         */
        steps: 1021, //2053, // 1021,

        /**
         * If the absolute maximum of the set of differences is larger than
         * criticalThreshold * median of these values, it is regarded as critical point.
         * @see JXG.Math.Plot#_criticalInterval
         */
        criticalThreshold: 1000,

        plot_v4: function(curve, ta, tb, steps) {
            var i, j, le, components, idx, comp,
                groups, g, start,
                ret, x_table, y_table,
                t, t1, t2,
                good, bad,
                x_int, y_int,
                degree_x, degree_y,
                h  = (tb - ta) / steps,
                Ypl = function(x) { return curve.Y(x, true); },
                Ymi = function(x) { return -curve.Y(x, true); },
                h2 = h * 0.5;

            components = this.findComponents(curve, ta, tb, steps);
            for (idx = 0; idx < components.length; idx++) {
                comp = components[idx];
                ret = this.differenceMethod(comp, curve);
                groups = ret[0];
                x_table = ret[1];
                y_table = ret[2];
                degree_x = ret[3];
                degree_y = ret[4];

                // if (degree_x >= 0) {
                //     console.log("x polynomial of degree", degree_x);
                // }
                // if (degree_y >= 0) {
                //     console.log("y polynomial of degree", degree_y);
                // }
                if (groups.length === 0 || groups[0].type !== 'borderleft') {
                    groups.unshift({
                        idx: 0,
                        t: comp.t_values[0],
                        x: comp.x_values[0],
                        y: comp.y_values[0],
                        type: 'borderleft'
                    });
                }
                if (groups[groups.length - 1].type !== 'borderright') {
                    le = comp.t_values.length;
                    groups.push({
                        idx: le - 1,
                        t: comp.t_values[le - 1],
                        x: comp.x_values[le - 1],
                        y: comp.y_values[le - 1],
                        type: 'borderright'
                    });
                }


                start = 0;
                for (g = 0; g <= groups.length; g++) {
                    if (g === groups.length) {
                        le = comp.len;
                    } else {
                        le = groups[g].idx - 1;
                    }

                    good = 0;
                    bad = 0;
                    // Insert all uncritical points until next critical point
                    for (i = start; i < le - 2; i++) {
                        this._insertPoint_v4(curve, [1, comp.x_values[i], comp.y_values[i]], comp.t_values[i]);
                        j = Math.max(0, i - 2);
                        // Add more points in critical intervals
                        if (true &&
                            //degree_y === -1 && // No polynomial
                            i >= start + 3 &&
                            i < le - 3 &&               // Do not do this if too close to a critical point
                            y_table.length > 3 &&
                            Math.abs(y_table[2][i]) > 0.2 * Math.abs(y_table[0][i])) {
                            t = comp.t_values[i];
                            h2 = h * 0.25;
                            y_int = this.getInterval(curve, t, t + h);
                            if (Type.isObject(y_int)) {
                                if (y_table[2][i] > 0) {
                                    this._insertPoint_v4(curve, [1, t + h2, y_int.lo], t + h2);
                                } else {
                                    this._insertPoint_v4(curve, [1, t + h - h2, y_int.hi], t + h - h2);
                                }
                            } else {
                                t1 = Numerics.fminbr(Ypl, [t, t + h]);
                                t2 = Numerics.fminbr(Ymi, [t, t + h]);
                                if (t1 < t2) {
                                    this._insertPoint_v4(curve, [1, curve.X(t1, true), curve.Y(t1, true)], t1);
                                    this._insertPoint_v4(curve, [1, curve.X(t2, true), curve.Y(t2, true)], t2);
                                } else {
                                    this._insertPoint_v4(curve, [1, curve.X(t2, true), curve.Y(t2, true)], t2);
                                    this._insertPoint_v4(curve, [1, curve.X(t1, true), curve.Y(t1, true)], t1);
                                }
                            }
                            bad++;
                        } else {
                            good++;
                        }
                    }
                    // console.log("GOOD", good, "BAD", bad);

                    // Handle next critical point
                    if (g < groups.length) {
                        //console.log("critical point / interval", groups[g]);

                        i = groups[g].idx;
                        if (groups[g].type === 'borderleft' || groups[g].type === 'borderright') {
                            this.handleBorder(curve, comp, groups[g], x_table, y_table);
                        } else {
                            this._seconditeration_v4(curve, comp, groups[g], x_table, y_table);
                        }

                        start = groups[g].idx + 1 + 1;
                    }
                }

                le = comp.len;
                if (idx < components.length - 1) {
                    this._insertPoint_v4(curve, [1, NaN, NaN], comp.right_t);
                }
            }


        },

        /**
         * Updates the data points of a parametric curve, plotVersion 4. This version is used if {@link JXG.Curve#plotVersion} is <tt>4</tt>.
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Number} mi Left bound of curve
         * @param {Number} ma Right bound of curve
         * @returns {JXG.Curve} Reference to the curve object.
         */
        updateParametricCurve_v4: function (curve, mi, ma) {
            var ta, tb, w2, bbox;

            if (curve.xterm === 'x') {
                // For function graphs we can restrict the plot interval
                // to the visible area +plus margin
                bbox = curve.board.getBoundingBox();
                w2 = (bbox[2] - bbox[0]) * 0.3;
                // h2 = (bbox[1] - bbox[3]) * 0.3;
                ta = Math.max(mi, bbox[0] - w2);
                tb = Math.min(ma, bbox[2] + w2);
            } else {
                ta = mi;
                tb = ma;
            }

            curve.points = [];

            //console.log("--------------------");
            this.plot_v4(curve, ta, tb, this.steps);

            curve.numberPoints = curve.points.length;
            //console.log(curve.numberPoints);
        },

        //----------------------------------------------------------------------
        // Plot algorithm alias
        //----------------------------------------------------------------------

        /**
         * Updates the data points of a parametric curve, alias for {@link JXG.Curve#updateParametricCurve_v2}.
         * This is needed for backwards compatibility, if this method has been
         * used directly in an application.
         * @param {JXG.Curve} curve JSXGraph curve element
         * @param {Number} mi Left bound of curve
         * @param {Number} ma Right bound of curve
         * @returns {JXG.Curve} Reference to the curve object.
         *
         * @see JXG.Curve#updateParametricCurve_v2
         */
        updateParametricCurve: function (curve, mi, ma) {
            return this.updateParametricCurve_v2(curve, mi, ma);
        }
    };


    return Mat.Plot;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.


    Metapost/Hobby curves, see e.g. https://bosker.wordpress.com/2013/11/13/beyond-bezier-curves/

    * Ported to Python for the project PyX. Copyright (C) 2011 Michael Schindler <m-schindler@users.sourceforge.net>
    * Ported to javascript from the PyX implementation (http://pyx.sourceforge.net/) by Vlad-X.
    * Adapted to JSXGraph and some code changes by Alfred Wassermann 2020.

    This program 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 General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

    Internal functions of MetaPost
    This file re-implements some of the functionality of MetaPost
    (http://tug.org/metapost). MetaPost was developed by John D. Hobby and
    others. The code of Metapost is in the public domain, which we understand as
    an implicit permission to reuse the code here (see the comment at
    http://www.gnu.org/licenses/license-list.html)

    This file is based on the MetaPost version distributed by TeXLive:
    svn://tug.org/texlive/trunk/Build/source/texk/web2c/mplibdir revision 22737 #
    (2011-05-31)
*/

/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 utils/type
 math/math
 */

/**
 * @fileoverview In this file the namespace Math.Metapost is defined which holds algorithms translated from Metapost
 * by D.E. Knuth and J.D. Hobby.
 */

define('math/metapost',['utils/type', 'math/math'], function (Type, Mat) {

    "use strict";

    /**
     * The JXG.Math.Metapost namespace holds algorithms translated from Metapost
     * by D.E. Knuth and J.D. Hobby.
     *
     * @name JXG.Math.Metapost
     * @exports Mat.Metapost as JXG.Math.Metapost
     * @namespace
     */
    Mat.Metapost = {
        MP_ENDPOINT: 0,
        MP_EXPLICIT: 1,
        MP_GIVEN: 2,
        MP_CURL: 3,
        MP_OPEN: 4,
        MP_END_CYCLE: 5,

        UNITY: 1.0,
        // two: 2,
        // fraction_half: 0.5,
        FRACTION_ONE: 1.0,
        FRACTION_THREE: 3.0,
        ONE_EIGHTY_DEG: Math.PI,
        THREE_SIXTY_DEG: 2 * Math.PI,
        // EPSILON: 1e-5,
        EPS_SQ: 1e-5 * 1e-5,

        /**
         * @private
         */
        make_choices: function (knots) {
            var dely, h, k, delx, n,
                q, p, s, cosine, t, sine,
                delta_x, delta_y, delta, psi;

            p = knots[0];
            do {
                if (!p) {
                    break;
                }
                q = p.next;

                // Join two identical knots by setting the control points to the same
                // coordinates.
                // MP 291
                if (p.rtype > this.MP_EXPLICIT &&
                    ((p.x - q.x) * (p.x - q.x)  + (p.y - q.y) * (p.y - q.y) < this.EPS_SQ)) {

                    p.rtype = this.MP_EXPLICIT;
                    if (p.ltype === this.MP_OPEN) {
                        p.ltype = this.MP_CURL;
                        p.set_left_curl(this.UNITY);
                    }

                    q.ltype = this.MP_EXPLICIT;
                    if (q.rtype === this.MP_OPEN) {
                        q.rtype = this.MP_CURL;
                        q.set_right_curl(this.UNITY);
                    }

                    p.rx = p.x;
                    q.lx = p.x;
                    p.ry = p.y;
                    q.ly = p.y;
                }
                p = q;
            } while (p !== knots[0]);

            // Find the first breakpoint, h, on the path
            // MP 292
            h = knots[0];
            while (true) {
                if (h.ltype !== this.MP_OPEN || h.rtype !== this.MP_OPEN) {
                    break;
                }
                h = h.next;
                if (h === knots[0]) {
                    h.ltype = this.MP_END_CYCLE;
                    break;
                }
            }

            p = h;
            while (true) {
                if (!p) {
                  break;
                }

                // Fill in the control points between p and the next breakpoint,
                // then advance p to that breakpoint
                // MP 299
                q = p.next;
                if (p.rtype >= this.MP_GIVEN) {
                    while (q.ltype === this.MP_OPEN && q.rtype === this.MP_OPEN) {
                        q = q.next;
                    }

                    // Calculate the turning angles psi_ k and the distances d_{k,k+1};
                    // set n to the length of the path
                    // MP 302
                    k = 0;
                    s = p;
                    n = knots.length;

                    delta_x = [];
                    delta_y = [];
                    delta = [];
                    psi = [null];

                    // tuple([]) = tuple([[], [], [], [null]]);
                    while (true) {
                        t = s.next;
                        // None;
                        delta_x.push(t.x - s.x);
                        delta_y.push(t.y - s.y);
                        delta.push( this.mp_pyth_add(delta_x[k], delta_y[k]) );
                        if (k > 0) {
                            sine =   delta_y[k - 1] / delta[k - 1];
                            cosine = delta_x[k - 1] / delta[k - 1];
                            psi.push(
                                Math.atan2(
                                    delta_y[k] * cosine - delta_x[k] * sine,
                                    delta_x[k] * cosine + delta_y[k] * sine
                                    )
                                );
                        }
                        k++;
                        s = t;
                        if (s === q) {
                            n = k;
                        }
                        if (k >= n && s.ltype !== this.MP_END_CYCLE) {
                            break;
                        }
                    }
                    if (k === n) {
                        psi.push(0);
                    } else {
                        psi.push(psi[1]);
                    }

                    // Remove open types at the breakpoints
                    // MP 303
                    if (q.ltype === this.MP_OPEN) {
                        delx = (q.rx - q.x);
                        dely = (q.ry - q.y);
                        if (delx * delx + dely * dely < this.EPS_SQ) {
                            q.ltype = this.MP_CURL;
                            q.set_left_curl(this.UNITY);
                        } else {
                            q.ltype = this.MP_GIVEN;
                            q.set_left_given(Math.atan2(dely, delx));
                        }
                    }
                    if (p.rtype === this.MP_OPEN && p.ltype === this.MP_EXPLICIT) {
                        delx = (p.x - p.lx);
                        dely = (p.y - p.ly);
                        if ( delx * delx + dely * dely  < this.EPS_SQ) {
                            p.rtype = this.MP_CURL;
                            p.set_right_curl(this.UNITY);
                        } else {
                            p.rtype = this.MP_GIVEN;
                            p.set_right_given(Math.atan2(dely, delx));
                        }
                    }
                    this.mp_solve_choices(p, q, n, delta_x, delta_y, delta, psi);
                } else if (p.rtype === this.MP_ENDPOINT) {
                    // MP 294
                    p.rx = p.x;
                    p.ry = p.y;
                    q.lx = q.x;
                    q.ly = q.y;
                }
                p = q;

                if (p === h) {
                    break;
                }
            }
        },

        /**
         * Implements solve_choices form metapost
         * MP 305
         * @private
         */
        mp_solve_choices: function (p, q, n, delta_x, delta_y, delta, psi) {
            var aa, acc, vv, bb, ldelta, ee, k, s,
                ww, uu, lt, r, t, ff, theta, rt, dd, cc,
                ct_st, ct, st, cf_sf, cf, sf, i,
                k_idx;

            ldelta = delta.length + 1;
            uu = new Array(ldelta);
            ww = new Array(ldelta);
            vv = new Array(ldelta);
            theta = new Array(ldelta);
            for (i = 0; i < ldelta; i++) {
                theta[i] = vv[i] = ww[i] = uu[i] = 0;
            }
            k = 0;
            s = p;
            r = 0;
            while (true) {
                t = s.next;
                if (k === 0) {
                    // MP 306
                    if (s.rtype === this.MP_GIVEN) {
                        // MP 314
                        if (t.ltype === this.MP_GIVEN) {
                            aa = Math.atan2(delta_y[0], delta_x[0]);
                            ct_st = this.mp_n_sin_cos(p.right_given() - aa);
                            ct = ct_st[0];
                            st = ct_st[1];
                            cf_sf = this.mp_n_sin_cos(q.left_given() - aa);
                            cf = cf_sf[0];
                            sf = cf_sf[1];
                            this.mp_set_controls(p, q, delta_x[0], delta_y[0], st, ct, -sf, cf);
                            return;
                        }
                        vv[0] = s.right_given() - Math.atan2(delta_y[0], delta_x[0]);
                        vv[0] = this.reduce_angle(vv[0]);
                        uu[0] = 0;
                        ww[0] = 0;

                    } else if (s.rtype === this.MP_CURL) {
                        // MP 315
                        if (t.ltype === this.MP_CURL) {
                            p.rtype = this.MP_EXPLICIT;
                            q.ltype = this.MP_EXPLICIT;
                            lt = Math.abs(q.left_tension());
                            rt = Math.abs(p.right_tension());
                            ff = this.UNITY / (3.0 * rt);
                            p.rx = p.x + delta_x[0] * ff;
                            p.ry = p.y + delta_y[0] * ff;
                            ff = this.UNITY / (3.0 * lt);
                            q.lx = q.x - delta_x[0] * ff;
                            q.ly = q.y - delta_y[0] * ff;
                            return;
                        }
                        cc = s.right_curl();
                        lt = Math.abs(t.left_tension());
                        rt = Math.abs(s.right_tension());
                        uu[0] = this.mp_curl_ratio(cc, rt, lt);
                        vv[0] = -psi[1] * uu[0];
                        ww[0] = 0;
                    } else {
                        if (s.rtype === this.MP_OPEN) {
                            uu[0] = 0;
                            vv[0] = 0;
                            ww[0] = this.FRACTION_ONE;
                        }
                    }
                } else {
                    if (s.ltype === this.MP_END_CYCLE || s.ltype === this.MP_OPEN) {
                        // MP 308
                        aa = this.UNITY / (3.0 * Math.abs(r.right_tension()) - this.UNITY);
                        dd = delta[k] * (this.FRACTION_THREE - this.UNITY / Math.abs(r.right_tension()));
                        bb = this.UNITY / (3 * Math.abs(t.left_tension()) - this.UNITY);
                        ee = delta[k - 1] * (this.FRACTION_THREE - this.UNITY / Math.abs(t.left_tension()));
                        cc = this.FRACTION_ONE - uu[k - 1] * aa;
                        dd = dd * cc;
                        lt = Math.abs(s.left_tension());
                        rt = Math.abs(s.right_tension());
                        if (lt < rt) {
                            dd *= Math.pow(lt / rt, 2);
                        } else {
                            if (lt > rt) {
                                ee *= Math.pow(rt / lt, 2);
                            }
                        }
                        ff = ee / (ee + dd);
                        uu[k] = ff * bb;
                        acc = -psi[k + 1] * uu[k];
                        if (r.rtype === this.MP_CURL) {
                            ww[k] = 0;
                            vv[k] = acc - psi[1] * (this.FRACTION_ONE - ff);
                        } else {
                            ff = (this.FRACTION_ONE - ff) / cc;
                            acc = acc - psi[k] * ff;
                            ff = ff * aa;
                            vv[k] = acc - vv[k - 1] * ff;
                            ww[k] = -ww[k - 1] * ff;
                        }
                        if (s.ltype === this.MP_END_CYCLE) {
                            aa = 0;
                            bb = this.FRACTION_ONE;
                            while (true) {
                                k -= 1;
                                if (k === 0) {
                                    k = n;
                                }
                                aa = vv[k] - aa * uu[k];
                                bb = ww[k] - bb * uu[k];
                                if (k === n) {
                                    break;
                                }
                            }
                            aa = aa / (this.FRACTION_ONE - bb);
                            theta[n] = aa;
                            vv[0] = aa;
                            // k_val = range(1, n);
                            // for (k_idx in k_val) {
                              // k = k_val[k_idx];
                            for (k_idx = 1; k_idx < n; k_idx++) {
                                vv[k_idx] = vv[k_idx] + aa * ww[k_idx];
                            }
                            break;
                        }
                    } else {
                        if (s.ltype === this.MP_CURL) {
                            cc = s.left_curl();
                            lt = Math.abs(s.left_tension());
                            rt = Math.abs(r.right_tension());
                            ff = this.mp_curl_ratio(cc, lt, rt);
                            theta[n] = -(vv[n - 1] * ff) / (this.FRACTION_ONE - ff * uu[n - 1]);
                            break;
                        }
                        if (s.ltype === this.MP_GIVEN) {
                            theta[n] = s.left_given() - Math.atan2(delta_y[n - 1], delta_x[n - 1]);
                            theta[n] = this.reduce_angle(theta[n]);
                            break;
                        }
                    }
                }
                r = s;
                s = t;
                k += 1;
            }

            // MP 318
            for (k = n-1; k > -1; k--) {
                theta[k] = vv[k] - theta[k + 1] * uu[k];
            }

            s = p;
            k = 0;
            while (true) {
                t = s.next;
                ct_st = this.mp_n_sin_cos(theta[k]);
                ct = ct_st[0];
                st = ct_st[1];
                cf_sf = this.mp_n_sin_cos((-(psi[k + 1]) - theta[k + 1]));
                cf = cf_sf[0];
                sf = cf_sf[1];
                this.mp_set_controls(s, t, delta_x[k], delta_y[k], st, ct, sf, cf);
                k++;
                s = t;
                if (k === n) {
                  break;
                }
            }
        },

        /**
         * @private
         */
        mp_n_sin_cos: function (z) {
            return [Math.cos(z), Math.sin(z)];
        },

        /**
         * @private
         */
        mp_set_controls: function (p, q, delta_x, delta_y, st, ct, sf, cf) {
            var rt, ss, lt, sine, rr;
            lt = Math.abs(q.left_tension());
            rt = Math.abs(p.right_tension());
            rr = this.mp_velocity(st, ct, sf, cf, rt);
            ss = this.mp_velocity(sf, cf, st, ct, lt);

            // console.log('lt rt rr ss', lt, rt, rr, ss);
            if (p.right_tension() < 0 || q.left_tension() < 0) {
                if ((st >= 0 && sf >= 0) || (st <= 0 && sf <= 0)) {
                    sine = Math.abs(st) * cf + Math.abs(sf) * ct;
                    if (sine > 0) {
                        sine *= 1.00024414062;
                        if (p.right_tension() < 0) {
                            if (this.mp_ab_vs_cd(Math.abs(sf), this.FRACTION_ONE, rr, sine) < 0) {
                                rr = Math.abs(sf) / sine;
                            }
                        }
                        if (q.left_tension() < 0) {
                            if (this.mp_ab_vs_cd(Math.abs(st), this.FRACTION_ONE, ss, sine) < 0) {
                                ss = Math.abs(st) / sine;
                            }
                        }
                    }
                }
            }
            p.rx = p.x + (delta_x * ct - delta_y * st) * rr;
            p.ry = p.y + (delta_y * ct + delta_x * st) * rr;
            q.lx = q.x - (delta_x * cf + delta_y * sf) * ss;
            q.ly = q.y - (delta_y * cf - delta_x * sf) * ss;
            p.rtype = this.MP_EXPLICIT;
            q.ltype = this.MP_EXPLICIT;
        },

        /**
         * @private
         */
        mp_pyth_add: function (a, b) {
            return Math.sqrt((a * a + b * b));
        },

        /**
         *
         * @private
         */
        mp_curl_ratio: function (gamma, a_tension, b_tension) {
            var alpha = 1.0 / a_tension,
                beta =  1.0 / b_tension;

            return Math.min (4.0,
                ((3.0 - alpha) * alpha * alpha * gamma + beta * beta * beta) /
                 (alpha * alpha * alpha * gamma + (3.0 - beta) * beta * beta)
                );
        },

        /**
         * @private
         */
        mp_ab_vs_cd: function (a, b, c, d) {
            if (a * b === c * d) {
                return 0;
            }
            if (a * b > c * d) {
                return 1;
            }
            return -1;
        },

        /**
         * @private
         */
        mp_velocity: function (st, ct, sf, cf, t) {
          return Math.min (4.0,
                (2.0 + Math.sqrt(2) * (st - sf / 16.0) * (sf - st / 16.0) * (ct - cf)) /
                (1.5 * t * ((2 + (Math.sqrt(5) - 1) * ct) + (3 - Math.sqrt(5)) * cf))
            );
        },

        /**
         * @private
         * @param {Number} A
         */
        reduce_angle: function (A) {
            if (Math.abs(A) > this.ONE_EIGHTY_DEG) {
                if (A > 0) {
                    A -= this.THREE_SIXTY_DEG;
                } else {
                    A += this.THREE_SIXTY_DEG;
                }
            }
            return A;
        },

        /**
         *
         * @private
         * @param {Array} p
         * @param {Number} tension
         * @param {Boolean} cycle
         */
        makeknots: function (p, tension, cycle) {
            var i, len,
                knots = [];

            tension = tension || 1;

            len = p.length;
            for (i = 0; i < len; i++) {
                knots.push({
                    x: p[i][0],
                    y: p[i][1],
                    ltype: this.MP_OPEN,
                    rtype: this.MP_OPEN,
                    ly: tension,
                    ry: tension,
                    lx: tension,
                    rx: tension,
                    left_curl: function() { return this.lx || 0; },
                    right_curl: function() { return this.rx || 0; },
                    left_tension: function() {
                            if (!this.ly) { this.ly = 1; }
                            return this.ly;
                        },
                    right_tension: function() {
                            if (!this.ry) { this.ry = 1; }
                            return this.ry;
                        },
                    set_right_curl: function(x) { this.rx = x || 0; },
                    set_left_curl: function(x) { this.lx = x || 0; }
                });
            }
            len = knots.length;
            for (i = 0; i < len; i++) {
                knots[i].next = knots[i+1] || knots[i];
                knots[i].set_right_given = knots[i].set_right_curl;
                knots[i].set_left_given = knots[i].set_left_curl;
                knots[i].right_given = knots[i].right_curl;
                knots[i].left_given = knots[i].left_curl;
            }
            knots[len - 1].next = knots[0];

            if (!cycle) {
                knots[len - 1].rtype = this.MP_ENDPOINT;

                knots[len - 1].ltype = this.MP_CURL;
                knots[0].rtype = this.MP_CURL;
            }

            return knots;
        },

        /**
         *
         * @param {Array} point_list
         * @param {Object} controls
         *
         * @returns {Array}
         */
        curve: function(point_list, controls) {
            var knots, len, i, val,
                x = [],
                y = [];

            controls = controls || {
                    tension: 1,
                    direction: {},
                    curl: {},
                    isClosed: false
                };

            knots = this.makeknots(point_list, Type.evaluate(controls.tension), controls.isClosed);

            len = knots.length;
            for (i in controls.direction) {
                if (controls.direction.hasOwnProperty(i)) {
                    val = Type.evaluate(controls.direction[i]);
                    if (Type.isArray(val)) {
                        if (val[0] !== false) {
                            knots[i].lx = val[0] * Math.PI / 180;
                            knots[i].ltype = this.MP_GIVEN;
                        }
                        if (val[1] !== false) {
                            knots[i].rx = val[1] * Math.PI / 180;
                            knots[i].rtype = this.MP_GIVEN;
                        }
                    } else {
                        knots[i].lx = val * Math.PI / 180;
                        knots[i].rx = val * Math.PI / 180;
                        knots[i].ltype = knots[i].rtype = this.MP_GIVEN;
                    }
                }
            }
            for (i in controls.curl) {
                if (controls.curl.hasOwnProperty(i)) {
                    val = Type.evaluate(controls.curl[i]);
                    if (parseInt(i, 10) === 0) {
                        knots[i].rtype = this.MP_CURL;
                        knots[i].set_right_curl(val);
                    } else if (parseInt(i, 10) === len - 1) {
                        knots[i].ltype = this.MP_CURL;
                        knots[i].set_left_curl(val);
                    }
                }
            }

            this.make_choices(knots);

            for (i = 0; i < len - 1; i++) {
                x.push(knots[i].x);
                x.push(knots[i].rx);
                x.push(knots[i + 1].lx);
                y.push(knots[i].y);
                y.push(knots[i].ry);
                y.push(knots[i + 1].ly);
            }
            x.push(knots[len - 1].x);
            y.push(knots[len - 1].y);

            if (controls.isClosed) {
                x.push(knots[len - 1].rx);
                y.push(knots[len - 1].ry);
                x.push(knots[0].lx);
                y.push(knots[0].ly);
                x.push(knots[0].x);
                y.push(knots[0].y);
            }

            return [x, y];
        }

    };

    return Mat.Metapost;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        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
    <http://www.gnu.org/licenses/>, <https://www.apache.org/licenses/LICENSE-2.0.html>,
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true, bitwise: true*/

/* depends:
 jxg
 */

/**
 * @fileoverview Utilities for uncompressing and base64 decoding
 */

define('utils/zip',['jxg'], function (JXG) {

    "use strict";

    // Zip routine constants

    var bitReverse = [
            0x00, 0x80, 0x40, 0xc0, 0x20, 0xa0, 0x60, 0xe0,
            0x10, 0x90, 0x50, 0xd0, 0x30, 0xb0, 0x70, 0xf0,
            0x08, 0x88, 0x48, 0xc8, 0x28, 0xa8, 0x68, 0xe8,
            0x18, 0x98, 0x58, 0xd8, 0x38, 0xb8, 0x78, 0xf8,
            0x04, 0x84, 0x44, 0xc4, 0x24, 0xa4, 0x64, 0xe4,
            0x14, 0x94, 0x54, 0xd4, 0x34, 0xb4, 0x74, 0xf4,
            0x0c, 0x8c, 0x4c, 0xcc, 0x2c, 0xac, 0x6c, 0xec,
            0x1c, 0x9c, 0x5c, 0xdc, 0x3c, 0xbc, 0x7c, 0xfc,
            0x02, 0x82, 0x42, 0xc2, 0x22, 0xa2, 0x62, 0xe2,
            0x12, 0x92, 0x52, 0xd2, 0x32, 0xb2, 0x72, 0xf2,
            0x0a, 0x8a, 0x4a, 0xca, 0x2a, 0xaa, 0x6a, 0xea,
            0x1a, 0x9a, 0x5a, 0xda, 0x3a, 0xba, 0x7a, 0xfa,
            0x06, 0x86, 0x46, 0xc6, 0x26, 0xa6, 0x66, 0xe6,
            0x16, 0x96, 0x56, 0xd6, 0x36, 0xb6, 0x76, 0xf6,
            0x0e, 0x8e, 0x4e, 0xce, 0x2e, 0xae, 0x6e, 0xee,
            0x1e, 0x9e, 0x5e, 0xde, 0x3e, 0xbe, 0x7e, 0xfe,
            0x01, 0x81, 0x41, 0xc1, 0x21, 0xa1, 0x61, 0xe1,
            0x11, 0x91, 0x51, 0xd1, 0x31, 0xb1, 0x71, 0xf1,
            0x09, 0x89, 0x49, 0xc9, 0x29, 0xa9, 0x69, 0xe9,
            0x19, 0x99, 0x59, 0xd9, 0x39, 0xb9, 0x79, 0xf9,
            0x05, 0x85, 0x45, 0xc5, 0x25, 0xa5, 0x65, 0xe5,
            0x15, 0x95, 0x55, 0xd5, 0x35, 0xb5, 0x75, 0xf5,
            0x0d, 0x8d, 0x4d, 0xcd, 0x2d, 0xad, 0x6d, 0xed,
            0x1d, 0x9d, 0x5d, 0xdd, 0x3d, 0xbd, 0x7d, 0xfd,
            0x03, 0x83, 0x43, 0xc3, 0x23, 0xa3, 0x63, 0xe3,
            0x13, 0x93, 0x53, 0xd3, 0x33, 0xb3, 0x73, 0xf3,
            0x0b, 0x8b, 0x4b, 0xcb, 0x2b, 0xab, 0x6b, 0xeb,
            0x1b, 0x9b, 0x5b, 0xdb, 0x3b, 0xbb, 0x7b, 0xfb,
            0x07, 0x87, 0x47, 0xc7, 0x27, 0xa7, 0x67, 0xe7,
            0x17, 0x97, 0x57, 0xd7, 0x37, 0xb7, 0x77, 0xf7,
            0x0f, 0x8f, 0x4f, 0xcf, 0x2f, 0xaf, 0x6f, 0xef,
            0x1f, 0x9f, 0x5f, 0xdf, 0x3f, 0xbf, 0x7f, 0xff
        ],
        cplens = [
            3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31,
            35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 0, 0
        ],

        cplext = [
            0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2,
            3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 99, 99
        ], /* 99==invalid */

        cpdist = [
            0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0007, 0x0009, 0x000d,
            0x0011, 0x0019, 0x0021, 0x0031, 0x0041, 0x0061, 0x0081, 0x00c1,
            0x0101, 0x0181, 0x0201, 0x0301, 0x0401, 0x0601, 0x0801, 0x0c01,
            0x1001, 0x1801, 0x2001, 0x3001, 0x4001, 0x6001
        ],

        cpdext = [
            0,  0,  0,  0,  1,  1,  2,  2,
            3,  3,  4,  4,  5,  5,  6,  6,
            7,  7,  8,  8,  9,  9, 10, 10,
            11, 11, 12, 12, 13, 13
        ],

        border = [16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15],

        NAMEMAX = 256;


    // Util namespace
    JXG.Util = JXG.Util || {};

    /**
     * @class Unzip class
     * Class for gunzipping, unzipping and base64 decoding of files.
     * It is used for reading GEONExT, Geogebra and Intergeo files.
     *
     * Only Huffman codes are decoded in gunzip.
     * The code is based on the source code for gunzip.c by Pasi Ojala
     * @see http://www.cs.tut.fi/~albert/Dev/gunzip/gunzip.c
     * @see http://www.cs.tut.fi/~albert
     */
    JXG.Util.Unzip = function (barray) {
        var gpflags, crc, SIZE, fileout, flens, fmax,
            outputArr = [],
            output = '',
            debug = false,
            files = 0,
            unzipped = [],
            buf32k = new Array(32768),
            bIdx = 0,
            modeZIP = false,
            barraylen = barray.length,
            bytepos = 0,
            bitpos = 0,
            bb = 1,
            bits = 0,
            literalTree = new Array(288),
            distanceTree = new Array(32),
            treepos = 0,
            Places = null,
            Places2 = null,
            impDistanceTree = new Array(64),
            impLengthTree = new Array(64),
            len = 0,
            fpos = new Array(17),
            nameBuf = [];

        fpos[0] = 0;

        function readByte() {
            bits += 8;

            if (bytepos < barraylen) {
                return barray[bytepos++];
            }

            return -1;
        }

        function byteAlign() {
            bb = 1;
        }

        function readBit() {
            var carry;

            try {   // Prevent problems on iOS7 with >>
                bits++;
                carry = (bb & 1);
                bb >>= 1;

                if (bb === 0) {
                    bb = readByte();
                    carry = (bb & 1);
                    bb = (bb >> 1) | 0x80;
                }

                return carry;
            } catch (e) {
                throw e;
            }
        }

        function readBits(a) {
            var res = 0,
                i = a;

            // Prevent problems on iOS7 with >>
            try {
                while (i--) {
                    res = (res << 1) | readBit();
                }

                if (a) {
                    res = bitReverse[res] >> (8 - a);
                }
            } catch (e) {
                throw e;
            }

            return res;
        }

        function flushBuffer() {
            bIdx = 0;
        }

        function addBuffer(a) {
            SIZE++;
            buf32k[bIdx++] = a;
            outputArr.push(String.fromCharCode(a));

            if (bIdx === 0x8000) {
                bIdx = 0;
            }
        }

        function HufNode() {
            this.b0 = 0;
            this.b1 = 0;
            this.jump = null;
            this.jumppos = -1;
        }

        function isPat() {
            while (true) {
                if (fpos[len] >= fmax) {
                    return -1;
                }

                if (flens[fpos[len]] === len) {
                    return fpos[len]++;
                }

                fpos[len]++;
            }
        }

        function rec() {
            var curplace = Places[treepos],
                tmp;

            if (len === 17) {
                return -1;
            }
            treepos++;
            len++;

            tmp = isPat();

            if (tmp >= 0) {
                /* leaf cell for 0-bit */
                curplace.b0 = tmp;
            } else {
                /* Not a Leaf cell */
                curplace.b0 = 0x8000;

                if (rec()) {
                    return -1;
                }
            }

            tmp = isPat();

            if (tmp >= 0) {
                /* leaf cell for 1-bit */
                curplace.b1 = tmp;
                /* Just for the display routine */
                curplace.jump = null;
            } else {
                /* Not a Leaf cell */
                curplace.b1 = 0x8000;
                curplace.jump = Places[treepos];
                curplace.jumppos = treepos;
                if (rec()) {
                    return -1;
                }
            }
            len--;

            return 0;
        }

        function createTree(currentTree, numval, lengths, show) {
            var i;

            Places = currentTree;
            treepos = 0;
            flens = lengths;
            fmax  = numval;

            for (i = 0; i < 17; i++) {
                fpos[i] = 0;
            }
            len = 0;

            if (rec()) {
                return -1;
            }

            return 0;
        }

        function decodeValue(currentTree) {
            var len, i, b,
                xtreepos = 0,
                X = currentTree[xtreepos];

            /* decode one symbol of the data */
            while (true) {
                b = readBit();

                if (b) {
                    if (!(X.b1 & 0x8000)) {
                        /* If leaf node, return data */
                        return X.b1;
                    }

                    X = X.jump;
                    len = currentTree.length;

                    for (i = 0; i < len; i++) {
                        if (currentTree[i] === X) {
                            xtreepos = i;
                            break;
                        }
                    }
                } else {
                    if (!(X.b0 & 0x8000)) {
                        /* If leaf node, return data */
                        return X.b0;
                    }
                    xtreepos++;
                    X = currentTree[xtreepos];
                }
            }
        }

        function deflateLoop() {
            var last, c, type, i, j, l, ll, ll2, len, blockLen, dist, cSum,
                n, literalCodes, distCodes, lenCodes, z;

            do {
                last = readBit();
                type = readBits(2);

                if (type === 0) {
                    // Stored
                    byteAlign();
                    blockLen = readByte();
                    blockLen |= (readByte() << 8);

                    cSum = readByte();
                    cSum |= (readByte() << 8);

                    if (((blockLen ^ ~cSum) & 0xffff)) {
                        JXG.debug('BlockLen checksum mismatch\n');
                    }

                    while (blockLen--) {
                        c = readByte();
                        addBuffer(c);
                    }
                } else if (type === 1) {
                    /* Fixed Huffman tables -- fixed decode routine */
                    while (true) {
                        /*
                         256    0000000        0
                         :   :     :
                         279    0010111        23
                         0   00110000    48
                         :    :      :
                         143    10111111    191
                         280 11000000    192
                         :    :      :
                         287 11000111    199
                         144    110010000    400
                         :    :       :
                         255    111111111    511

                         Note the bit order!
                         */

                        j = (bitReverse[readBits(7)] >> 1);

                        if (j > 23) {
                            j = (j << 1) | readBit();    /* 48..255 */

                            if (j > 199) {    /* 200..255 */
                                j -= 128;    /*  72..127 */
                                j = (j << 1) | readBit();        /* 144..255 << */
                            } else {        /*  48..199 */
                                j -= 48;    /*   0..151 */
                                if (j > 143) {
                                    j = j + 136;    /* 280..287 << */
                                    /*   0..143 << */
                                }
                            }
                        } else {    /*   0..23 */
                            j += 256;    /* 256..279 << */
                        }

                        if (j < 256) {
                            addBuffer(j);
                        } else if (j === 256) {
                            /* EOF */
                            break;
                        } else {
                            j -= 256 + 1;    /* bytes + EOF */
                            len = readBits(cplext[j]) + cplens[j];
                            j = bitReverse[readBits(5)] >> 3;

                            if (cpdext[j] > 8) {
                                dist = readBits(8);
                                dist |= (readBits(cpdext[j] - 8) << 8);
                            } else {
                                dist = readBits(cpdext[j]);
                            }

                            dist += cpdist[j];

                            for (j = 0; j < len; j++) {
                                c = buf32k[(bIdx - dist) & 0x7fff];
                                addBuffer(c);
                            }
                        }
                    } // while
                } else if (type === 2) {
                    // "static" just to preserve stack
                    ll = new Array(288 + 32);

                    // Dynamic Huffman tables
                    literalCodes = 257 + readBits(5);
                    distCodes = 1 + readBits(5);
                    lenCodes = 4 + readBits(4);

                    for (j = 0; j < 19; j++) {
                        ll[j] = 0;
                    }

                    // Get the decode tree code lengths

                    for (j = 0; j < lenCodes; j++) {
                        ll[border[j]] = readBits(3);
                    }
                    len = distanceTree.length;

                    for (i = 0; i < len; i++) {
                        distanceTree[i] = new HufNode();
                    }

                    if (createTree(distanceTree, 19, ll, 0)) {
                        flushBuffer();
                        return 1;
                    }

                    //read in literal and distance code lengths
                    n = literalCodes + distCodes;
                    i = 0;
                    z = -1;

                    while (i < n) {
                        z++;
                        j = decodeValue(distanceTree);

                        // length of code in bits (0..15)
                        if (j < 16) {
                            ll[i++] = j;
                        // repeat last length 3 to 6 times
                        } else if (j === 16) {
                            j = 3 + readBits(2);

                            if (i + j > n) {
                                flushBuffer();
                                return 1;
                            }
                            l = i ? ll[i - 1] : 0;

                            while (j--) {
                                ll[i++] = l;
                            }
                        } else {
                            // 3 to 10 zero length codes
                            if (j === 17) {
                                j = 3 + readBits(3);
                            // j == 18: 11 to 138 zero length codes
                            } else {
                                j = 11 + readBits(7);
                            }

                            if (i + j > n) {
                                flushBuffer();
                                return 1;
                            }

                            while (j--) {
                                ll[i++] = 0;
                            }
                        }
                    }

                    // Can overwrite tree decode tree as it is not used anymore
                    len = literalTree.length;
                    for (i = 0; i < len; i++) {
                        literalTree[i] = new HufNode();
                    }

                    if (createTree(literalTree, literalCodes, ll, 0)) {
                        flushBuffer();
                        return 1;
                    }

                    len = literalTree.length;

                    for (i = 0; i < len; i++) {
                        distanceTree[i] = new HufNode();
                    }

                    ll2 = [];

                    for (i = literalCodes; i < ll.length; i++) {
                        ll2[i - literalCodes] = ll[i];
                    }

                    if (createTree(distanceTree, distCodes, ll2, 0)) {
                        flushBuffer();
                        return 1;
                    }

                    while (true) {
                        j = decodeValue(literalTree);

                        // In C64: if carry set
                        if (j >= 256) {
                            j -= 256;
                            if (j === 0) {
                                // EOF
                                break;
                            }

                            j -= 1;
                            len = readBits(cplext[j]) + cplens[j];
                            j = decodeValue(distanceTree);

                            if (cpdext[j] > 8) {
                                dist = readBits(8);
                                dist |= (readBits(cpdext[j] - 8) << 8);
                            } else {
                                dist = readBits(cpdext[j]);
                            }

                            dist += cpdist[j];

                            while (len--) {
                                c = buf32k[(bIdx - dist) & 0x7fff];
                                addBuffer(c);
                            }
                        } else {
                            addBuffer(j);
                        }
                    }
                }
            } while (!last);

            flushBuffer();
            byteAlign();

            return 0;
        }


        /**
         * nextFile:
         * Extract the next file from the compressed archive.
         * Calls skipdir() to proceed recursively.
         *
         * @return {Boolean}  false if the end of files' data section has baseElement
         * reached. Then, then all recursive functions are stopped immediately.
         *
         */
        function nextFile() {
            var i, c, extralen, filelen, size, compSize, crc, method,
                tmp = [];

            // Prevent problems on iOS7 with >>
            try {
                outputArr = [];
                modeZIP = false;
                tmp[0] = readByte();
                tmp[1] = readByte();

                //GZIP
                if (tmp[0] === 0x78 && tmp[1] === 0xda) {
                    deflateLoop();
                    unzipped[files] = [outputArr.join(''), 'geonext.gxt'];
                    files++;
                }

                //GZIP
                if (tmp[0] === 0x1f && tmp[1] === 0x8b) {
                    skipdir();
                    unzipped[files] = [outputArr.join(''), 'file'];
                    files++;
                }

                //ZIP
                if (tmp[0] === 0x50 && tmp[1] === 0x4b) {
                    modeZIP = true;
                    tmp[2] = readByte();
                    tmp[3] = readByte();

                    if (tmp[2] === 0x03 && tmp[3] === 0x04) {
                        //MODE_ZIP
                        tmp[0] = readByte();
                        tmp[1] = readByte();

                        gpflags = readByte();
                        gpflags |= (readByte() << 8);

                        method = readByte();
                        method |= (readByte() << 8);

                        readByte();
                        readByte();
                        readByte();
                        readByte();

                        crc = readByte();
                        crc |= (readByte() << 8);
                        crc |= (readByte() << 16);
                        crc |= (readByte() << 24);

                        compSize = readByte();
                        compSize |= (readByte() << 8);
                        compSize |= (readByte() << 16);
                        compSize |= (readByte() << 24);

                        size = readByte();
                        size |= (readByte() << 8);
                        size |= (readByte() << 16);
                        size |= (readByte() << 24);

                        filelen = readByte();
                        filelen |= (readByte() << 8);

                        extralen = readByte();
                        extralen |= (readByte() << 8);

                        i = 0;
                        nameBuf = [];

                        while (filelen--) {
                            c = readByte();
                            if (c === '/' | c === ':') {
                                i = 0;
                            } else if (i < NAMEMAX - 1) {
                                nameBuf[i++] = String.fromCharCode(c);
                            }
                        }

                        if (!fileout) {
                            fileout = nameBuf;
                        }

                        i = 0;
                        while (i < extralen) {
                            c = readByte();
                            i++;
                        }

                        SIZE = 0;
                        if (method === 8) {
                            deflateLoop();
                            unzipped[files] = new Array(2);
                            unzipped[files][0] = outputArr.join('');
                            unzipped[files][1] = nameBuf.join('');
                            files++;
                        }

                        if (skipdir()) {
                            // We are beyond the files' data in the zip archive.
                            // Let's get out immediately...
                            return false;
                        }
                    }
                    return true;
                }
            } catch (e) {
                throw e;
            }
            return false;
        }


        /**
         * Test if the end of the files' data part of the archive has baseElement
         * reached. If not, uncompressing is resumed.
         *
         * @return {Boolean}  true if the end of the files' data sections have
         * been reached.
         *
         * @private
         */
        function skipdir() {
            var crc, compSize, size, os, i, c,
                tmp = [];

            if ((gpflags & 8)) {
                tmp[0] = readByte();
                tmp[1] = readByte();
                tmp[2] = readByte();
                tmp[3] = readByte();

                // signature for data descriptor record: 0x08074b50
                // 12 bytes:
                //  crc 4 bytes
                //  compressed size 4 bytes
                // uncompressed size 4 bytes
                if (tmp[0] === 0x50 &&
                        tmp[1] === 0x4b &&
                        tmp[2] === 0x07 &&
                        tmp[3] === 0x08) {
                    crc = readByte();
                    crc |= (readByte() << 8);
                    crc |= (readByte() << 16);
                    crc |= (readByte() << 24);
                } else {
                    crc = tmp[0] | (tmp[1] << 8) | (tmp[2] << 16) | (tmp[3] << 24);
                }

                compSize = readByte();
                compSize |= (readByte() << 8);
                compSize |= (readByte() << 16);
                compSize |= (readByte() << 24);

                size = readByte();
                size |= (readByte() << 8);
                size |= (readByte() << 16);
                size |= (readByte() << 24);
            }

            if (modeZIP) {
                if (nextFile()) {
                    // A file has been decompressed, we have to proceed
                    return false;
                }
            }

            tmp[0] = readByte();
            if (tmp[0] !== 8) {
                // It seems, we are beyond the files' data in the zip archive.
                // We'll skip the rest..
                return true;
            }

            // There is another file in the zip file. We proceed...
            gpflags = readByte();

            readByte();
            readByte();
            readByte();
            readByte();

            readByte();
            os = readByte();

            if ((gpflags & 4)) {
                tmp[0] = readByte();
                tmp[2] = readByte();
                len = tmp[0] + 256 * tmp[1];
                for (i = 0; i < len; i++) {
                    readByte();
                }
            }

            if ((gpflags & 8)) {
                i = 0;
                nameBuf = [];

                c = readByte();
                while (c) {
                    if (c === '7' || c === ':') {
                        i = 0;
                    }

                    if (i < NAMEMAX - 1) {
                        nameBuf[i++] = c;
                    }

                    c = readByte();
                }
            }

            if ((gpflags & 16)) {
                c = readByte();
                while (c) {
                    c = readByte();
                }
            }

            if ((gpflags & 2)) {
                readByte();
                readByte();
            }

            deflateLoop();

            crc = readByte();
            crc |= (readByte() << 8);
            crc |= (readByte() << 16);
            crc |= (readByte() << 24);

            size = readByte();
            size |= (readByte() << 8);
            size |= (readByte() << 16);
            size |= (readByte() << 24);

            if (modeZIP) {
                if (nextFile()) {
                    // A file has been decompressed, we have to proceed
                    return false;
                }
            }

            // We are here in non-ZIP-files only,
            // In that case the eturn value doesn't matter
            return false;
        }

        JXG.Util.Unzip.prototype.unzipFile = function (name) {
            var i;

            this.unzip();

            for (i = 0; i < unzipped.length; i++) {
                if (unzipped[i][1] === name) {
                    return unzipped[i][0];
                }
            }

            return '';
        };

        JXG.Util.Unzip.prototype.unzip = function () {
            nextFile();
            return unzipped;
        };
    };

    return JXG.Util;
});

/*global JXG: true, define: true, escape: true, unescape: true*/
/*jslint nomen: true, plusplus: true, bitwise: true*/

/* depends:
 jxg
 */

define('utils/encoding',['jxg'], function (JXG) {

    "use strict";

    // constants
    var UTF8_ACCEPT = 0,
        UTF8_REJECT = 12,
        UTF8D = [
            // The first part of the table maps bytes to character classes that
            // to reduce the size of the transition table and create bitmasks.
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,   0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
            1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,   9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9,
            7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,   7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
            8, 8, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,   2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2,
            10, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 4, 3, 3,  11, 6, 6, 6, 5, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8,

            // The second part is a transition table that maps a combination
            // of a state of the automaton and a character class to a state.
            0, 12, 24, 36, 60, 96, 84, 12, 12, 12, 48, 72,  12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12,
            12,  0, 12, 12, 12, 12, 12,  0, 12,  0, 12, 12,  12, 24, 12, 12, 12, 12, 12, 24, 12, 24, 12, 12,
            12, 12, 12, 12, 12, 12, 12, 24, 12, 12, 12, 12,  12, 24, 12, 12, 12, 12, 12, 12, 12, 24, 12, 12,
            12, 12, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,  12, 36, 12, 12, 12, 12, 12, 36, 12, 36, 12, 12,
            12, 36, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12
        ];

    // Util namespace
    JXG.Util = JXG.Util || {};

    /**
     * UTF8 encoding routines
     * @namespace
     */
    JXG.Util.UTF8 = {
        /**
         * Encode a string to utf-8.
         * @param {String} string
         * @returns {String} utf8 encoded string
         */
        encode : function (string) {
            var n, c,
                utftext = '',
                len = string.length;

            string = string.replace(/\r\n/g, '\n');

            // See
            // http://ecmanaut.blogspot.ca/2006/07/encoding-decoding-utf8-in-javascript.html
            // http://monsur.hossa.in/2012/07/20/utf-8-in-javascript.html
            if (typeof unescape === 'function' && typeof encodeURIComponent === 'function') {
                return unescape(encodeURIComponent(string));
            }

            for (n = 0; n < len; n++) {
                c = string.charCodeAt(n);

                if (c < 128) {
                    utftext += String.fromCharCode(c);
                } else if ((c > 127) && (c < 2048)) {
                    utftext += String.fromCharCode((c >> 6) | 192);
                    utftext += String.fromCharCode((c & 63) | 128);
                } else {
                    utftext += String.fromCharCode((c >> 12) | 224);
                    utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                    utftext += String.fromCharCode((c & 63) | 128);
                }

            }

            return utftext;
        },

        /**
         * Decode a string from utf-8.
         * @param {String} utftext to decode
         * @returns {String} utf8 decoded string
         */
        decode : function (utftext) {
            /*
                 The following code is a translation from C99 to JavaScript.

                 The original C99 code can be found at
                 http://bjoern.hoehrmann.de/utf-8/decoder/dfa/

                 Original copyright note:

                 Copyright (c) 2008-2009 Bjoern Hoehrmann <bjoern@hoehrmann.de>

                 License: MIT License (see LICENSE.MIT)
            */

            var i, charCode, type,
                j = 0,
                codepoint = 0,
                state = UTF8_ACCEPT,
                chars = [],
                len = utftext.length,
                results = [];

            for (i = 0; i < len; i++) {
                charCode = utftext.charCodeAt(i);
                type = UTF8D[charCode];

                if (state !== UTF8_ACCEPT) {
                    codepoint = (charCode & 0x3f) | (codepoint << 6);
                } else {
                    codepoint = (0xff >> type) & charCode;
                }

                state = UTF8D[256 + state + type];

                if (state === UTF8_ACCEPT) {
                    if (codepoint > 0xffff) {
                        chars.push(0xD7C0 + (codepoint >> 10), 0xDC00 + (codepoint & 0x3FF));
                    } else {
                        chars.push(codepoint);
                    }

                    j++;

                    if (j % 10000 === 0) {
                        results.push(String.fromCharCode.apply(null, chars));
                        chars = [];
                    }
                }
            }
            results.push(String.fromCharCode.apply(null, chars));
            return results.join("");
        },

        /**
         * Extends the standard charCodeAt() method of the String class to find the ASCII char code of
         * a character at a given position in a UTF8 encoded string.
         * @param {String} str
         * @param {Number} i position of the character
         * @returns {Number}
         */
        asciiCharCodeAt: function (str, i) {
            var c = str.charCodeAt(i);

            if (c > 255) {
                switch (c) {
                case 8364:
                    c = 128;
                    break;
                case 8218:
                    c = 130;
                    break;
                case 402:
                    c = 131;
                    break;
                case 8222:
                    c = 132;
                    break;
                case 8230:
                    c = 133;
                    break;
                case 8224:
                    c = 134;
                    break;
                case 8225:
                    c = 135;
                    break;
                case 710:
                    c = 136;
                    break;
                case 8240:
                    c = 137;
                    break;
                case 352:
                    c = 138;
                    break;
                case 8249:
                    c = 139;
                    break;
                case 338:
                    c = 140;
                    break;
                case 381:
                    c = 142;
                    break;
                case 8216:
                    c = 145;
                    break;
                case 8217:
                    c = 146;
                    break;
                case 8220:
                    c = 147;
                    break;
                case 8221:
                    c = 148;
                    break;
                case 8226:
                    c = 149;
                    break;
                case 8211:
                    c = 150;
                    break;
                case 8212:
                    c = 151;
                    break;
                case 732:
                    c = 152;
                    break;
                case 8482:
                    c = 153;
                    break;
                case 353:
                    c = 154;
                    break;
                case 8250:
                    c = 155;
                    break;
                case 339:
                    c = 156;
                    break;
                case 382:
                    c = 158;
                    break;
                case 376:
                    c = 159;
                    break;
                default:
                    break;
                }
            }
            return c;
        }
    };

    return JXG.Util.UTF8;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true, bitwise: true*/

/* depends:
 jxg
 utils/encoding
 */

define('utils/base64',['jxg', 'utils/encoding'], function (JXG, Encoding) {

    "use strict";

    var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/',
        pad = '=';

    // Util namespace
    JXG.Util = JXG.Util || {};

    // Local helper functions
    /**
     * Extracts one byte from a string and ensures the result is less than or equal to 255.
     * @param {String} s
     * @param {Number} i
     * @returns {Number} <= 255
     * @private
     */
    function _getByte(s, i) {
        return s.charCodeAt(i) & 0xff;
    }

    /**
     * Determines the index of a base64 character in the base64 alphabet.
     * @param {String} s
     * @param {Number} i
     * @returns {Number}
     * @throws {Error} If the character can not be found in the alphabet.
     * @private
     */
    function _getIndex(s, i) {
        return alphabet.indexOf(s.charAt(i));
    }

    /**
     * Base64 routines
     * @namespace
     */
    JXG.Util.Base64 = {
        /**
         * Encode the given string.
         * @param {String} input
         * @returns {string} base64 encoded version of the input string.
         */
        encode : function (input) {
            var i, bin, len, padLen, encInput,
                buffer = [];

            encInput =  Encoding.encode(input);
            len = encInput.length;
            padLen = len % 3;

            for (i = 0; i < len - padLen; i += 3) {
                bin = (_getByte(encInput, i) << 16) | (_getByte(encInput, i + 1) << 8) | (_getByte(encInput, i + 2));
                buffer.push(
                    alphabet.charAt(bin >> 18),
                    alphabet.charAt((bin >> 12) & 63),
                    alphabet.charAt((bin >> 6) & 63),
                    alphabet.charAt(bin & 63)
                );
            }

            switch (padLen) {
            case 1:
                bin = _getByte(encInput, len - 1);
                buffer.push(alphabet.charAt(bin >> 2), alphabet.charAt((bin << 4) & 63), pad, pad);
                break;
            case 2:
                bin = (_getByte(encInput, len - 2) << 8) | _getByte(encInput, len - 1);
                buffer.push(
                    alphabet.charAt(bin >> 10),
                    alphabet.charAt((bin >> 4) & 63),
                    alphabet.charAt((bin << 2) & 63),
                    pad
                );
                break;
            }

            return buffer.join('');
        },

        /**
         * Decode from Base64
         * @param {String} input Base64 encoded data
         * @param {Boolean} utf8 In case this parameter is true {@link JXG.Util.UTF8.decode} will be applied to
         * the result of the base64 decoder.
         * @throws {Error} If the string has the wrong length.
         * @returns {String}
         */
        decode : function (input, utf8) {
            var encInput, i, len, padLen, bin, output,
                result = [],
                buffer = [];

            // deactivate regexp linting. Our regex is secure, because we replace everything with ''
            /*jslint regexp:true*/
            encInput = input.replace(/[^A-Za-z0-9+/=]/g, '');
            /*jslint regexp:false*/

            len = encInput.length;

            if (len % 4 !== 0) {
                throw new Error('JSXGraph/utils/base64: Can\'t decode string (invalid input length).');
            }

            if (encInput.charAt(len - 1) === pad) {
                padLen = 1;

                if (encInput.charAt(len - 2) === pad) {
                    padLen = 2;
                }

                // omit the last four bytes (taken care of after the for loop)
                len -= 4;
            }

            for (i = 0; i < len; i += 4) {
                bin = (_getIndex(encInput, i) << 18) | (_getIndex(encInput, i + 1) << 12) | (_getIndex(encInput, i + 2) << 6) | _getIndex(encInput, i + 3);
                buffer.push(bin >> 16, (bin >> 8) & 255, bin & 255);

                // flush the buffer, if it gets too big fromCharCode will crash
                if (i % 10000 === 0) {
                    result.push(String.fromCharCode.apply(null, buffer));
                    buffer = [];
                }
            }

            switch (padLen) {
            case 1:
                bin = (_getIndex(encInput, len) << 12) | (_getIndex(encInput, len + 1) << 6) | (_getIndex(encInput, len + 2));
                buffer.push(bin >> 10, (bin >> 2) & 255);
                break;

            case 2:
                bin = (_getIndex(encInput, i) << 6) | (_getIndex(encInput, i + 1));
                buffer.push(bin >> 4);
                break;
            }

            result.push(String.fromCharCode.apply(null, buffer));
            output = result.join('');

            if (utf8) {
                output = Encoding.decode(output);
            }

            return output;
        },

        /**
         * Decode the base64 input data as an array
         * @param {string} input
         * @returns {Array}
         */
        decodeAsArray: function (input) {
            var i,
                dec = this.decode(input),
                ar = [],
                len = dec.length;

            for (i = 0; i < len; i++) {
                ar[i] = dec.charCodeAt(i);
            }

            return ar;
        }
    };

    return JXG.Util.Base64;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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
    
    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, escape:true, window:true, ActiveXObject:true, XMLHttpRequest:true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/zip
 utils/base64
 utils/type
 */

/**
 * @fileoverview The JXG.Server is a wrapper for a smoother integration of server side calculations. on the
 * server side a python plugin system is used.
 */

define('server/server',[
    'jxg', 'utils/zip', 'utils/base64', 'utils/type'
], function (JXG, Zip, Base64, Type) {

    "use strict";

    /**
     * @namespace
     * JXG.Server namespace holding functions to load JXG server modules.
     */
    JXG.Server = {
        /**
         * This is where all of a module's handlers are accessed from. If you're loading a module named JXGModule which
         * provides a handler called ImaHandler, then this handler can be called by invoking JXG.Server.modules.JXGModule.ImaHandler().
         * @namespace
         */
        modules: {},

        /**
         * Stores all asynchronous calls to server which aren't finished yet.
         * @private
         */
        runningCalls: {},

        /**
         * Handles errors, just a default implementation, can be overwritten by you, if you want to handle errors by yourself.
         * @param {object} data An object holding a field of type string named message handling the error described in the message string.
         */
        handleError: function (data) {
            JXG.debug('error occured, server says: ' + data.message);
        },

        /**
         * The main method of JXG.Server. Actually makes the calls to the server and parses the feedback.
         * @param {String} action Can be 'load' or 'exec'.
         * @param {function} callback Function pointer or anonymous function which takes as it's only argument an
         * object containing the data from the server. The fields of this object depend on the reply of the server
         * module. See the correspondings server module readme.
         * @param {Object} data What is to be sent to the server.
         * @param {Boolean} sync If the call should be synchronous or not.
         */
        callServer: function (action, callback, data, sync) {
            var fileurl, passdata, AJAX,
                params, id, dataJSONStr,
                k;

            sync = sync || false;

            params = '';
            for (k in data) {
                if (data.hasOwnProperty(k)) {
                    params += '&' + escape(k) + '=' + escape(data[k]);
                }
            }

            dataJSONStr = Type.toJSON(data);

            // generate id
            do {
                id = action + Math.floor(Math.random() * 4096);
            } while (Type.exists(this.runningCalls[id]));

            // store information about the calls
            this.runningCalls[id] = {action: action};
            if (Type.exists(data.module)) {
                this.runningCalls[id].module = data.module;
            }

            fileurl = JXG.serverBase + 'JXGServer.py';
            passdata = 'action=' + escape(action) + '&id=' + id + '&dataJSON=' + escape(Base64.encode(dataJSONStr));

            this.cbp = function (d) {
                /*jslint evil:true*/
                var str, data,
                    tmp, inject, paramlist, id,
                    i, j;

                str = (new Zip.Unzip(Base64.decodeAsArray(d))).unzip();
                if (Type.isArray(str) && str.length > 0) {
                    str = str[0][0];
                }

                if (!Type.exists(str)) {
                    return;
                }

                data = window.JSON && window.JSON.parse ? window.JSON.parse(str) : (new Function('return ' + str))();

                if (data.type === 'error') {
                    this.handleError(data);
                } else if (data.type === 'response') {
                    id = data.id;

                    // inject fields
                    for (i = 0; i < data.fields.length; i++) {
                        tmp = data.fields[i];
                        inject = tmp.namespace + (typeof ((new Function('return ' + tmp.namespace))()) === 'object' ? '.' : '.prototype.') + tmp.name + ' = ' + tmp.value;
                        (new Function(inject))();
                    }

                    // inject handlers
                    for (i = 0; i < data.handler.length; i++) {
                        tmp = data.handler[i];
                        paramlist = [];

                        for (j = 0; j < tmp.parameters.length; j++) {
                            paramlist[j] = '"' + tmp.parameters[j] + '": ' + tmp.parameters[j];
                        }
                        // insert subnamespace named after module.
                        inject = 'if(typeof JXG.Server.modules.' + this.runningCalls[id].module + ' == "undefined")' + 'JXG.Server.modules.' + this.runningCalls[id].module + ' = {};';

                        // insert callback method which fetches and uses the server's data for calculation in JavaScript
                        inject += 'JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + '_cb = ' + tmp.callback + ';';

                        // insert handler as JXG.Server.modules.<module name>.<handler name>
                        inject += 'JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + ' = function (' + tmp.parameters.join(',') + ', __JXGSERVER_CB__, __JXGSERVER_SYNC) {' +
                            'if(typeof __JXGSERVER_CB__ == "undefined") __JXGSERVER_CB__ = JXG.Server.modules.' + this.runningCalls[id].module + '.' + tmp.name + '_cb;' +
                            'var __JXGSERVER_PAR__ = {' + paramlist.join(',') + ', "module": "' + this.runningCalls[id].module + '", "handler": "' + tmp.name + '" };' +
                            'JXG.Server.callServer("exec", __JXGSERVER_CB__, __JXGSERVER_PAR__, __JXGSERVER_SYNC);' +
                            '};';
                        (new Function(inject))();
                    }

                    delete this.runningCalls[id];

                    // handle data
                    callback(data.data);
                }
            };

            // bind cbp callback method to JXG.Server to get access to JXG.Server fields from within cpb
            this.cb = JXG.bind(this.cbp, this);

            // We are using our own XMLHttpRequest object in here because of a/sync and POST
            if (window.XMLHttpRequest) {
                AJAX = new XMLHttpRequest();
                AJAX.overrideMimeType('text/plain; charset=iso-8859-1');
            } else {
                AJAX = new ActiveXObject("Microsoft.XMLHTTP");
            }
            if (AJAX) {
                // POST is required if data sent to server is too long for a url.
                // some browsers/http servers don't accept long urls.
                AJAX.open("POST", fileurl, !sync);
                AJAX.setRequestHeader("Content-type", "application/x-www-form-urlencoded");

                if (!sync) {
                    // Define function to fetch data received from server
                    // that function returning a function is required to make this.cb known to the function.
                    AJAX.onreadystatechange = (function (cb) {
                        return function () {
                            if (AJAX.readyState === 4 && AJAX.status === 200) {
                                cb(AJAX.responseText);
                                return true;
                            }
                            return false;
                        };
                    }(this.cb));
                }

                // send the data
                AJAX.send(passdata);
                if (sync) {
                    this.cb(AJAX.responseText);
                    return true;
                }
            }

            return false;
        },

        /**
         * Callback for the default action 'load'.
         */
        loadModule_cb: function (data) {
            var i;
            for (i = 0; i < data.length; i++) {
                JXG.debug(data[i].name + ': ' + data[i].value);
            }
        },

        /**
         * Loads a module from the server.
         * @param {string} module A string containing the module. Has to match the filename of the Python module on the server exactly including
         * lower and upper case letters without the file ending .py.
         */
        loadModule: function (module) {
            return JXG.Server.callServer('load', JXG.Server.loadModule_cb, {'module': module}, true);
        }
    };

    JXG.Server.load = JXG.Server.loadModule;

    return JXG.Server;
});
/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 base/coords
 math/math
 math/geometry
 server/server
 utils/type
 */

/**
 * @fileoverview In this file the namespace Math.Symbolic is defined, which holds methods
 * and algorithms for symbolic computations.
 * @author graphjs
 */

define('math/symbolic',[
    'base/constants', 'base/coords', 'math/math', 'math/geometry', 'server/server', 'utils/type'
], function (Const, Coords, Mat, Geometry, Server, Type) {

    "use strict";

    var undef;

    /**
     * The JXG.Math.Symbolic namespace holds algorithms for symbolic computations.
     * @name JXG.Math.Symbolic
     * @exports Mat.Symbolic as JXG.Math.Symbolic
     * @namespace
     */
    Mat.Symbolic = {
        /**
         * Generates symbolic coordinates for the part of a construction including all the elements from that
         * a specific element depends of. These coordinates will be stored in GeometryElement.symbolic.
         * @param {JXG.Board} board The board that's element get some symbolic coordinates.
         * @param {JXG.GeometryElement} element All ancestor of this element get symbolic coordinates.
         * @param {String} variable Name for the coordinates, e.g. x or u.
         * @param {String} append Method for how to append the number of the coordinates. Possible values are
         *                        'underscore' (e.g. x_2), 'none' (e.g. x2), 'brace' (e.g. x[2]).
         * @returns {Number} Number of coordinates given.
         * @memberof JXG.Math.Symbolic
         */
        generateSymbolicCoordinatesPartial: function (board, element, variable, append) {
            var t_num, t, k,
                list = element.ancestors,
                count = 0,
                makeCoords = function (num) {
                    var r;

                    if (append === 'underscore') {
                        r = variable + '_{' + num + '}';
                    } else if (append === 'brace') {
                        r = variable + '[' + num + ']';
                    } else {
                        r = variable + num;
                    }

                    return r;
                };

            board.listOfFreePoints = [];
            board.listOfDependantPoints = [];

            for (t in list) {
                if (list.hasOwnProperty(t)) {
                    t_num = 0;

                    if (Type.isPoint(list[t])) {
                        for (k in list[t].ancestors) {
                            if (list[t].ancestors.hasOwnProperty(k)) {
                                t_num++;
                            }
                        }

                        if (t_num === 0) {
                            list[t].symbolic.x = list[t].coords.usrCoords[1];
                            list[t].symbolic.y = list[t].coords.usrCoords[2];
                            board.listOfFreePoints.push(list[t]);
                        } else {
                            count += 1;
                            list[t].symbolic.x = makeCoords(count);
                            count += 1;
                            list[t].symbolic.y = makeCoords(count);
                            board.listOfDependantPoints.push(list[t]);
                        }

                    }
                }
            }

            if (Type.isPoint(element)) {
                element.symbolic.x = 'x';
                element.symbolic.y = 'y';
            }

            return count;
        },

        /**
         * Clears all .symbolic.x and .symbolic.y members on every point of a given board.
         * @param {JXG.Board} board The board that's points get cleared their symbolic coordinates.
         * @memberof JXG.Math.Symbolic
         */
        clearSymbolicCoordinates: function (board) {
            var clear = function (list) {
                    var t, l = (list && list.length) || 0;

                    for (t = 0; t < l; t++) {
                        if (Type.isPoint(list[t])) {
                            list[t].symbolic.x = '';
                            list[t].symbolic.y = '';
                        }
                    }
                };

            clear(board.listOfFreePoints);
            clear(board.listOfDependantPoints);

            delete (board.listOfFreePoints);
            delete (board.listOfDependantPoints);
        },

        /**
         * Generates polynomials for a part of the construction including all the points from that
         * a specific element depends of.
         * @param {JXG.Board} board The board that's points polynomials will be generated.
         * @param {JXG.GeometryElement} element All points in the set of ancestors of this element are used to generate the set of polynomials.
         * @param {Boolean} generateCoords
         * @returns {Array} An array of polynomials as strings.
         * @memberof JXG.Math.Symbolic
         */
        generatePolynomials: function (board, element, generateCoords) {
            var t, k, i,
                list = element.ancestors,
                number_of_ancestors,
                pgs = [],
                result = [];

            if (generateCoords) {
                this.generateSymbolicCoordinatesPartial(board, element, 'u', 'brace');
            }

            list[element.id] = element;

            for (t in list) {
                if (list.hasOwnProperty(t)) {
                    number_of_ancestors = 0;
                    pgs = [];

                    if (Type.isPoint(list[t])) {
                        for (k in list[t].ancestors) {
                            if (list[t].ancestors.hasOwnProperty(k)) {
                                number_of_ancestors++;
                            }
                        }
                        if (number_of_ancestors > 0) {
                            pgs = list[t].generatePolynomial();

                            for (i = 0; i < pgs.length; i++) {
                                result.push(pgs[i]);
                            }
                        }
                    }
                }
            }

            if (generateCoords) {
                this.clearSymbolicCoordinates(board);
            }

            return result;
        },

        /**
         * Calculate geometric locus of a point given on a board. Invokes python script on server.
         * @param {JXG.Board} board The board on which the point lies.
         * @param {JXG.Point} point The point that will be traced.
         * @returns {Array} An array of points.
         * @memberof JXG.Math.Symbolic
         */
        geometricLocusByGroebnerBase: function (board, point) {
            var poly, polyStr, result,
                P1, P2, i,
                xs, xe, ys, ye,
                c, s, tx,
                bol = board.options.locus,
                oldRadius = {},
                numDependent = this.generateSymbolicCoordinatesPartial(board, point, 'u', 'brace'),
                xsye = new Coords(Const.COORDS_BY_USR, [0, 0], board),
                xeys = new Coords(Const.COORDS_BY_USR, [board.canvasWidth, board.canvasHeight], board),
                sf = 1, transx = 0, transy = 0, rot = 0;

            if (Server.modules.geoloci === undef) {
                Server.loadModule('geoloci');
            }

            if (Server.modules.geoloci === undef) {
                throw new Error("JSXGraph: Unable to load JXG.Server module 'geoloci.py'.");
            }

            xs = xsye.usrCoords[1];
            xe = xeys.usrCoords[1];
            ys = xeys.usrCoords[2];
            ye = xsye.usrCoords[2];

            // Optimizations - but only if the user wants to
            //   Step 1: Translate all related points, such that one point P1 (board.options.locus.toOrigin if set
            //     or a random point otherwise) is moved to (0, 0)
            //   Step 2: Rotate the construction around the new P1, such that another point P2 (board.options.locus.to10 if set
            //     or a random point \neq P1 otherwise) is moved onto the positive x-axis
            //  Step 3: Dilate the construction, such that P2 is moved to (1, 0)
            //  Step 4: Give the scale factor (sf), the rotation (rot) and the translation vector (transx, transy) to
            //    the server, which retransforms the plot (if any).

            // Step 1
            if (bol.translateToOrigin && (board.listOfFreePoints.length > 0)) {
                if ((bol.toOrigin !== undef) && (bol.toOrigin !== null) && Type.isInArray(board.listOfFreePoints, bol.toOrigin.id)) {
                    P1 = bol.toOrigin;
                } else {
                    P1 = board.listOfFreePoints[0];
                }

                transx = P1.symbolic.x;
                transy = P1.symbolic.y;
                // translate the whole construction
                for (i = 0; i < board.listOfFreePoints.length; i++) {
                    board.listOfFreePoints[i].symbolic.x -= transx;
                    board.listOfFreePoints[i].symbolic.y -= transy;
                }

                xs -= transx;
                xe -= transx;
                ys -= transy;
                ye -= transy;

                // Step 2
                if (bol.translateTo10 && (board.listOfFreePoints.length > 1)) {
                    if ((bol.to10 !== undef) && (bol.to10 !== null) && (bol.to10.id !== bol.toOrigin.id) && Type.isInArray(board.listOfFreePoints, bol.to10.id)) {
                        P2 = bol.to10;
                    } else {
                        if (board.listOfFreePoints[0].id === P1.id) {
                            P2 = board.listOfFreePoints[1];
                        } else {
                            P2 = board.listOfFreePoints[0];
                        }
                    }

                    rot = Geometry.rad([1, 0], [0, 0], [P2.symbolic.x, P2.symbolic.y]);
                    c = Math.cos(-rot);
                    s = Math.sin(-rot);


                    for (i = 0; i < board.listOfFreePoints.length; i++) {
                        tx = board.listOfFreePoints[i].symbolic.x;
                        board.listOfFreePoints[i].symbolic.x = c * board.listOfFreePoints[i].symbolic.x - s * board.listOfFreePoints[i].symbolic.y;
                        board.listOfFreePoints[i].symbolic.y = s * tx + c * board.listOfFreePoints[i].symbolic.y;
                    }

                    // thanks to the rotation this is zero
                    P2.symbolic.y = 0;

                    tx = xs;
                    xs = c * xs - s * ys;
                    ys = s * tx + c * ys;
                    tx = xe;
                    xe = c * xe - s * ye;
                    ye = s * tx + c * ye;

                    // Step 3
                    if (bol.stretch && (Math.abs(P2.symbolic.x) > Mat.eps)) {
                        sf = P2.symbolic.x;

                        for (i = 0; i < board.listOfFreePoints.length; i++) {
                            board.listOfFreePoints[i].symbolic.x /= sf;
                            board.listOfFreePoints[i].symbolic.y /= sf;
                        }

                        for (i = 0; i < board.objectsList.length; i++) {
                            if ((board.objectsList[i].elementClass === Const.OBJECT_CLASS_CIRCLE) && (board.objectsList[i].method === 'pointRadius')) {
                                oldRadius[i] = board.objectsList[i].radius;
                                board.objectsList[i].radius /= sf;
                            }
                        }

                        xs /= sf;
                        xe /= sf;
                        ys /= sf;
                        ye /= sf;

                        // this is now 1
                        P2.symbolic.x = 1;
                    }
                }

                // make the coordinates "as rational as possible"
                for (i = 0; i < board.listOfFreePoints.length; i++) {
                    tx = board.listOfFreePoints[i].symbolic.x;

                    if (Math.abs(tx) < Mat.eps) {
                        board.listOfFreePoints[i].symbolic.x = 0;
                    }

                    if (Math.abs(tx - Math.round(tx)) < Mat.eps) {
                        board.listOfFreePoints[i].symbolic.x = Math.round(tx);
                    }

                    tx = board.listOfFreePoints[i].symbolic.y;

                    if (Math.abs(tx) < Mat.eps) {
                        board.listOfFreePoints[i].symbolic.y = 0;
                    }

                    if (Math.abs(tx - Math.round(tx)) < Mat.eps) {
                        board.listOfFreePoints[i].symbolic.y = Math.round(tx);
                    }
                }
            }

            // end of optimizations

            poly = this.generatePolynomials(board, point);
            polyStr = poly.join(',');

            this.cbp = function (data) {
                result = data;
            };

            this.cb = Type.bind(this.cbp, this);

            Server.modules.geoloci.lociCoCoA(xs, xe, ys, ye, numDependent, polyStr, sf, rot, transx, transy, this.cb, true);

            this.clearSymbolicCoordinates(board);

            for (i in oldRadius) {
                if (oldRadius.hasOwnProperty(i)) {
                    board.objects[i].radius = oldRadius[i];
                }
            }


            return result;
        }
    };

    return Mat.Symbolic;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Alfred Wassermann
console.log("P:", P.coords.usrCoords, P.data.type)

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 base/coords
 math/math
 math/numerics
 math/geometry
 utils/type
 */

/**
 * @fileoverview This file contains the Math.Clip namespace for clipping and computing boolean operations
 * on polygons and curves
 *
 * // TODO:
 * * Check if input polygons are closed. If not, handle this case.
 */

define('math/clip',[
    'jxg', 'base/constants', 'base/coords', 'math/math', 'math/geometry', 'utils/type'
], function (JXG, Const, Coords, Mat, Geometry, Type) {

    "use strict";

    /**
     * Math.Clip namespace definition. This namespace contains algorithms for Boolean operations on paths, i.e.
     * intersection, union and difference of paths. Base is the Greiner-Hormann algorithm.
     * @name JXG.Math.Clip
     * @exports Mat.Clip as JXG.Math.Clip
     * @namespace
     */
    // Mat.Clip = function () {
    // };

    // JXG.extend(Mat.Clip.prototype, /** @lends JXG.Curve.prototype */ {

    Mat.Clip = {

        _isSeparator: function(node) {
            return isNaN(node.coords.usrCoords[1]) && isNaN(node.coords.usrCoords[2]);
        },

        /**
         * Add pointers to an array S such that it is a circular doubly-linked list.
         *
         * @private
         * @param  {Array} S Array
         * @return {Array} return containing the starter indices of each component.
         */
        makeDoublyLinkedList: function(S) {
            var i,
                first = null,
                components = [],
                le = S.length;

            if (le > 0) {
                for (i = 0; i < le; i++) {
                    // S[i]._next = S[(i + 1) % le];
                    // S[i]._prev = S[(le + i - 1) % le];

                    // If S[i] is component separator we proceed with the next node.
                    if (this._isSeparator(S[i])) {
                        S[i]._next = S[(i + 1) % le];
                        S[i]._prev = S[(le + i - 1) % le];
                        continue;
                    }

                    // Now we know that S[i] is a path component
                    if (first === null) {
                        // Start the component if it is not yet started.
                        first = i;
                        components.push(first);
                    }
                    if (this._isSeparator(S[(i + 1) % le]) || i === le - 1) {
                        // If the next node is a component separator or if the node is the last node,
                        // then we close the loop

                        S[i]._next = S[first];
                        S[first]._prev = S[i];
                        S[i]._end = true;
                        first = null;
                    } else {
                        // Here, we are not at the end of component
                        S[i]._next = S[(i + 1) % le];
                        S[first]._prev = S[i];
                    }
                    if (!this._isSeparator(S[(le + i - 1) % le])) {
                        S[i]._prev = S[(le + i - 1) % le];
                    }
                }
            }
            return components;
        },

        /**
         * Determinant of three points in the Euclidean plane.
         * Zero, if the points are collinear. Used to determine of a point q is left or
         * right to a segment defined by points p1 and p2.
         * @private
         * @param  {Array} p1 Coordinates of the first point of the segment. Array of length 3. First coordinate is equal to 1.
         * @param  {Array} p2 Coordinates of the second point of the segment. Array of length 3. First coordinate is equal to 1.
         * @param  {Array} q Coordinates of the point. Array of length 3. First coordinate is equal to 1.
         * @return {Number} Signed area of the triangle formed by these three points.
         */
        det: function(p1, p2, q) {
            return (p1[1] - q[1]) * (p2[2] - q[2]) - (p2[1] - q[1]) * (p1[2] - q[2]);
        },

        /**
         * Winding number of a point in respect to a polygon path.
         *
         * The point is regarded outside if the winding number is zero,
         * inside otherwise. The algorithm tries to find degenerate cases, i.e.
         * if the point is on the path. This is regarded as "outside".
         * If the point is a vertex of the path, it is regarded as "inside".
         *
         * Implementation of algorithm 7 from "The point in polygon problem for
         * arbitrary polygons" by Kai Hormann and Alexander Agathos, Computational Geometry,
         * Volume 20, Issue 3, November 2001, Pages 131-144.
         *
         * @param  {Array} usrCoords Homogenous coordinates of the point
         * @param  {Array} path      Array of points determining a path, i.e. the vertices of the polygon. The array elements
         * do not have to be full points, but have to have a subobject "coords".
         * @return {Number}          Winding number of the point. The point is
         *                           regarded outside if the winding number is zero,
         *                           inside otherwise.
         */
        windingNumber: function(usrCoords, path) {
            var wn = 0,
                le = path.length,
                x = usrCoords[1],
                y = usrCoords[2],
                p1, p2, d, sign, i;

            if (le === 0) {
                return 0;
            }

            // Infinite points are declared outside
            if (isNaN(x) || isNaN(y)) {
                return 1;
            }

            // Handle the case if the point is a vertex of the path
            if (path[0].coords.usrCoords[1] === x &&
                path[0].coords.usrCoords[2] === y) {

                // console.log('<<<<<<< Vertex 1');
                return 1;
            }

            for (i = 0; i < le; i++) {
                // Consider the edge from p1 = path[i] to p2 = path[i+1]
                p1 = path[i].coords.usrCoords;
                p2 = path[(i + 1) % le].coords.usrCoords;
                if (p1[0] === 0 || p2[0] === 0 ||
                    isNaN(p1[1]) || isNaN(p2[1]) ||
                    isNaN(p1[2]) || isNaN(p2[2])) {

                    continue;
                }

                if (p2[2] === y) {
                    if (p2[1] === x) {
                        // console.log('<<<<<<< Vertex 2');
                        return 1;
                    }
                    if (p1[2] === y && ((p2[1] > x) === (p1[1] < x))) {
                        // console.log('<<<<<<< Edge 1', p1, p2, [x, y]);
                        return 0;
                    }
                }

                if ((p1[2] < y) !== (p2[2] < y)) {
                    sign = 2 * ((p2[2] > p1[2]) ? 1 : 0) - 1;
                    if (p1[1] >= x) {
                        if (p2[1] > x) {
                            wn += sign;
                        } else {
                            d = this.det(p1, p2, usrCoords);
                            if (d === 0) {
                                // console.log('<<<<<<< Edge 2');
                                return 0;
                            }
                            if ((d > 0) === (p2[2] > p1[2])) {
                                wn += sign;
                            }
                        }
                    } else {
                        if (p2[1] > x) {
                            d = this.det(p1, p2, usrCoords);
                            if ((d > 0 + Mat.eps) === (p2[2] > p1[2])) {
                                wn += sign;
                            }
                        }
                    }
                }
            }

            return wn;
        },

        /**
         * JavaScript object containing the intersection of two paths. Every intersection point is on one path, but
         * comes with a neighbour point having the same coordinates and being on the other path.
         *
         * The intersection point is inserted into the doubly linked list of the path.
         *
         * @private
         * @param  {JXG.Coords} coords JSXGraph Coords object conatining the coordinates of the intersection
         * @param  {Number} i        Number of the segment of the subject path (first path) containing the intersection.
         * @param  {Number} alpha    The intersection is a p_1 + alpha*(p_2 - p_1), where p_1 and p_2 are the end points
         *      of the i-th segment.
         * @param  {Array} path      Pointer to the path containing the intersection point
         * @param  {String} pathname Name of the path: 'S' or 'C'.
         */
        Vertex: function(coords, i, alpha, path, pathname, type) {
            this.pos = i;
            this.intersection = true;
            this.coords = coords;
            this.elementClass = Const.OBJECT_CLASS_POINT;

            this.data = {
                alpha: alpha,
                path: path,
                pathname: pathname,
                done: false,
                type: type,
                idx: 0
            };

            // Set after initialisation
            this.neighbour = null;
            this.entry_exit = false;
        },

        _addToList: function(list, coords, pos) {
            var len = list.length,
                eps = Mat.eps * Mat.eps;

            if (len > 0 &&
                Math.abs(list[len - 1].coords.usrCoords[0] - coords.usrCoords[0]) < eps &&
                Math.abs(list[len - 1].coords.usrCoords[1] - coords.usrCoords[1]) < eps &&
                Math.abs(list[len - 1].coords.usrCoords[2] - coords.usrCoords[2]) < eps) {
                // Skip point
                return;
            }
            list.push({
                pos: pos,
                intersection: false,
                coords: coords,
                elementClass: Const.OBJECT_CLASS_POINT
            });
        },

        /**
         * Sort the intersection points into their path.
         * @private
         * @param  {Array} P_crossings Array of arrays. Each array contains the intersections of the path
         *      with one segment of the other path.
         * @return {Array}  Array of intersection points ordered by first occurrence in the path.
         */
        sortIntersections: function(P_crossings) {
            var i, j, P, Q,
                last,
                next_node,
                P_intersect = [],
                P_le = P_crossings.length;

            for (i = 0; i < P_le; i++) {
                P_crossings[i].sort(function(a, b) { return (a.data.alpha > b.data.alpha) ? 1 : -1; });

                if (P_crossings[i].length > 0) {
                    // console.log("Crossings", P_crossings[i])
                    last = P_crossings[i].length - 1;
                    P = P_crossings[i][0];

                    //console.log("SORT", P.coords.usrCoords)
                    Q =  P.data.path[P.pos];
                    next_node = Q._next;  // Store the next "normal" node

                    if (i === P_le - 1) {
                        Q._end = false;
                    }

                    if (P.data.alpha === 0.0 && P.data.type === 'T') {
                        // console.log("SKIP", P.coords.usrCoords, P.data.type, P.neighbour.data.type);
                        Q.intersection = true;
                        Q.data = P.data;
                        Q.neighbour = P.neighbour;
                        Q.neighbour.neighbour = Q;
                        Q.entry_exit = false;
                        P_crossings[i][0] = Q;
                    } else {
                        // Insert the first intersection point
                        P._prev = Q;
                        P._prev._next = P;
                    }

                    // Insert the other intersection points, but the last
                    for (j = 1; j <= last; j++) {
                        P = P_crossings[i][j];
                        P._prev = P_crossings[i][j - 1];
                        P._prev._next = P;
                    }

                    // Link last intersection point to the next node
                    P = P_crossings[i][last];
                    P._next = next_node;
                    P._next._prev = P;

                    if (i === P_le - 1) {
                        P._end = true;
                        //console.log("END", P._end, P.coords.usrCoords, P._prev.coords.usrCoords, P._next.coords.usrCoords);
                    }

                    P_intersect = P_intersect.concat(P_crossings[i]);
                }
            }
            return P_intersect;
        },

        _inbetween: function(q, p1, p2) {
            var alpha,
                eps = Mat.eps * Mat.eps,
                px = p2[1] - p1[1],
                py = p2[2] - p1[2],
                qx = q[1]  - p1[1],
                qy = q[2]  - p1[2];

            if (px === 0 && py === 0 && qx === 0 && qy === 0) {
                // All three points are equal
                return true;
            }
            if (Math.abs(qx) < eps && Math.abs(px) < eps) {
                alpha = qy / py;
            } else {
                alpha = qx / px;
            }
            if (Math.abs(alpha) < eps) {
                alpha = 0.0;
            }
            return alpha;
        },

        _print_array: function(arr) {
            var i, end;
            for (i = 0; i < arr.length; i++) {
                //console.log(i, arr[i].coords.usrCoords,  arr[i].data.type);
                try {
                    end = "";
                    if (arr[i]._end) {
                        end = " end";
                    }
                    console.log(i, arr[i].coords.usrCoords, arr[i].data.type, "\t",
                                "prev", arr[i]._prev.coords.usrCoords,
                                "next", arr[i]._next.coords.usrCoords + end);
                } catch (e) {
                    console.log(i, arr[i].coords.usrCoords);
                }
            }
        },

        _print_list: function(P) {
            var cnt = 0, alpha;
            while (cnt < 100) {
                if (P.data) {
                    alpha = P.data.alpha;
                } else {
                    alpha = '-';
                }
                console.log("\t", P.coords.usrCoords, "\n\t\tis:", P.intersection, "end:", P._end,
                            alpha,
                            "\n\t\t-:", P._prev.coords.usrCoords,
                            "\n\t\t+:", P._next.coords.usrCoords,
                            "\n\t\tn:", (P.intersection) ? P.neighbour.coords.usrCoords : '-'
                            );
                if (P._end) {
                    break;
                }
                P = P._next;
                cnt++;
            }
        },

        _noOverlap: function(p1, p2, q1, q2) {
            var k,
                eps = Math.sqrt(Mat.eps),
                minp, maxp, minq, maxq,
                no_overlap = false;

            for (k = 0; k < 3; k++) {
                minp = Math.min(p1[k], p2[k]);
                maxp = Math.max(p1[k], p2[k]);
                minq = Math.min(q1[k], q2[k]);
                maxq = Math.max(q1[k], q2[k]);
                if (maxp < minq - eps || minp > maxq + eps) {
                    no_overlap = true;
                    break;
                }
            }
            return no_overlap;
        },

        /**
         * Find all intersections between two paths.
         * @private
         * @param  {Array} S     Subject path
         * @param  {Array} C     Clip path
         * @param  {JXG.Board} board JSXGraph board object. It is needed to convert between
         * user coordinates and screen coordinates.
         * @return {Array}  Array containing two arrays. The first array contains the intersection vertices
         * of the subject path and the second array contains the intersection vertices of the clip path.
         * @see JXG.Clip#Vertex
         */
        findIntersections: function(S, C, board) {
            var res = [],
                eps = Mat.eps,
                i, j,
                crds,
                S_le = S.length,
                C_le = C.length,
                Si, Si1, Cj, Cj1,
                d1, d2,
                alpha,
                type,
                IS, IC,
                S_intersect = [],
                C_intersect = [],
                S_crossings = [],
                C_crossings = [],
                hasMultCompsS = false,
                hasMultCompsC = false,
                DEBUG = false;

            for (j = 0; j < C_le; j++) {
                C_crossings.push([]);
            }

            // Run through the subject path.
            for (i = 0; i < S_le; i++) {
                S_crossings.push([]);

                // Test if S[i] or its successor is a path separator.
                // If yes, we know that the path consists of multiple components.
                // We immediately jump to the next segment.
                if (this._isSeparator(S[i]) || this._isSeparator(S[(i + 1) % S_le])) {
                    hasMultCompsS = true;
                    continue;
                }

                // If the path consists of multiple components then there is
                // no path-closing segment between the last node and the first
                // node. In this case we can leave the loop now.
                if (hasMultCompsS && i === S_le - 1) {
                    break;
                }

                Si = S[i].coords.usrCoords;
                Si1 = S[(i + 1) % S_le].coords.usrCoords;
                // Run through the clip path.
                for (j = 0; j < C_le; j++) {
                    // Test if C[j] or its successor is a path separator.
                    // If yes, we know that the path consists of multiple components.
                    // We immediately jump to the next segment.
                    if (this._isSeparator(C[j]) || this._isSeparator(C[(j + 1) % C_le])) {
                        hasMultCompsC = true;
                        continue;
                    }

                    // If the path consists of multiple components then there is
                    // no path-closing segment between the last node and the first
                    // node. In this case we can leave the loop now.
                    if (hasMultCompsC && j === C_le - 1) {
                        break;
                    }

                    // Test if bounding boxes of the two curve segments overlap
                    // If not, the expensive intersection test can be skipped.
                    Cj  = C[j].coords.usrCoords;
                    Cj1 = C[(j + 1) % C_le].coords.usrCoords;

                    if (this._noOverlap(Si, Si1, Cj, Cj1)) {
                        continue;
                    }

                    // Intersection test
                    res = Geometry.meetSegmentSegment(Si, Si1, Cj, Cj1);

                    d1 = Geometry.distance(Si, Si1, 3);
                    d2 = Geometry.distance(Cj, Cj1, 3);

                    // Found an intersection point
                    if ( // "Regular" intersection
                        (res[1] * d1 > -eps && res[1] < 1 - eps / d1 && res[2] * d2 > -eps && res[2] < 1 - eps / d2) ||
                        // Collinear segments
                        (res[1] === Infinity && res[2] === Infinity && Mat.norm(res[0], 3) < eps)
                        ) {

                        crds = new Coords(Const.COORDS_BY_USER, res[0], board);
                        type = 'X';

                        // Handle degenerated cases
                        if (Math.abs(res[1]) * d1 < eps || Math.abs(res[2]) * d2 < eps) {
                            // Crossing / bouncing at vertex or
                            // end of delayed crossing / bouncing
                            type  = 'T';
                            if (Math.abs(res[1]) * d1 < eps) {
                                res[1] = 0;
                            }
                            if (Math.abs(res[2]) * d2 < eps) {
                                res[2] = 0;
                            }
                            if (res[1] === 0) {
                                crds = new Coords(Const.COORDS_BY_USER, Si, board);
                            } else {
                                crds = new Coords(Const.COORDS_BY_USER, Cj, board);
                            }

                            if (DEBUG) {
                                console.log("Degenerate case I", res[1], res[2], crds.usrCoords, "type", type);
                            }
                        } else if (res[1] === Infinity &&
                                   res[2] === Infinity &&
                                   Mat.norm(res[0], 3) < eps) {                // console.log(C_intersect);


                            // Collinear segments
                            // Here, there might be two intersection points to be added

                            alpha = this._inbetween(Si, Cj, Cj1);
                            if (DEBUG) {
                                // console.log("alpha Si", alpha, Si);
                                // console.log(j, Cj)
                                // console.log((j + 1) % C_le, Cj1)
                            }
                            if (alpha >= 0 && alpha < 1) {
                                type = 'T';
                                crds = new Coords(Const.COORDS_BY_USER, Si, board);
                                res[1] = 0;
                                res[2] = alpha;
                                IS = new this.Vertex(crds, i, res[1], S, 'S', type);
                                IC = new this.Vertex(crds, j, res[2], C, 'C', type);
                                IS.neighbour = IC;
                                IC.neighbour = IS;
                                S_crossings[i].push(IS);
                                C_crossings[j].push(IC);
                                if (DEBUG) {
                                    console.log("Degenerate case II", res[1], res[2], crds.usrCoords, "type T");
                                }
                            }
                            alpha = this._inbetween(Cj, Si, Si1);
                            if (DEBUG) {
                                // console.log("alpha Cj", alpha, Si, Geometry.distance(Si, Cj, 3));
                            }
                            if (Geometry.distance(Si, Cj, 3) > eps &&
                                alpha >= 0 && alpha < 1) {

                                type = 'T';
                                crds = new Coords(Const.COORDS_BY_USER, Cj, board);
                                res[1] = alpha;
                                res[2] = 0;
                                IS = new this.Vertex(crds, i, res[1], S, 'S', type);
                                IC = new this.Vertex(crds, j, res[2], C, 'C', type);
                                IS.neighbour = IC;
                                IC.neighbour = IS;
                                S_crossings[i].push(IS);
                                C_crossings[j].push(IC);
                                if (DEBUG) {
                                    console.log("Degenerate case III", res[1], res[2], crds.usrCoords, "type T");
                                }
                            }
                            continue;
                        }
                        if (DEBUG) {
                            console.log("IS", i, j, crds.usrCoords, type);
                        }

                        IS = new this.Vertex(crds, i, res[1], S, 'S', type);
                        IC = new this.Vertex(crds, j, res[2], C, 'C', type);
                        IS.neighbour = IC;
                        IC.neighbour = IS;

                        S_crossings[i].push(IS);
                        C_crossings[j].push(IC);
                    }
                }
            }

            // For both paths, sort their intersection points
            S_intersect = this.sortIntersections(S_crossings);

            if (DEBUG) {
                console.log('>>>>>> Intersections ');
                console.log("S_intersect");
                this._print_array(S_intersect);
                console.log('----------');
            }
            for (i = 0; i < S_intersect.length; i++) {
                S_intersect[i].data.idx = i;
                S_intersect[i].neighbour.data.idx = i;
            }
            C_intersect = this.sortIntersections(C_crossings);

            if (DEBUG) {
                console.log("C_intersect");
                this._print_array(C_intersect);
                console.log('<<<<<< Phase 1 done');
            }
            return [S_intersect, C_intersect];
        },

        /**
         * It is testedd if the point q lies to the left or right
         * of the poylgonal chain [p1, p2, p3].
         * @param {Array} q User coords array
         * @param {Array} p1 User coords array
         * @param {Array} p2 User coords array
         * @param {Array} p3 User coords array
         * @returns string 'left' or 'right'
         * @private
         */
        _getPosition: function(q, p1, p2, p3) {
            var s1 = this.det(q, p1, p2),
                s2 = this.det(q, p2, p3),
                s3 = this.det(p1, p2, p3);

            // Left turn
            if (s3 >= 0) {
                if (s1 >= 0 && s2 >= 0) {
                    return 'left';
                }
                return 'right';
            }
            // Right turn
            if (s1 >= 0 || s2 >= 0) {
                return 'left';
            }
            return 'right';
        },

        /**
         * Determine the delayed status of degenerated intersection points.
         * It is of the form
         *   ['on|left|right', 'on|left|right']
         * <p>
         * If all four determinants are zero, we add random noise to the point.
         *
         * @param {JXG.Math.Clip.Vertex} P Start of path
         * @private
         * @see JXG.Math.Clip#markEntryExit
         * @see JXG.Math.Clip#_handleIntersectionChains
         */
        _classifyDegenerateIntersections: function(P) {
            var Pp, Pm, Qp, Qm, Q, side,
                cnt, tmp,
                oppositeDir,
                s1, s2, s3, s4,
                DEBUG = false;

            if (DEBUG) {
                console.log("\n-------------- _classifyDegenerateIntersections()", (Type.exists(P.data))?P.data.pathname:' ');
            }
            cnt = 0;
            P._tours = 0;
            while (true) {
                if (DEBUG) {
                    console.log("Inspect P:", P.coords.usrCoords, (P.data) ? P.data.type : " ");
                }
                if (P.intersection && (P.data.type === 'T')) {

                    // Handle the degenerate cases
                    // Decide if they are (delayed) bouncing or crossing intersections
                    Pp = P._next.coords.usrCoords;  // P+
                    Pm = P._prev.coords.usrCoords;  // P-

                    // If the intersection point is degenerated and
                    // equal to the start and end of one component,
                    // then there will be two adjacent points with
                    // the same coordinate.
                    // In that case, we proceed to the next node.
                    if (Geometry.distance(P.coords.usrCoords, Pp, 3) < Mat.eps) {
                        Pp = P._next._next.coords.usrCoords;
                    }
                    if (Geometry.distance(P.coords.usrCoords, Pm, 3) < Mat.eps) {
                        Pm = P._prev._prev.coords.usrCoords;
                    }

                    Q = P.neighbour;
                    Qm = Q._prev.coords.usrCoords;  // Q-
                    Qp = Q._next.coords.usrCoords;  // Q+
                    if (Geometry.distance(Q.coords.usrCoords, Qp, 3) < Mat.eps) {
                        Qp = Q._next._next.coords.usrCoords;
                    }
                    if (Geometry.distance(Q.coords.usrCoords, Qm, 3) < Mat.eps) {
                        Qm = Q._prev._prev.coords.usrCoords;
                    }

                    if (DEBUG) {
                        console.log("P chain:", Pm, P.coords.usrCoords, Pp);
                        console.log("Q chain:", Qm, P.neighbour.coords.usrCoords, Qp);
                        console.log("Pm", this._getPosition(Pm,  Qm, Q.coords.usrCoords, Qp));
                        console.log("Pp", this._getPosition(Pp,  Qm, Q.coords.usrCoords, Qp));
                    }

                    s1 = this.det(P.coords.usrCoords, Pm, Qm);
                    s2 = this.det(P.coords.usrCoords, Pp, Qp);
                    s3 = this.det(P.coords.usrCoords, Pm, Qp);
                    s4 = this.det(P.coords.usrCoords, Pp, Qm);

                    if (s1 === 0 && s2 === 0 && s3 === 0 && s4 === 0) {
                        P.coords.usrCoords[1] *= 1 + Math.random() * Mat.eps;
                        P.coords.usrCoords[2] *= 1 + Math.random() * Mat.eps;
                        Q.coords.usrCoords[1] = P.coords.usrCoords[1];
                        Q.coords.usrCoords[2] = P.coords.usrCoords[2];
                        s1 = this.det(P.coords.usrCoords, Pm, Qm);
                        s2 = this.det(P.coords.usrCoords, Pp, Qp);
                        s3 = this.det(P.coords.usrCoords, Pm, Qp);
                        s4 = this.det(P.coords.usrCoords, Pp, Qm);
                        if (DEBUG) {
                            console.log("Random shift", P.coords.usrCoords);
                            console.log(s1, s2, s3, s4, s2 === 0);
                            console.log(this._getPosition(Pm,  Qm, Q.coords.usrCoords, Qp),
                                this._getPosition(Pp,  Qm, Q.coords.usrCoords, Qp));
                        }
                    }
                    oppositeDir = false;
                    if (s1 === 0) {
                        // Q-, Q=P, P- on straight line
                        if (Geometry.affineRatio(P.coords.usrCoords, Pm, Qm) < 0) {
                            oppositeDir = true;
                        }
                    } else if (s2 === 0) {
                        if (Geometry.affineRatio(P.coords.usrCoords, Pp, Qp) < 0) {
                            oppositeDir = true;
                        }
                    } else if (s3 === 0) {
                        if (Geometry.affineRatio(P.coords.usrCoords, Pm, Qp) > 0) {
                            oppositeDir = true;
                        }
                    } else if (s4 === 0) {
                        if (Geometry.affineRatio(P.coords.usrCoords, Pp, Qm) > 0) {
                            oppositeDir = true;
                        }
                    }
                    if (oppositeDir) {
                        // Swap Qm and Qp
                        // Then Qm Q Qp has the same direction as Pm P Pp
                        tmp = Qm; Qm = Qp; Qp = tmp;
                        tmp = s1; s1 = s3; s3 = tmp;
                        tmp = s2; s2 = s4; s4 = tmp;
                    }

                    if (DEBUG) {
                        console.log(s1, s2, s3, s4, oppositeDir);
                    }

                    if (!Type.exists(P.delayedStatus)) {
                        P.delayedStatus = [];
                    }

                    if (s1 === 0 && s2 === 0) {
                        // Line [P-,P] equals [Q-,Q] and line [P,P+] equals [Q,Q+]
                        // Interior of delayed crossing / bouncing
                        P.delayedStatus = ['on', 'on'];

                    } else if (s1 === 0) {
                        // P- on line [Q-,Q], P+ not on line [Q,Q+]
                        // Begin / end of delayed crossing / bouncing
                        side = this._getPosition(Pp,  Qm, Q.coords.usrCoords, Qp);
                        P.delayedStatus = ['on', side];

                    } else if (s2 === 0) {
                        // P+ on line [Q,Q+], P- not on line [Q-,Q]
                        // Begin / end of delayed crossing / bouncing
                        side = this._getPosition(Pm,  Qm, Q.coords.usrCoords, Qp);
                        P.delayedStatus = [side, 'on'];

                    } else {
                        // Neither P+ on line [Q,Q+], nor P- on line [Q-,Q]
                        // No delayed crossing / bouncing
                        if (P.delayedStatus.length === 0) {
                            if (this._getPosition(Pm,  Qm, Q.coords.usrCoords, Qp) !== this._getPosition(Pp,  Qm, Q.coords.usrCoords, Qp)) {
                                P.data.type = 'X';
                            } else {
                                P.data.type = 'B';
                            }
                        }
                    }

                    if (DEBUG) {
                        console.log(">>>> P:", P.coords.usrCoords, "delayedStatus:", P.delayedStatus.toString(), (P.data) ? P.data.type : " ", "\n---");
                    }

                }

                if (Type.exists(P._tours)) {
                    P._tours++;
                }

                if (P._tours > 3 || P._end || cnt > 1000) {
                    // Jump out if either
                    // - we reached the end
                    // - there are more than 1000 intersection points
                    // - P._tours > 3: We went already 4 times through this path.
                    if (cnt > 1000) {
                        console.log("Clipping: _classifyDegenerateIntersections exit");
                    }
                    if (Type.exists(P._tours)) {
                        delete P._tours;
                    }
                    break;
                }
                if (P.intersection) {
                    cnt++;
                }
                P = P._next;
            }
            if (DEBUG) {
                console.log("------------------------");
            }
        },

        /**
         * At this point the degenerated intersections have been classified.
         * Now we decide if the intersection chains of the given path
         * ultimatively cross the other path or bounce.
         *
         * @param {JXG.Math.Clip.Vertex} P Start of path
         *
         * @see JXG.Math.Clip#markEntryExit
         * @see JXG.Math.Clip#_classifyDegenerateIntersections
         * @private
         */
        _handleIntersectionChains: function(P) {
            var cnt = 0,
                start_status = 'Null',
                P_start,
                intersection_chain = false,
                wait_for_exit = false,
                DEBUG = false;

            if (DEBUG) {
                console.log("\n-------------- _handleIntersectionChains()",
                    (Type.exists(P.data))?P.data.pathname:' ');
            }
            while (true) {
                if (P.intersection === true) {
                    if (DEBUG) {
                        if (P.data.type === 'T') {
                            console.log("Degenerate point", P.coords.usrCoords, P.data.type, (P.data.type === 'T')?P.delayedStatus:' ');
                        } else {
                            console.log("Intersection point", P.coords.usrCoords, P.data.type);
                        }
                    }
                    if (P.data.type === 'T') {
                        if (P.delayedStatus[0] !== 'on' && P.delayedStatus[1] === 'on') {
                            // First point of intersection chain
                            intersection_chain = true;
                            P_start = P;
                            start_status = P.delayedStatus[0];

                        } else if (intersection_chain &&
                                    P.delayedStatus[0] === 'on' && P.delayedStatus[1] === 'on') {
                            // Interior of intersection chain
                            P.data.type    = 'B';
                            if (DEBUG) {
                                console.log("Interior", P.coords.usrCoords);
                            }
                        } else if (intersection_chain &&
                                    P.delayedStatus[0] === 'on' && P.delayedStatus[1] !== 'on') {
                            // Last point of intersection chain
                            intersection_chain = false;
                            if (start_status === P.delayedStatus[1]) {
                                // Intersection chain is delayed bouncing
                                P_start.data.type    = 'DB';
                                P.data.type          = 'DB';
                                if (DEBUG) {
                                    console.log("Chain: delayed bouncing", P_start.coords.usrCoords, '...', P.coords.usrCoords);
                                }
                            } else {
                                // Intersection chain is delayed crossing
                                P_start.data.type    = 'DX';
                                P.data.type          = 'DX';
                                if (DEBUG) {
                                    console.log("Chain: delayed crossing", P_start.coords.usrCoords, '...', P.coords.usrCoords);
                                }
                            }
                        }
                    }
                    cnt++;
                }
                if (P._end) {
                    wait_for_exit = true;
                }
                if (wait_for_exit && !intersection_chain) {
                    break;
                }
                if (cnt > 1000) {
                    console.log("Warning: _handleIntersectionChains: intersection chain reached maximum numbers of iterations");
                    break;
                }
                P = P._next;
            }
        },

        /**
         * Handle the case that all vertices of one path are contained
         * in the other path. In this case we search for a midpoint of an edge
         * which is not contained in the other path and add it to the path.
         * It will be used as starting point for the entry/exit algorithm.
         *
         * @private
         * @param {Array} S Subject path
         * @param {Array} C Clip path
         * @param {JXG.board} board JSXGraph board object. It is needed to convert between
         * user coordinates and screen coordinates.
         */
        _handleFullyDegenerateCase: function(S, C, board) {
            var P, Q, l, M, crds, q1, q2, node,
                i, j, le, le2, is_on_Q,
                is_fully_degenerated,
                arr = [S, C];

            for (l = 0; l < 2; l++) {
                P = arr[l];
                le = P.length;
                for (i = 0, is_fully_degenerated = true; i < le; i++) {
                    if (!P[i].intersection) {
                        is_fully_degenerated = false;
                        break;
                    }
                }

                if (is_fully_degenerated) {
                    // All nodes of P are also on the other path.
                    Q = arr[(l + 1) % 2];
                    le2 = Q.length;

                    // We search for a midpoint of one edge of P which is not the other path and
                    // we add that midpoint to P.
                    for (i = 0; i < le; i++) {
                        q1 = P[i].coords.usrCoords;
                        q2 = P[(i + 1) % le].coords.usrCoords;
                        // M id the midpoint
                        M = [(q1[0] +  q2[0]) * 0.5,
                             (q1[1] +  q2[1]) * 0.5,
                             (q1[2] +  q2[2]) * 0.5];

                        // Test if M is on path Q. If this is not the case,
                        // we take M as additional point of P.
                        for (j = 0, is_on_Q = false; j < le2; j++) {
                            if (Math.abs(this.det(Q[j].coords.usrCoords, Q[(j + 1) % le2].coords.usrCoords, M)) < Mat.eps) {
                                is_on_Q = true;
                                break;
                            }
                        }
                        if (!is_on_Q) {
                            // The midpoint is added to the doubly-linked list.
                            crds = new Coords(Const.COORDS_BY_USER, M, board);
                            node = {
                                    pos: i,
                                    intersection: false,
                                    coords: crds,
                                    elementClass: Const.OBJECT_CLASS_POINT
                                };
                            P[i]._next = node;
                            node._prev = P[i];
                            P[(i + 1) % le]._prev = node;
                            node._next = P[(i + 1) % le];
                            if (P[i]._end) {
                                P[i]._end = false;
                                node._end = true;
                            }

                            break;
                        }
                    }
                }
            }
        },

        _getStatus: function(P, path) {
            var status;
            while (P.intersection) {
                if (P._end) {
                    break;
                }
                P = P._next;
            }
            if (this.windingNumber(P.coords.usrCoords, path) % 2 === 0) {
                // Outside
                status = 'entry';
            } else {
                // Inside
                status = 'exit';
            }

            return [P, status];
        },

        /**
         * Mark the intersection vertices of path1 as entry points or as exit points
         * in respect to path2.
         * <p>
         * This is the simple algorithm as in
         * Greiner, Günther; Kai Hormann (1998). "Efficient clipping of arbitrary polygons".
         * ACM Transactions on Graphics. 17 (2): 71–83
         * <p>
         * The algorithm handles also "delayed crossings" from
         * Erich, L. Foster, and Kai Hormann, Kai, and Romeo Traaian Popa (2019),
         * "Clipping simple polygons with degenerate intersections", Computers & Graphics:X, 2.
         * and - as an additional improvement -
         * handles self intersections of delayed crossings (A.W. 2021).
         *
         * @private
         * @param  {Array} path1 First path
         * @param  {Array} path2 Second path
         */
        markEntryExit: function(path1, path2, starters) {
            var status, P, cnt, res,
                i, len, start,
                chain_start = null,
                intersection_chain = 0,
                DEBUG = false;

            len = starters.length;
            for (i = 0; i < len; i++) {
                start = starters[i];
                if (DEBUG) {
                    console.log("\n;;;;;;;;;; Labelling phase",
                        (Type.exists(path1[start].data))?path1[start].data.pathname:' ',
                        path1[start].coords.usrCoords);
                }
                this._classifyDegenerateIntersections(path1[start]);
                this._handleIntersectionChains(path1[start]);
                if (DEBUG) {
                    console.log("\n---- back to markEntryExit");
                }

                // Decide if the first point of the component is inside or outside
                // of the other path.
                res = this._getStatus(path1[start], path2);
                P = res[0];
                status = res[1];
                if (DEBUG) {
                    console.log("Start node:", P.coords.usrCoords, status);
                }

                P._starter = true;

                // Greiner-Hormann entry/exit algorithm
                // with additional handling of delayed crossing / bouncing
                cnt = 0;
                chain_start = null;
                intersection_chain = 0;

                while (true) {
                    if (P.intersection === true) {
                        if (P.data.type === 'X' && intersection_chain === 1) {
                            // While we are in an intersection chain, i.e. a delayed crossing,
                            // we stumble on a crossing intersection.
                            // Probably, the other path is self intersecting.
                            // We end the intersection chain here and
                            // mark this event by setting intersection_chain = 2.
                            chain_start.entry_exit = status;
                            if (status === 'exit') {
                                chain_start.data.type = 'X';
                            }
                            intersection_chain = 2;
                        }

                        if (P.data.type === 'X' || P.data.type === 'DB') {
                            P.entry_exit = status;
                            status = (status === 'entry') ? 'exit' : 'entry';
                            if (DEBUG) {
                                console.log("mark:", P.coords.usrCoords, P.data.type, P.entry_exit);
                            }
                        }

                        if (P.data.type === 'DX') {
                            if (intersection_chain === 0) {
                                // Start of intersection chain.
                                // No active intersection chain yet,
                                // i.e. we did not pass a the first node of a delayed crossing.
                                chain_start = P;
                                intersection_chain = 1;
                                if (DEBUG) {
                                    console.log("Start intersection chain:", P.coords.usrCoords, P.data.type, status);
                                }

                            } else if (intersection_chain === 1) {
                                // Active intersection chain (intersection_chain===1)!
                                // End of delayed crossing chain reached
                                P.entry_exit = status;
                                chain_start.entry_exit = status;
                                if (status === 'exit') {
                                    chain_start.data.type = 'X';
                                } else {
                                    P.data.type = 'X';
                                }
                                status = (status === 'entry') ? 'exit' : 'entry';

                                if (DEBUG) {
                                    console.log("mark':", chain_start.coords.usrCoords, chain_start.data.type, chain_start.entry_exit);
                                    console.log("mark:", P.coords.usrCoords, P.data.type, P.entry_exit);
                                }
                                chain_start = null;
                                intersection_chain = 0;

                            } else if (intersection_chain === 2) {
                                // The delayed crossing had been interrupted by a crossing intersection.
                                // Now we treat the end of the delayed crossing as regular crossing.
                                P.entry_exit = status;
                                P.data.type = 'X';
                                status = (status === 'entry') ? 'exit' : 'entry';
                                chain_start = null;
                                intersection_chain = 0;
                            }
                        }
                    }

                    P = P._next;
                    if (Type.exists(P._starter) || cnt > 10000) {
                            break;
                    }

                    cnt++;
                }
            }
        },

        /**
         *
         * @private
         * @param {Array} P
         * @param {Boolean} isBackward
         * @returns {Boolean} True, if the node is an intersection and is of type 'X'
         */
        _stayOnPath: function(P, status) {
            var stay = true;

            if (P.intersection && P.data.type !== 'B') {
                stay = (status === P.entry_exit);
            }
            return stay;
        },

        /**
         * Add a point to the clipping path and returns if the algorithms
         * arrived at an intersection point which has already been visited.
         * In this case, true is returned.
         *
         * @param {Array} path Resulting path
         * @param {JXG.Math.Clip.Vertex} vertex Point to be added
         * @param {Boolean} DEBUG debug output to console.log
         * @returns {Boolean} true: point has been visited before, false otherwise
         * @private
         */
        _addVertex: function(path, vertex, DEBUG) {
            if (!isNaN(vertex.coords.usrCoords[1]) && !isNaN(vertex.coords.usrCoords[2])) {
                path.push(vertex);
            }
            if (vertex.intersection && vertex.data.done) {
                if (DEBUG) {
                    console.log("Add last intersection point", vertex.coords.usrCoords,
                        "on", vertex.data.pathname, vertex.entry_exit,
                        vertex.data.type);
                }
                return true;
            }
            if (vertex.intersection) {
                vertex.data.done = true;

                if (DEBUG) {
                    console.log("Add intersection point", vertex.coords.usrCoords,
                        "on", vertex.data.pathname, vertex.entry_exit,
                        vertex.data.type);
                }
            }
            return false;
        },

        /**
         * Tracing phase of the Greiner-Hormann algorithm, see
         * Greiner, Günther; Kai Hormann (1998).
         * "Efficient clipping of arbitrary polygons". ACM Transactions on Graphics. 17 (2): 71–83
         *
         * Boolean operations on polygons are distinguished: 'intersection', 'union', 'difference'.
         *
         * @private
         * @param  {Array} S           Subject path
         * @param  {Array} S_intersect Array containing the intersection vertices of the subject path
         * @param  {String} clip_type  contains the Boolean operation: 'intersection', 'union', or 'difference'
         * @return {Array}             Array consisting of two arrays containing the x-coordinates and the y-coordintaes of
         *      the resulting path.
         */
        tracing: function(S, S_intersect, clip_type) {
            var P, current, start,
                cnt = 0,
                status,
                maxCnt = 10000,
                S_idx = 0,
                path = [],
                done = false,
                DEBUG = false;

            if (DEBUG) {
                console.log("\n------ Start Phase 3");
            }

            // reverse = (clip_type === 'difference' || clip_type === 'union') ? true : false;
            while (S_idx < S_intersect.length && cnt < maxCnt) {
                // Take the first intersection node of the subject path
                // which is not yet included as start point.
                current = S_intersect[S_idx];
                if (current.data.done || current.data.type !== 'X' /*|| !this._isCrossing(current, reverse)*/) {
                    S_idx++;
                    continue;
                }

                if (DEBUG) {
                    console.log("\nStart", current.data.pathname, current.coords.usrCoords, current.data.type, current.entry_exit, S_idx);
                }
                if (path.length > 0) {    // Add a new path
                    path.push([NaN, NaN]);
                }

                // Start now the tracing with that node of the subject path
                start = current.data.idx;
                P = S;

                done = this._addVertex(path, current, DEBUG);
                status = current.entry_exit;
                do {
                    if (done) {
                        break;
                    }
                    //
                    // Decide if we follow the current path forward or backward.
                    // for example, in case the clipping is of type "intersection"
                    // and the current intersection node is of type entry, we go forward.
                    //
                    if ((clip_type === 'intersection' && current.entry_exit === 'entry') ||
                        (clip_type === 'union' && current.entry_exit === 'exit') ||
                        (clip_type === 'difference' && (P === S) === (current.entry_exit === 'exit')) ) {

                        if (DEBUG) {
                            console.log("Go forward on", current.data.pathname, current.entry_exit);
                        }

                        //
                        // Take the next nodes and add them to the path
                        // as long as they are not intersection nodes of type 'X'.
                        //
                        do {
                            current = current._next;
                            done = this._addVertex(path, current, DEBUG);
                            if (done) {
                                break;
                            }
                        } while (this._stayOnPath(current, status));
                        cnt++;
                    } else {
                        if (DEBUG) {
                            console.log("Go backward on", current.data.pathname);
                        }
                        //
                        // Here, we go backward:
                        // Take the previous nodes and add them to the path
                        // as long as they are not intersection nodes of type 'X'.
                        //
                        do {
                            current = current._prev;
                            done = this._addVertex(path, current, DEBUG);
                            if (done) {
                                break;
                            }
                        } while (this._stayOnPath(current, status));
                        cnt++;
                    }

                    if (done) {
                        break;
                    }

                    if (!current.neighbour) {
                        console.log("Tracing: emergency break - no neighbour!!!!!!!!!!!!!!!!!", cnt);
                        return [[0], [0]];
                    }
                    //
                    // We stopped the forward or backward loop, because we've
                    // arrived at a crossing intersection node, i.e. we have to
                    // switch to the other path now.
                    if (DEBUG) {
                        console.log("Switch from", current.coords.usrCoords, current.data.pathname, "to",
                        current.neighbour.coords.usrCoords, "on", current.neighbour.data.pathname);
                    }
                    current = current.neighbour;
                    if (current.data.done) {
                        break;
                    }
                    current.data.done = true;
                    status = current.entry_exit;

                    // if (current.data.done) {
                    //     // We arrived at an intersection node which is already
                    //     // added to the clipping path.
                    //     // We add it again to close the clipping path and jump out of the
                    //     // loop.
                    //     path.push(current);
                    //     if (DEBUG) {
                    //         console.log("Push last", current.coords.usrCoords);
                    //     }
                    //     break;
                    // }
                    P = current.data.path;

                    // Polygon closed:
                    // if (DEBUG) {
                    //     console.log("End of loop:", "start=", start, "idx=", current.data.idx);
                    // }
                // } while (!(current.data.pathname === 'S' && current.data.idx === start) && cnt < maxCnt);
                } while (current.data.idx !== start && cnt < maxCnt);

                if (cnt >= maxCnt) {
                    console.log("Tracing: stopping an infinite loop!", cnt);
                }

                S_idx++;
            }
            return this._getCoordsArrays(path, false);
        },

        /**
         * Handle path clipping if one of the two paths is empty.
         * @private
         * @param  {Array} S        First path, array of JXG.Coords
         * @param  {Array} C        Second path, array of JXG.Coords
         * @param  {String} clip_type Type of Boolean operation: 'intersection', 'union', 'differrence'.
         * @return {Boolean}        true, if one of the input paths is empty, false otherwise.
         */
        isEmptyCase: function(S, C, clip_type) {
            if (clip_type === 'intersection' && (S.length === 0 || C.length === 0)) {
                return true;
            }
            if (clip_type === 'union' && (S.length === 0 && C.length === 0)) {
                return true;
            }
            if (clip_type === 'difference' && S.length === 0) {
                return true;
            }

            return false;
        },

        _getCoordsArrays: function(path, doClose) {
            var pathX = [],
                pathY = [],
                i, le = path.length;

            for (i = 0; i < le; i++) {
                if (path[i].coords) {
                    pathX.push(path[i].coords.usrCoords[1]);
                    pathY.push(path[i].coords.usrCoords[2]);
                } else {
                    pathX.push(path[i][0]);
                    pathY.push(path[i][1]);
                }
            }
            if (doClose && le > 0) {
                if (path[0].coords) {
                    pathX.push(path[0].coords.usrCoords[1]);
                    pathY.push(path[0].coords.usrCoords[2]);
                } else {
                    pathX.push(path[0][0]);
                    pathY.push(path[0][1]);
                }
            }

            return [pathX, pathY];
        },

        /**
         * Handle cases when there are no intersection points of the two paths. This is the case if the
         * paths are disjoint or one is contained in the other.
         * @private
         * @param  {Array} S        First path, array of JXG.Coords
         * @param  {Array} C        Second path, array of JXG.Coords
         * @param  {String} clip_type Type of Boolean operation: 'intersection', 'union', 'differrence'.
         * @return {Array}          Array consisting of two arrays containing the x-coordinates and the y-coordinates of
         *      the resulting path.
         */
        handleEmptyIntersection: function(S, C, clip_type) {
            var P, Q,
                doClose = false,
                path = [];

            // Handle trivial cases
            if (S.length === 0) {
                if (clip_type === 'union') {
                    // S cup C = C
                    path = C;
                } else {
                    // S cap C = S \ C = {}
                    path = [];
                }
                return this._getCoordsArrays(path, true);
            }
            if (C.length === 0) {
                if (clip_type === 'intersection') {
                    // S cap C = {}
                    path = [];
                } else {
                    // S cup C = S \ C = S
                    path = S;
                }
                return this._getCoordsArrays(path, true);
            }

            // From now on, both paths have non-zero length.
            // The two paths have no crossing intersections,
            // but there might be bouncing intersections.

            // First, we find -- if possible -- on each path a point which is not an intersection point.
            if (S.length > 0) {
                P = S[0];
                while (P.intersection) {
                    P = P._next;
                    if (P._end) {
                        break;
                    }
                }
            }
            if (C.length > 0) {
                Q = C[0];
                while (Q.intersection) {
                    Q = Q._next;
                    if (Q._end) {
                        break;
                    }
                }
            }

            // Test if one curve is contained by the other
            if (this.windingNumber(P.coords.usrCoords, C) === 0) {
                // P is outside of C:
                // Either S is disjoint from C or C is inside of S
                if (this.windingNumber(Q.coords.usrCoords, S) !== 0) {
                    // C is inside of S, i.e. C subset of S

                    if (clip_type === 'union') {
                        path = path.concat(S);
                        path.push(S[0]);
                    } else if (clip_type === 'difference') {
                        path = path.concat(S);
                        path.push(S[0]);
                        if (Geometry.signedPolygon(S) * Geometry.signedPolygon(C) > 0) {
                            // Pathes have same orientation, we have to revert one.
                            path.reverse();
                        }
                        path.push([NaN, NaN]);
                    }
                    if (clip_type === 'difference' || clip_type === 'intersection') {
                        path = path.concat(C);
                        path.push(C[0]);
                        doClose = false;
                    }
                } else {                                           // The curves are disjoint
                    if (clip_type === 'difference') {
                        path = path.concat(S);
                        doClose = true;
                    } else if (clip_type === 'union') {
                        path = path.concat(S);
                        path.push(S[0]);
                        path.push([NaN, NaN]);
                        path = path.concat(C);
                        path.push(C[0]);
                    }
                }
            } else {
                                                                    // S inside of C, i.e. S subset of C
                if (clip_type === 'intersection') {
                    path = path.concat(S);
                    doClose = true;
                } else if (clip_type === 'union') {
                    path = path.concat(C);
                    path.push(C[0]);
                }

                // 'difference': path is empty
            }

            return this._getCoordsArrays(path, doClose);
        },

        /**
         * Count intersection points of type 'X'.
         * @param {JXG.Mat.Clip.Vertex} intersections
         * @returns Number
         * @private
         */
        _countCrossingIntersections: function(intersections) {
            var i,
                le = intersections.length,
                sum = 0;

            for (i = 0; i  < le; i++) {
                if (intersections[i].data.type === 'X') {
                    sum++;
                }
            }
            return sum;
        },

        /**
         * Create path from all sorts of input elements and convert it
         * to a suitable input path for greinerHormann().
         *
         * @private
         * @param {Object} obj Maybe curve, arc, sector, circle, polygon, array of points, array of JXG.Coords,
         * array of coordinate pairs.
         * @param  {JXG.Board} board   JSXGraph board object. It is needed to convert between
         * user coordinates and screen coordinates.
         * @returns {Array} Array of JXG.Coords elements containing a path.
         * @see JXG.Math.Clip#greinerHormann
         */
        _getPath: function(obj, board) {
            var i, len, r, rad, angle, alpha,
                steps,
                S = [];

            // Collect all points into path array S
            if (obj.elementClass === Const.OBJECT_CLASS_CURVE &&
                (obj.type === Const.OBJECT_TYPE_ARC || obj.type === Const.OBJECT_TYPE_SECTOR)) {
                angle = Geometry.rad(obj.radiuspoint, obj.center, obj.anglepoint);
                steps = Math.floor(angle * 180 / Math.PI);
                r = obj.Radius();
                rad = angle / steps;
                alpha = Math.atan2(obj.radiuspoint.coords.usrCoords[2] - obj.center.coords.usrCoords[2],
                    obj.radiuspoint.coords.usrCoords[1] - obj.center.coords.usrCoords[1]);

                if (obj.type === Const.OBJECT_TYPE_SECTOR) {
                    this._addToList(S, obj.center.coords, 0);
                }
                for (i = 0; i <= steps; i++) {
                    this._addToList(S, new Coords(Const.COORDS_BY_USER, [
                        obj.center.coords.usrCoords[0],
                        obj.center.coords.usrCoords[1] + Math.cos(i * rad + alpha) * r,
                        obj.center.coords.usrCoords[2] + Math.sin(i * rad + alpha) * r
                    ], board), i + 1);
                }
                if (obj.type === Const.OBJECT_TYPE_SECTOR) {
                    this._addToList(S, obj.center.coords, steps + 2);
                }

            } else if (obj.elementClass === Const.OBJECT_CLASS_CURVE && Type.exists(obj.points)) {
                len = obj.numberPoints;
                for (i = 0; i < len; i++) {
                    this._addToList(S, obj.points[i], i);
                }
            } else if (obj.type === Const.OBJECT_TYPE_POLYGON) {
                for (i = 0; i < obj.vertices.length; i++) {
                    this._addToList(S, obj.vertices[i].coords, i);
                }
            } else if (obj.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                steps = 359;
                r = obj.Radius();
                rad = 2 * Math.PI / steps;
                for (i = 0; i <= steps; i++) {
                    this._addToList(S, new Coords(Const.COORDS_BY_USER, [
                        obj.center.coords.usrCoords[0],
                        obj.center.coords.usrCoords[1] + Math.cos(i * rad) * r,
                        obj.center.coords.usrCoords[2] + Math.sin(i * rad) * r
                    ], board), i);
                }
            } else if (Type.isArray(obj)) {
                len = obj.length;
                for (i = 0; i < len; i++) {
                    if (Type.exists(obj[i].coords)) {
                        // Point type
                        this._addToList(S, obj[i].coords, i);
                    } else if (Type.isArray(obj[i])) {
                        // Coordinate pair
                        this._addToList(S, new Coords(Const.COORDS_BY_USER, obj[i], board), i);
                    } else if (Type.exists(obj[i].usrCoords)) {
                        // JXG.Coordinates
                        this._addToList(S, obj[i], i);
                    }
                }
            }

            return S;
        },

        /**
         * Determine the intersection, union or difference of two closed paths.
         * <p>
         * This is an implementation of the Greiner-Hormann algorithm, see
         * Günther Greiner and Kai Hormann (1998).
         * "Efficient clipping of arbitrary polygons". ACM Transactions on Graphics. 17 (2): 71–83.
         * and
         * Erich, L. Foster, and Kai Hormann, Kai, and Romeo Traaian Popa (2019),
         * "Clipping simple polygons with degenerate intersections", Computers & Graphics:X, 2.
         * <p>
         * It is assumed that the pathes are closed, whereby it does not matter if the last point indeed
         * equals the first point. In contrast to the original Greiner-Hormann algorithm,
         * this algorithm can cope with many degenerate cases. A degenerate case is a vertext of one path
         * which is contained in the other path.
         * <p>
         *
         * <p>Problematic are:
         * <ul>
         *   <li>degenerate cases where one path additionally has self-intersections
         *   <li>differences with one path having self-intersections.
         * </ul>
         *
         * @param  {JXG.Circle|JXG.Curve|JXG.Polygon} subject   First closed path, usually called 'subject'.
         * Maybe curve, arc, sector, circle, polygon, array of points, array of JXG.Coords,
         * array of coordinate pairs.
         * @param  {JXG.Circle|JXG.Curve|JXG.Polygon} clip      Second closed path, usually called 'clip'.
         * Maybe curve, arc, sector, circle, polygon, array of points, array of JXG.Coords,
         * array of coordinate pairs.
         * @param  {String} clip_type Determines the type of boolean operation on the two paths.
         *  Possible values are 'intersection', 'union', or 'difference'.
         * @param  {JXG.Board} board   JSXGraph board object. It is needed to convert between
         * user coordinates and screen coordinates.
         * @return {Array}          Array consisting of two arrays containing the x-coordinates and the y-coordinates of
         *      the resulting path.
         *
         * @see JXG.Math.Clip#intersection
         * @see JXG.Math.Clip#union
         * @see JXG.Math.Clip#difference
         *
         * @example
         *     var curve1 = board.create('curve', [
         *             [-3, 3, 0, -3],
         *             [3, 3, 0, 3]
         *         ],
         *         {strokeColor: 'black'});
         *
         *     var curve2 = board.create('curve', [
         *             [-4, 4, 0, -4],
         *             [2, 2, 4, 2]
         *         ],
         *         {strokeColor: 'blue'});
         *
         *     var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6});
         *     clip_path.updateDataArray = function() {
         *         var a = JXG.Math.Clip.greinerHormann(curve2, curve1, 'intersection', this.board);
         *
         *         this.dataX = a[0];
         *         this.dataY = a[1];
         *     };
         *
         *     board.update();
         *
         * </pre><div id="JXG9d2a6acf-a43b-4035-8f8a-9b1bee580210" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG9d2a6acf-a43b-4035-8f8a-9b1bee580210',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *
         *         var curve1 = board.create('curve', [
         *                 [-3, 3, 0, -3],
         *                 [3, 3, 0, 3]
         *             ],
         *             {strokeColor: 'black'});
         *
         *         var curve2 = board.create('curve', [
         *                 [-4, 4, 0, -4],
         *                 [2, 2, 4, 2]
         *             ],
         *             {strokeColor: 'blue'});
         *
         *         var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6});
         *         clip_path.updateDataArray = function() {
         *             var a = JXG.Math.Clip.greinerHormann(curve2, curve1, 'intersection', this.board);
         *
         *             this.dataX = a[0];
         *             this.dataY = a[1];
         *         };
         *
         *         board.update();
         *
         *     })();
         *
         * </script><pre>
         *
         * @example
         *     var curve1 = board.create('curve', [
         *             [-3, 3, 0, -3],
         *             [3, 3, 0, 3]
         *         ],
         *         {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8});
         *
         *     var curve2 = board.create('polygon', [[3, 4], [-4, 0], [-4, 4]],
         *             {strokeColor: 'blue', fillColor: 'none'});
         *
         *     var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6});
         *     clip_path.updateDataArray = function() {
         *         var a = JXG.Math.Clip.greinerHormann(curve1, curve2, 'union', this.board);
         *         this.dataX = a[0];
         *         this.dataY = a[1];
         *     };
         *
         *     board.update();
         *
         * </pre><div id="JXG6075c918-4d57-4b72-b600-6597a6a4f44e" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG6075c918-4d57-4b72-b600-6597a6a4f44e',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *         var curve1 = board.create('curve', [
         *                 [-3, 3, 0, -3],
         *                 [3, 3, 0, 3]
         *             ],
         *             {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8});
         *
         *         var curve2 = board.create('polygon', [[3, 4], [-4, 0], [-4, 4]],
         *                 {strokeColor: 'blue', fillColor: 'none'});
         *
         *
         *         var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6});
         *         clip_path.updateDataArray = function() {
         *             var a = JXG.Math.Clip.greinerHormann(curve1, curve2, 'union', this.board);
         *             this.dataX = a[0];
         *             this.dataY = a[1];
         *         };
         *
         *         board.update();
         *
         *     })();
         *
         * </script><pre>
         *
         * @example
         *     var curve1 = board.create('curve', [
         *             [-4, 4, 0, -4],
         *             [4, 4, -2, 4]
         *         ],
         *         {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8});
         *
         *     var curve2 = board.create('circle', [[0, 0], [0, -2]],
         *             {strokeColor: 'blue', strokeWidth: 1, fillColor: 'red', fixed: true, fillOpacity: 0.3,
         *             center: {visible: true, size: 5}, point2: {size: 5}});
         *
         *     var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6});
         *     clip_path.updateDataArray = function() {
         *         var a = JXG.Math.Clip.greinerHormann(curve1, curve2, 'difference', this.board);
         *
         *         this.dataX = a[0];
         *         this.dataY = a[1];
         *     };
         *
         *     board.update();
         *
         * </pre><div id="JXG46b3316b-5ab9-4928-9473-ccb476ca4185" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG46b3316b-5ab9-4928-9473-ccb476ca4185',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *         var curve1 = board.create('curve', [
         *                 [-4, 4, 0, -4],
         *                 [4, 4, -2, 4]
         *             ],
         *             {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8});
         *
         *         var curve2 = board.create('circle', [[0, 0], [0, -2]],
         *                 {strokeColor: 'blue', strokeWidth: 1, fillColor: 'red', fixed: true, fillOpacity: 0.3,
         *                 center: {visible: true, size: 5}, point2: {size: 5}});
         *
         *
         *         var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.6});
         *         clip_path.updateDataArray = function() {
         *             var a = JXG.Math.Clip.greinerHormann(curve1, curve2, 'difference', this.board);
         *
         *             this.dataX = a[0];
         *             this.dataY = a[1];
         *         };
         *
         *         board.update();
         *
         *     })();
         *
         * </script><pre>
         *
         * @example
         * var clip_path = board.create('curve', [[], []], {strokeWidth: 1, fillColor: 'yellow', fillOpacity: 0.6});
         * clip_path.updateDataArray = function() {
         *     var bbox = this.board.getBoundingBox(),
         *         canvas, triangle;
         *
         *     canvas = [[bbox[0], bbox[1]], // ul
         *          [bbox[0], bbox[3]], // ll
         *          [bbox[2], bbox[3]], // lr
         *          [bbox[2], bbox[1]], // ur
         *          [bbox[0], bbox[1]]] // ul
         *     triangle = [[-1,1], [1,1], [0,-1], [-1,1]];
         *
         *     var a = JXG.Math.Clip.greinerHormann(canvas, triangle, 'difference', this.board);
         *     this.dataX = a[0];
         *     this.dataY = a[1];
         * };
         *
         * </pre><div id="JXGe94da07a-2a01-4498-ad62-f71a327f8e25" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXGe94da07a-2a01-4498-ad62-f71a327f8e25',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var clip_path = board.create('curve', [[], []], {strokeWidth: 1, fillColor: 'yellow', fillOpacity: 0.6});
         *     clip_path.updateDataArray = function() {
         *         var bbox = this.board.getBoundingBox(),
         *             canvas, triangle;
         *
         *         canvas = [[bbox[0], bbox[1]], // ul
         *              [bbox[0], bbox[3]], // ll
         *              [bbox[2], bbox[3]], // lr
         *              [bbox[2], bbox[1]], // ur
         *              [bbox[0], bbox[1]]] // ul
         *         triangle = [[-1,1], [1,1], [0,-1], [-1,1]];
         *
         *         var a = JXG.Math.Clip.greinerHormann(canvas, triangle, 'difference', this.board);
         *         this.dataX = a[0];
         *         this.dataY = a[1];
         *     };
         *
         *     })();
         *
         * </script><pre>
         *
         */
        greinerHormann: function(subject, clip, clip_type, board) { //},
                // subject_first_point_type, clip_first_point_type) {

            var len, S = [],
                C = [],
                S_intersect = [],
                // C_intersect = [],
                S_starters,
                C_starters,
                res = [],
                DEBUG = false;

            if (DEBUG) {
                console.log("\n------------ GREINER-HORMANN --------------");
            }
            // Collect all subject points into subject array S
            S = this._getPath(subject, board);
            len = S.length;
            if (len > 0 && Geometry.distance(S[0].coords.usrCoords, S[len - 1].coords.usrCoords, 3) < Mat.eps) {
                S.pop();
            }

            // Collect all points into clip array C
            C = this._getPath(clip, board);

            len = C.length;
            if (len > 0 && Geometry.distance(C[0].coords.usrCoords, C[len - 1].coords.usrCoords, 3) < Mat.eps * Mat.eps) {
                C.pop();
            }

            // Handle cases where at least one of the paths is empty
            if (this.isEmptyCase(S, C, clip_type)) {
                return [[], []];
            }

            // Add pointers for doubly linked lists
            S_starters = this.makeDoublyLinkedList(S);
            C_starters = this.makeDoublyLinkedList(C);

            if (DEBUG) {
                this._print_array(S);
                console.log("Components:", S_starters);
                this._print_array(C);
                console.log("Components:", C_starters);
            }

            res = this.findIntersections(S, C, board);
            S_intersect = res[0];

            this._handleFullyDegenerateCase(S, C, board);

            // Phase 2: mark intersection points as entry or exit points
            this.markEntryExit(S, C, S_starters);

            // if (S[0].coords.distance(Const.COORDS_BY_USER, C[0].coords) === 0) {
            //     // Randomly disturb the first point of the second path
            //     // if both paths start at the same point.
            //     C[0].usrCoords[1] *= 1 + Math.random() * 0.0001 - 0.00005;
            //     C[0].usrCoords[2] *= 1 + Math.random() * 0.0001 - 0.00005;
            // }
            this.markEntryExit(C, S, C_starters);

            // Handle cases without intersections
            if (this._countCrossingIntersections(S_intersect) === 0) {
                return this.handleEmptyIntersection(S, C, clip_type);
            }

            // Phase 3: tracing
            return this.tracing(S, S_intersect, clip_type);
        },

        /**
         * Union of two closed paths. The paths could be JSXGraph elements circle, curve, or polygon.
         * Computed by the Greiner-Hormann algorithm.
         *
         * @param  {JXG.Circle|JXG.Curve|JXG.Polygon} subject   First closed path.
         * @param  {JXG.Circle|JXG.Curve|JXG.Polygon} clip      Second closed path.
         * @param  {JXG.Board} board   JSXGraph board object. It is needed to convert between
         * user coordinates and screen coordinates.
         * @return {Array}          Array consisting of two arrays containing the x-coordinates and the y-coordinates of
         *      the resulting path.
         *
         * @see JXG.Math.Clip#greinerHormann
         * @see JXG.Math.Clip#intersection
         * @see JXG.Math.Clip#difference
         *
         * @example
         *     var curve1 = board.create('curve', [
         *             [-3, 3, 0, -3],
         *             [3, 3, 0, 3]
         *         ],
         *         {strokeColor: 'black'});
         *
         *     var curve2 = board.create('polygon', [[3, 4], [-4, 0], [-4, 4]],
         *             {strokeColor: 'blue', fillColor: 'none'});
         *
         *     var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3});
         *     clip_path.updateDataArray = function() {
         *         var a = JXG.Math.Clip.union(curve1, curve2, this.board);
         *         this.dataX = a[0];
         *         this.dataY = a[1];
         *     };
         *
         *     board.update();
         *
         * </pre><div id="JXG7c5204aa-3824-4464-819c-80df7bf1d917" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG7c5204aa-3824-4464-819c-80df7bf1d917',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *         var curve1 = board.create('curve', [
         *                 [-3, 3, 0, -3],
         *                 [3, 3, 0, 3]
         *             ],
         *             {strokeColor: 'black'});
         *
         *         var curve2 = board.create('polygon', [[3, 4], [-4, 0], [-4, 4]],
         *                 {strokeColor: 'blue', fillColor: 'none'});
         *
         *
         *         var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3});
         *         clip_path.updateDataArray = function() {
         *             var a = JXG.Math.Clip.union(curve1, curve2, this.board);
         *             this.dataX = a[0];
         *             this.dataY = a[1];
         *         };
         *
         *         board.update();
         *
         *     })();
         *
         * </script><pre>
         *
         */
        union: function(path1, path2, board) {
            return this.greinerHormann(path1, path2, 'union', board);
        },

        /**
         * Intersection of two closed paths. The paths could be JSXGraph elements circle, curve, or polygon.
         * Computed by the Greiner-Hormann algorithm.
         *
         * @param  {JXG.Circle|JXG.Curve|JXG.Polygon} subject   First closed path.
         * @param  {JXG.Circle|JXG.Curve|JXG.Polygon} clip      Second closed path.
         * @param  {JXG.Board} board   JSXGraph board object. It is needed to convert between
         * user coordinates and screen coordinates.
         * @return {Array}          Array consisting of two arrays containing the x-coordinates and the y-coordinates of
         *      the resulting path.
         *
         * @see JXG.Math.Clip#greinerHormann
         * @see JXG.Math.Clip#union
         * @see JXG.Math.Clip#difference
         *
         * @example
         * var p = [];
         * p.push(board.create('point', [0, -5]));
         * p.push(board.create('point', [-5, 0]));
         * p.push(board.create('point', [-3, 3]));
         *
         * var curve1 = board.create('ellipse', p,
         *                 {strokeColor: 'black'});
         *
         * var curve2 = board.create('curve', [function(phi){return 4 * Math.cos(2*phi); },
         *                                     [0, 0],
         *                                     0, 2 * Math.PI],
         *                       {curveType:'polar', strokeColor: 'blue', strokewidth:1});
         *
         * var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3});
         * clip_path.updateDataArray = function() {
         *     var a = JXG.Math.Clip.intersection(curve2, curve1, this.board);
         *
         *     this.dataX = a[0];
         *     this.dataY = a[1];
         * };
         *
         * board.update();
         *
         * </pre><div id="JXG7ad547eb-7b6c-4a1a-a4d4-4ed298fc7998" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG7ad547eb-7b6c-4a1a-a4d4-4ed298fc7998',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var p = [];
         *     p.push(board.create('point', [0, -5]));
         *     p.push(board.create('point', [-5, 0]));
         *     p.push(board.create('point', [-3, 3]));
         *
         *     var curve1 = board.create('ellipse', p,
         *                     {strokeColor: 'black'});
         *
         *     var curve2 = board.create('curve', [function(phi){return 4 * Math.cos(2*phi); },
         *                                         [0, 0],
         *                                         0, 2 * Math.PI],
         *                           {curveType:'polar', strokeColor: 'blue', strokewidth:1});
         *
         *
         *     var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3});
         *     clip_path.updateDataArray = function() {
         *         var a = JXG.Math.Clip.intersection(curve2, curve1, this.board);
         *
         *         this.dataX = a[0];
         *         this.dataY = a[1];
         *     };
         *
         *     board.update();
         *
         *     })();
         *
         * </script><pre>
         *
         *
         */
        intersection: function(path1, path2, board) {
            return this.greinerHormann(path1, path2, 'intersection', board);
        },

        /**
         * Difference of two closed paths, i.e. path1 minus path2.
         * The paths could be JSXGraph elements circle, curve, or polygon.
         * Computed by the Greiner-Hormann algorithm.
         *
         * @param  {JXG.Circle|JXG.Curve|JXG.Polygon} subject   First closed path.
         * @param  {JXG.Circle|JXG.Curve|JXG.Polygon} clip      Second closed path.
         * @param  {JXG.Board} board   JSXGraph board object. It is needed to convert between
         * user coordinates and screen coordinates.
         * @return {Array}          Array consisting of two arrays containing the x-coordinates and the y-coordinates of
         *      the resulting path.
         *
         * @see JXG.Math.Clip#greinerHormann
         * @see JXG.Math.Clip#intersection
         * @see JXG.Math.Clip#union
         *
         * @example
         *     var curve1 = board.create('polygon', [[-4, 4], [4, 4], [0, -1]],
         *             {strokeColor: 'blue', fillColor: 'none'});
         *
         *     var curve2 = board.create('curve', [
         *             [-1, 1, 0, -1],
         *             [1, 1, 3, 1]
         *         ],
         *         {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8});
         *
         *     var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3});
         *     clip_path.updateDataArray = function() {
         *         var a = JXG.Math.Clip.difference(curve1, curve2, this.board);
         *         this.dataX = a[0];
         *         this.dataY = a[1];
         *     };
         *
         *     board.update();
         *
         * </pre><div id="JXGc5ce6bb3-146c-457f-a48b-6b9081fb68a3" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXGc5ce6bb3-146c-457f-a48b-6b9081fb68a3',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *         var curve1 = board.create('polygon', [[-4, 4], [4, 4], [0, -1]],
         *                 {strokeColor: 'blue', fillColor: 'none'});
         *
         *         var curve2 = board.create('curve', [
         *                 [-1, 1, 0, -1],
         *                 [1, 1, 3, 1]
         *             ],
         *             {strokeColor: 'black', fillColor: 'none', fillOpacity: 0.8});
         *
         *
         *         var clip_path = board.create('curve', [[], []], {strokeWidth: 3, fillColor: 'yellow', fillOpacity: 0.3});
         *         clip_path.updateDataArray = function() {
         *             var a = JXG.Math.Clip.difference(curve1, curve2, this.board);
         *             this.dataX = a[0];
         *             this.dataY = a[1];
         *         };
         *
         *         board.update();
         *
         *     })();
         *
         * </script><pre>
         *
         */
        difference: function(path1, path2, board) {
            return this.greinerHormann(path1, path2, 'difference', board);
        }
    }; //);

    JXG.extend(Mat.Clip, /** @lends JXG.Math.Clip */ {
    });

    return Mat.Clip;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/math
 utils/type
 */

/**
 * @fileoverview In this file the namespace Math.Poly is defined, which holds algorithms to create and
 * manipulate polynomials.
 */

define('math/poly',['jxg', 'math/math', 'utils/type'], function (JXG, Mat, Type) {

    "use strict";

    /**
     * The JXG.Math.Poly namespace holds algorithms to create and manipulate polynomials.
     * @name JXG.Math.Poly
     * @exports Mat.Poly as JXG.Math.Poly
     * @namespace
     */
    Mat.Poly = {};

    /**
     * Define a polynomial ring over R.
     * @class
     * @name JXG.Math.Poly.Ring
     * @param {Array} variables List of indeterminates.
     */
    Mat.Poly.Ring = function (variables) {
        /**
         * A list of variables in this polynomial ring.
         * @type Array
         */
        this.vars = variables;
    };

    JXG.extend(Mat.Poly.Ring.prototype, /** @lends JXG.Math.Poly.Ring.prototype */ {
        // nothing yet.
    });


    /**
     * Define a monomial over the polynomial ring <tt>ring</tt>.
     * @class
     * @name JXG.Math.Poly.Monomial
     * @param {JXG.Math.Poly.Ring} ring
     * @param {Number} coefficient
     * @param {Array} exponents An array of exponents, corresponding to ring
     */
    Mat.Poly.Monomial = function (ring, coefficient, exponents) {
        var i;

        if (!Type.exists(ring)) {
            throw new Error('JSXGraph error: In JXG.Math.Poly.monomial missing parameter \'ring\'.');
        }

        if (!Type.isArray(exponents)) {
            exponents = [];
        }

        exponents = exponents.slice(0, ring.vars.length);

        for (i = exponents.length; i < ring.vars.length; i++) {
            exponents.push(0);
        }

        /**
         * A polynomial ring.
         * @type JXG.Math.Poly.Ring
         */
        this.ring = ring;

        /**
         * The monomial's coefficient
         * @type Number
         */
        this.coefficient = coefficient || 0;

        /**
         * Exponent vector, the order depends on the order of the variables
         * in the ring definition.
         * @type Array
         */
        this.exponents = Type.deepCopy(exponents);
    };

    JXG.extend(Mat.Poly.Monomial.prototype, /** @lends JXG.Math.Poly.Monomial.prototype */ {

        /**
         * Creates a deep copy of the monomial.
         *
         * @returns {JXG.Math.Poly.Monomial}
         *
         * @memberof JXG.Math.Poly.Monomial
         */
        copy: function () {
            return new Mat.Poly.Monomial(this.ring, this.coefficient, this.exponents);
        },

        /**
         * Print the monomial.
         * @returns {String} String representation of the monomial

         * @memberof JXG.Math.Poly.Monomial
         */
        print: function () {
            var s = [],
                i;

            for (i = 0; i < this.ring.vars.length; i++) {
                s.push(this.ring.vars[i] + '^' + this.exponents[i]);
            }

            return this.coefficient + '*' + s.join('*');
        }
    });


    /**
     * A polynomial is a sum of monomials.
     * @class
     * @name JXG.Math.Poly.Polynomial
     * @param {JXG.Math.Poly.Ring} ring A polynomial ring.
     * @param {String} str TODO String representation of the polynomial, will be parsed.
     */
    Mat.Poly.Polynomial = function (ring, str) {
        var parse = function () {

            },
            mons;

        if (!Type.exists(ring)) {
            throw new Error('JSXGraph error: In JXG.Math.Poly.polynomial missing parameter \'ring\'.');
        }

        if (Type.exists(str) && Type.isString(str)) {
            mons = parse(str);
        } else {
            mons = [];
        }

        /**
         * A polynomial ring.
         * @type JXG.Math.Poly.Ring
         */
        this.ring = ring;

        /**
         * List of monomials.
         * @type Array
         */
        this.monomials = mons;
    };

    JXG.extend(Mat.Poly.Polynomial.prototype, /** @lends JXG.Math.Poly.Polynomial.prototype */ {
        /**
         * Find a monomial with the given signature, i.e. exponent vector.
         * @param {Array} sig An array of numbers
         * @returns {Number} The index of the first monomial with the given signature, or -1
         * if no monomial could be found.
         * @memberof JXG.Math.Poly.Polynomial
         */
        findSignature: function (sig) {
            var i;

            for (i = 0; i < this.monomials.length; i++) {
                if (Type.cmpArrays(this.monomials[i].exponents, sig)) {
                    return i;
                }
            }

            return -1;
        },

        /**
         * Adds a monomial to the polynomial. Checks the existing monomials for the added
         * monomial's signature and just adds the coefficient if one is found.
         * @param {JXG.Math.Poly.Monomial} m
         * @param {Number} factor Either <tt>1</tt> or <tt>-1</tt>.
         * @memberof JXG.Math.Poly.Polynomial
         */
        addSubMonomial: function (m, factor) {
            var i;

            i = this.findSignature(m.exponents);
            if (i > -1) {
                this.monomials[i].coefficient += factor * m.coefficient;
            } else {
                m.coefficient *= factor;
                this.monomials.push(m);
            }
        },

        /**
         * Adds another polynomial or monomial to this one and merges them by checking for the
         * signature of each new monomial in the existing monomials.
         * @param {JXG.Math.Poly.Polynomial|JXG.Math.Poly.Monomial} mp
         * @memberof JXG.Math.Poly.Polynomial
         */
        add: function (mp) {
            var i;

            if (Type.exists(mp) && mp.ring === this.ring) {
                if (Type.isArray(mp.exponents)) {
                    // mp is a monomial
                    this.addSubMonomial(mp, 1);
                } else {
                    // mp is a polynomial
                    for (i = 0; i < mp.monomials.length; i++) {
                        this.addSubMonomial(mp.monomials[i], 1);
                    }
                }
            } else {
                throw new Error('JSXGraph error: In JXG.Math.Poly.polynomial.add either summand is undefined or rings don\'t match.');
            }
        },

        /**
         * Subtracts another polynomial or monomial from this one and merges them by checking for the
         * signature of each new monomial in the existing monomials.
         * @param {JXG.Math.Poly.Polynomial|JXG.Math.Poly.Monomial} mp
         * @memberof JXG.Math.Poly.Polynomial
         */
        sub: function (mp) {
            var i;

            if (Type.exists(mp) && mp.ring === this.ring) {
                if (Type.isArray(mp.exponents)) {
                    // mp is a monomial
                    this.addSubMonomial(mp, -1);
                } else {
                    // mp is a polynomial
                    for (i = 0; i < mp.monomials.length; i++) {
                        this.addSubMonomial(mp.monomials[i], -1);
                    }
                }
            } else {
                throw new Error('JSXGraph error: In JXG.Math.Poly.polynomial.sub either summand is undefined or rings don\'t match.');
            }
        },

        /**
         * Creates a deep copy of the polynomial.
         * @returns {JXG.Math.Poly.Polynomial}
         * @memberof JXG.Math.Poly.Polynomial
         */
        copy: function () {
            var i, p;

            p = new Mat.Poly.Polynomial(this.ring);

            for (i = 0; i < this.monomials.length; i++) {
                p.monomials.push(this.monomials[i].copy());
            }
            return p;
        },

        /**
         * Prints the polynomial.
         * @returns {String} A string representation of the polynomial.
         * @memberof JXG.Math.Poly.Polynomial
         */
        print: function () {
            var s = [],
                i;

            for (i = 0; i < this.monomials.length; i++) {
                s.push('(' + this.monomials[i].print() + ')');
            }

            return s.join('+');
        }
    });

    return Mat.Poly;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 */

/**
 * @fileoverview A class for complex arithmetics JXG.Complex is defined in this
 * file. Also a namespace JXG.C is included to provide instance-independent
 * arithmetic functions.
 */

define('math/complex',['jxg', 'utils/type'], function (JXG, Type) {

    "use strict";

    /**
     * Creates a new complex number.
     * @class This class is for calculating with complex numbers.
     * @constructor
     * @param {Number} [x=0] Real part.
     * @param {Number} [y=0] Imaginary part.
     */
    JXG.Complex = function (x, y) {
        /**
         * This property is only to signalize that this object is of type JXG.Complex. Only
         * used internally to distinguish between normal JavaScript numbers and JXG.Complex numbers.
         * @type Boolean
         * @default true
         * @private
         */
        this.isComplex = true;

        /* is the first argument a complex number? if it is,
         * extract real and imaginary part. */
        if (x && x.isComplex) {
            y = x.imaginary;
            x = x.real;
        }

        /**
         * Real part of the complex number.
         * @type Number
         * @default 0
         */
        this.real = x || 0;

        /**
         * Imaginary part of the complex number.
         * @type Number
         * @default 0
         */
        this.imaginary = y || 0;

        /**
         * Absolute value in the polar form of the complex number. Currently unused.
         * @type Number
         */
        this.absval = 0;

        /**
         * Angle value in the polar form of the complex number. Currently unused.
         * @type Number
         */
        this.angle = 0;
    };

    JXG.extend(JXG.Complex.prototype, /** @lends JXG.Complex.prototype */ {
        /**
         * Converts a complex number into a string.
         * @returns {String} Formatted string containing the complex number in human readable form (algebraic form).
         */
        toString: function () {
            return this.real + ' + ' + this.imaginary + 'i';
        },

        /**
         * Add another complex number to this complex number.
         * @param {JXG.Complex,Number} c A JavaScript number or a JXG.Complex object to be added to the current object.
         * @returns {JXG.Complex} Reference to this complex number
         */
        add: function (c) {
            if (Type.isNumber(c)) {
                this.real += c;
            } else {
                this.real += c.real;
                this.imaginary += c.imaginary;
            }

            return this;
        },

        /**
         * Subtract another complex number from this complex number.
         * @param {JXG.Complex,Number} c A JavaScript number or a JXG.Complex object to subtract from the current object.
         * @returns {JXG.Complex} Reference to this complex number
         */
        sub: function (c) {
            if (Type.isNumber(c)) {
                this.real -= c;
            } else {
                this.real -= c.real;
                this.imaginary -= c.imaginary;
            }

            return this;
        },

        /**
         * Multiply another complex number to this complex number.
         * @param {JXG.Complex,Number} c A JavaScript number or a JXG.Complex object to
         * multiply with the current object.
         * @returns {JXG.Complex} Reference to this complex number
         */
        mult: function (c) {
            var re, im;

            if (Type.isNumber(c)) {
                this.real *= c;
                this.imaginary *= c;
            } else {
                re = this.real;
                im = this.imaginary;

                //  (a+ib)(x+iy) = ax-by + i(xb+ay)
                this.real = re * c.real - im * c.imaginary;
                this.imaginary = re * c.imaginary + im * c.real;
            }

            return this;
        },

        /**
         * Divide this complex number by the given complex number.
         * @param {JXG.Complex,Number} c A JavaScript number or a JXG.Complex object to
         * divide the current object by.
         * @returns {JXG.Complex} Reference to this complex number
         */
        div: function (c) {
            var denom, im, re;

            if (Type.isNumber(c)) {
                if (Math.abs(c) < Math.eps) {
                    this.real = Infinity;
                    this.imaginary = Infinity;

                    return this;
                }

                this.real /= c;
                this.imaginary /= c;
            } else {
                //  (a+ib)(x+iy) = ax-by + i(xb+ay)
                if ((Math.abs(c.real) < Math.eps) && (Math.abs(c.imaginary) < Math.eps)) {
                    this.real = Infinity;
                    this.imaginary = Infinity;

                    return this;
                }

                denom = c.real * c.real + c.imaginary * c.imaginary;

                re = this.real;
                im = this.imaginary;
                this.real = (re * c.real + im * c.imaginary) / denom;
                this.imaginary = (im * c.real - re * c.imaginary) / denom;
            }

            return this;
        },

        /**
         * Conjugate a complex number in place.
         * @returns {JXG.Complex} Reference to this complex number
         */
        conj: function () {
            this.imaginary *= -1;

            return this;
        }
    });

    /**
     * @description
     * JXG.C is the complex number (name)space. It provides functions to calculate with
     * complex numbers (defined in {@link JXG.Complex}). With this namespace you don't have to modify
     * your existing complex numbers, e.g. to add two complex numbers:
     * <pre class="code">   var z1 = new JXG.Complex(1, 0);
     *    var z2 = new JXG.Complex(0, 1);
     *    z = JXG.C.add(z1, z1);</pre>
     * z1 and z2 here remain unmodified. With the object oriented approach above this
     * section the code would look like:
     * <pre class="code">   var z1 = new JXG.Complex(1, 0);
     *    var z2 = new JXG.Complex(0, 1);
     *    var z = new JXG.Complex(z1);
     *    z.add(z2);</pre>
     * @namespace Namespace for the complex number arithmetic functions.
     */
    JXG.C = {};

    /**
     * Add two (complex) numbers z1 and z2 and return the result as a (complex) number.
     * @param {JXG.Complex,Number} z1 Summand
     * @param {JXG.Complex,Number} z2 Summand
     * @returns {JXG.Complex} A complex number equal to the sum of the given parameters.
     */
    JXG.C.add = function (z1, z2) {
        var z = new JXG.Complex(z1);
        z.add(z2);
        return z;
    };

    /**
     * Subtract two (complex) numbers z1 and z2 and return the result as a (complex) number.
     * @param {JXG.Complex,Number} z1 Minuend
     * @param {JXG.Complex,Number} z2 Subtrahend
     * @returns {JXG.Complex} A complex number equal to the difference of the given parameters.
     */
    JXG.C.sub = function (z1, z2) {
        var z = new JXG.Complex(z1);
        z.sub(z2);
        return z;
    };

    /**
     * Multiply two (complex) numbers z1 and z2 and return the result as a (complex) number.
     * @param {JXG.Complex,Number} z1 Factor
     * @param {JXG.Complex,Number} z2 Factor
     * @returns {JXG.Complex} A complex number equal to the product of the given parameters.
     */
    JXG.C.mult = function (z1, z2) {
        var z = new JXG.Complex(z1);
        z.mult(z2);
        return z;
    };

    /**
     * Divide two (complex) numbers z1 and z2 and return the result as a (complex) number.
     * @param {JXG.Complex,Number} z1 Dividend
     * @param {JXG.Complex,Number} z2 Divisor
     * @returns {JXG.Complex} A complex number equal to the quotient of the given parameters.
     */
    JXG.C.div = function (z1, z2) {
        var z = new JXG.Complex(z1);
        z.div(z2);
        return z;
    };

    /**
     * Conjugate a complex number and return the result.
     * @param {JXG.Complex,Number} z1 Complex number
     * @returns {JXG.Complex} A complex number equal to the conjugate of the given parameter.
     */
    JXG.C.conj = function (z1) {
        var z = new JXG.Complex(z1);
        z.conj();
        return z;
    };

    /**
     * Absolute value of a complex number.
     * @param {JXG.Complex,Number} z1 Complex number
     * @returns {Number} real number equal to the absolute value of the given parameter.
     */
    JXG.C.abs = function (z1) {
        var z = new JXG.Complex(z1);

        z.conj();
        z.mult(z1);

        return Math.sqrt(z.real);
    };

    JXG.Complex.C = JXG.C;

    return JXG.Complex;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Andreas Walter,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */

/*global JXG: true, define: true*/

/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/type
 math/math
 */

/**
 * Functions for color conversions. This was originally based on a class to parse color values by
 * Stoyan Stefanov <sstoo@gmail.com> (see http://www.phpied.com/rgb-color-parser-in-javascript/)
 */

define('utils/color',['jxg', 'utils/type', 'math/math'],
    function (JXG, Type, Mat) {

    "use strict";

    // private constants and helper functions

    // simple colors contains string color constants that can be used in various browser
    // in javascript
    var simpleColors = {
            aliceblue: 'f0f8ff',
            antiquewhite: 'faebd7',
            aqua: '00ffff',
            aquamarine: '7fffd4',
            azure: 'f0ffff',
            beige: 'f5f5dc',
            bisque: 'ffe4c4',
            black: '000000',
            blanchedalmond: 'ffebcd',
            blue: '0000ff',
            blueviolet: '8a2be2',
            brown: 'a52a2a',
            burlywood: 'deb887',
            cadetblue: '5f9ea0',
            chartreuse: '7fff00',
            chocolate: 'd2691e',
            coral: 'ff7f50',
            cornflowerblue: '6495ed',
            cornsilk: 'fff8dc',
            crimson: 'dc143c',
            cyan: '00ffff',
            darkblue: '00008b',
            darkcyan: '008b8b',
            darkgoldenrod: 'b8860b',
            darkgray: 'a9a9a9',
            darkgreen: '006400',
            darkkhaki: 'bdb76b',
            darkmagenta: '8b008b',
            darkolivegreen: '556b2f',
            darkorange: 'ff8c00',
            darkorchid: '9932cc',
            darkred: '8b0000',
            darksalmon: 'e9967a',
            darkseagreen: '8fbc8f',
            darkslateblue: '483d8b',
            darkslategray: '2f4f4f',
            darkturquoise: '00ced1',
            darkviolet: '9400d3',
            deeppink: 'ff1493',
            deepskyblue: '00bfff',
            dimgray: '696969',
            dodgerblue: '1e90ff',
            feldspar: 'd19275',
            firebrick: 'b22222',
            floralwhite: 'fffaf0',
            forestgreen: '228b22',
            fuchsia: 'ff00ff',
            gainsboro: 'dcdcdc',
            ghostwhite: 'f8f8ff',
            gold: 'ffd700',
            goldenrod: 'daa520',
            gray: '808080',
            green: '008000',
            greenyellow: 'adff2f',
            honeydew: 'f0fff0',
            hotpink: 'ff69b4',
            indianred: 'cd5c5c',
            indigo: '4b0082',
            ivory: 'fffff0',
            khaki: 'f0e68c',
            lavender: 'e6e6fa',
            lavenderblush: 'fff0f5',
            lawngreen: '7cfc00',
            lemonchiffon: 'fffacd',
            lightblue: 'add8e6',
            lightcoral: 'f08080',
            lightcyan: 'e0ffff',
            lightgoldenrodyellow: 'fafad2',
            lightgrey: 'd3d3d3',
            lightgreen: '90ee90',
            lightpink: 'ffb6c1',
            lightsalmon: 'ffa07a',
            lightseagreen: '20b2aa',
            lightskyblue: '87cefa',
            lightslateblue: '8470ff',
            lightslategray: '778899',
            lightsteelblue: 'b0c4de',
            lightyellow: 'ffffe0',
            lime: '00ff00',
            limegreen: '32cd32',
            linen: 'faf0e6',
            magenta: 'ff00ff',
            maroon: '800000',
            mediumaquamarine: '66cdaa',
            mediumblue: '0000cd',
            mediumorchid: 'ba55d3',
            mediumpurple: '9370d8',
            mediumseagreen: '3cb371',
            mediumslateblue: '7b68ee',
            mediumspringgreen: '00fa9a',
            mediumturquoise: '48d1cc',
            mediumvioletred: 'c71585',
            midnightblue: '191970',
            mintcream: 'f5fffa',
            mistyrose: 'ffe4e1',
            moccasin: 'ffe4b5',
            navajowhite: 'ffdead',
            navy: '000080',
            oldlace: 'fdf5e6',
            olive: '808000',
            olivedrab: '6b8e23',
            orange: 'ffa500',
            orangered: 'ff4500',
            orchid: 'da70d6',
            palegoldenrod: 'eee8aa',
            palegreen: '98fb98',
            paleturquoise: 'afeeee',
            palevioletred: 'd87093',
            papayawhip: 'ffefd5',
            peachpuff: 'ffdab9',
            peru: 'cd853f',
            pink: 'ffc0cb',
            plum: 'dda0dd',
            powderblue: 'b0e0e6',
            purple: '800080',
            red: 'ff0000',
            rosybrown: 'bc8f8f',
            royalblue: '4169e1',
            saddlebrown: '8b4513',
            salmon: 'fa8072',
            sandybrown: 'f4a460',
            seagreen: '2e8b57',
            seashell: 'fff5ee',
            sienna: 'a0522d',
            silver: 'c0c0c0',
            skyblue: '87ceeb',
            slateblue: '6a5acd',
            slategray: '708090',
            snow: 'fffafa',
            springgreen: '00ff7f',
            steelblue: '4682b4',
            tan: 'd2b48c',
            teal: '008080',
            thistle: 'd8bfd8',
            tomato: 'ff6347',
            turquoise: '40e0d0',
            violet: 'ee82ee',
            violetred: 'd02090',
            wheat: 'f5deb3',
            white: 'ffffff',
            whitesmoke: 'f5f5f5',
            yellow: 'ffff00',
            yellowgreen: '9acd32'
        },

        // array of color definition objects
        colorDefs = [{
            re: /^\s*rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*([\d.]{1,3})\s*\)\s*$/,
            example: ['rgba(123, 234, 45, 0.5)', 'rgba(255,234,245,1.0)'],
            process: function (bits) {
                return [
                    parseInt(bits[1], 10),
                    parseInt(bits[2], 10),
                    parseInt(bits[3], 10)
                ];
            }
        }, {
            re: /^\s*rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)\s*$/,
            example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],
            process: function (bits) {
                return [
                    parseInt(bits[1], 10),
                    parseInt(bits[2], 10),
                    parseInt(bits[3], 10)
                ];
            }
        }, {
            re: /^(\w{2})(\w{2})(\w{2})$/,
            example: ['#00ff00', '336699'],
            process: function (bits) {
                return [
                    parseInt(bits[1], 16),
                    parseInt(bits[2], 16),
                    parseInt(bits[3], 16)
                ];
            }
        }, {
            re: /^(\w{1})(\w{1})(\w{1})$/,
            example: ['#fb0', 'f0f'],
            process: function (bits) {
                return [
                    parseInt(bits[1] + bits[1], 16),
                    parseInt(bits[2] + bits[2], 16),
                    parseInt(bits[3] + bits[3], 16)
                ];
            }
        }];

    /**
     * Converts a valid HTML/CSS color string into a rgb value array. This is the base
     * function for the following wrapper functions which only adjust the output to
     * different flavors like an object, string or hex values.
     * @param {String,Array,Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black',
     * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
     * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
     * expects the parameters ag and ab.
     * @param {Number} ag
     * @param {Number} ab
     * @returns {Array} RGB color values as an array [r, g, b] with values ranging from 0 to 255.
     */
    JXG.rgbParser = function (color, ag, ab) {
        var color_string, channels, re, processor, bits, i,
            r, g, b,
            values = color,
            testFloat;

        if (!Type.exists(color)) {
            return [];
        }

        if (Type.exists(ag) && Type.exists(ab)) {
            values = [color, ag, ab];
        }

        color_string = values;

        testFloat = false;
        if (Type.isArray(color_string)) {
            for (i = 0; i < 3; i++) {
                testFloat = testFloat || /\./.test(values[i].toString());
            }

            for (i = 0; i < 3; i++) {
                testFloat = testFloat && (values[i] >= 0.0) && (values[i] <= 1.0);
            }

            if (testFloat) {
                return [Math.ceil(values[0] * 255), Math.ceil(values[1] * 255), Math.ceil(values[2] * 255)];
            }

            return values;
        }

        if (typeof values === 'string') {
            color_string = values;
        }

        // strip any leading #
        if (color_string.charAt(0) === '#') { // remove # if any
            color_string = color_string.substr(1, 6);
        }

        color_string = color_string.replace(/ /g, '').toLowerCase();

        // before getting into regexps, try simple matches
        // and overwrite the input
        color_string = simpleColors[color_string] || color_string;

        // search through the colorDefs definitions to find a match
        for (i = 0; i < colorDefs.length; i++) {
            re = colorDefs[i].re;
            processor = colorDefs[i].process;
            bits = re.exec(color_string);

            if (bits) {
                channels = processor(bits);
                r = channels[0];
                g = channels[1];
                b = channels[2];
            }

        }

        if (isNaN(r) || isNaN(g) || isNaN(b)) {
            return [];
        }

        // validate/cleanup values
        r = (r < 0 || isNaN(r)) ? 0 : ((r > 255) ? 255 : r);
        g = (g < 0 || isNaN(g)) ? 0 : ((g > 255) ? 255 : g);
        b = (b < 0 || isNaN(b)) ? 0 : ((b > 255) ? 255 : b);

        return [r, g, b];
    };

    /**
     * Converts a valid HTML/CSS color string into a string of the 'rgb(r, g, b)' format.
     * @param {String,Array,Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black',
     * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
     * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
     * expects the parameters ag and ab.
     * @param {Number} ag
     * @param {Number} ab
     * @returns {String} A 'rgb(r, g, b)' formatted string
     */
    JXG.rgb2css = function (color, ag, ab) {
        var r;

        r = JXG.rgbParser(color, ag, ab);

        return 'rgb(' + r[0] + ', ' + r[1] + ', ' + r[2] + ')';
    };

    /**
     * Converts a valid HTML/CSS color string into a HTML rgb string.
     * @param {String,Array,Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black',
     * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
     * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
     * expects the parameters ag and ab.
     * @param {Number} ag
     * @param {Number} ab
     * @returns {String} A '#rrggbb' formatted string
     */
    JXG.rgb2hex = function (color, ag, ab) {
        var r, g, b;

        r = JXG.rgbParser(color, ag, ab);
        g = r[1];
        b = r[2];
        r = r[0];
        r = r.toString(16);
        g = g.toString(16);
        b = b.toString(16);

        if (r.length === 1) {
            r = '0' + r;
        }

        if (g.length === 1) {
            g = '0' + g;
        }

        if (b.length === 1) {
            b = '0' + b;
        }

        return '#' + r + g + b;
    };

    /**
     * Converts a valid HTML/CSS color string from the '#rrggbb' format into the 'rgb(r, g, b)' format.
     * @param {String} hex A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', or 'black'
     * @deprecated Use {@link JXG#rgb2css} instead.
     * @returns {String} A 'rgb(r, g, b)' formatted string
     */
    JXG.hex2rgb = function (hex) {
        JXG.deprecated('JXG.hex2rgb()', 'JXG.rgb2css()');
        return JXG.rgb2css(hex);
    };

    /**
     * Converts HSV color to RGB color.
     * Based on C Code in "Computer Graphics -- Principles and Practice,"
     * Foley et al, 1996, p. 593.
     * See also http://www.efg2.com/Lab/Graphics/Colors/HSV.htm
     * @param {Number} H value between 0 and 360
     * @param {Number} S value between 0.0 (shade of gray) to 1.0 (pure color)
     * @param {Number} V value between 0.0 (black) to 1.0 (white)
     * @returns {String} RGB color string
     */
    JXG.hsv2rgb = function (H, S, V) {
        var R, G, B, f, i, hTemp, p, q, t;

        H = ((H % 360.0) + 360.0) % 360;

        if (S === 0) {
            if (isNaN(H) || H < Mat.eps) {
                R = V;
                G = V;
                B = V;
            } else {
                return '#ffffff';
            }
        } else {
            if (H >= 360) {
                hTemp = 0.0;
            } else {
                hTemp = H;
            }

            // h is now IN [0,6)
            hTemp = hTemp / 60;
            // largest integer <= h
            i = Math.floor(hTemp);
            // fractional part of h
            f = hTemp - i;
            p = V * (1.0 - S);
            q = V * (1.0 - (S * f));
            t = V * (1.0 - (S * (1.0 - f)));

            switch (i) {
                case 0:
                    R = V;
                    G = t;
                    B = p;
                    break;
                case 1:
                    R = q;
                    G = V;
                    B = p;
                    break;
                case 2:
                    R = p;
                    G = V;
                    B = t;
                    break;
                case 3:
                    R = p;
                    G = q;
                    B = V;
                    break;
                case 4:
                    R = t;
                    G = p;
                    B = V;
                    break;
                case 5:
                    R = V;
                    G = p;
                    B = q;
                    break;
            }
        }

        R = Math.round(R * 255).toString(16);
        R = (R.length === 2) ? R : ((R.length === 1) ? '0' + R : '00');
        G = Math.round(G * 255).toString(16);
        G = (G.length === 2) ? G : ((G.length === 1) ? '0' + G : '00');
        B = Math.round(B * 255).toString(16);
        B = (B.length === 2) ? B : ((B.length === 1) ? '0' + B : '00');

        return ['#', R, G, B].join('');
    };

    /**
     * Converts a color from the RGB color space into the HSV space. Input can be any valid HTML/CSS color definition.
     * @param {String,Array,Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black',
     * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
     * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
     * expects the parameters ag and ab.
     * @param {Number} ag
     * @param {Number} ab
     * @returns {Array} Contains the h, s, and v value in this order.
     * @see http://zach.in.tu-clausthal.de/teaching/cg1_0708/folien/13_color_3_4up.pdf
     */
    JXG.rgb2hsv = function (color, ag, ab) {
        var r, g, b, fr, fg, fb, fmax, fmin, h, s, v, max, min;

        r = JXG.rgbParser(color, ag, ab);

        g = r[1];
        b = r[2];
        r = r[0];
        fr = r / 255.0;
        fg = g / 255.0;
        fb = b / 255.0;
        max = Math.max(r, g, b);
        min = Math.min(r, g, b);
        fmax = max / 255.0;
        fmin = min / 255.0;

        v = fmax;
        s = 0.0;

        if (v > 0) {
            s = (v - fmin) / v;
        }

        h = 1.0 / (fmax - fmin);

        if (s > 0) {
            if (max === r) {
                h = (fg - fb) * h;
            } else if (max === g) {
                h = 2 + (fb - fr) * h;
            } else {
                h = 4 + (fr - fg) * h;
            }
        }

        h *= 60;

        if (h < 0) {
            h += 360;
        }

        if (max === min) {
            h = 0.0;
        }

        return [h, s, v];
    };

    /**
     * Converts a color from the RGB color space into the LMS space. Input can be any valid HTML/CSS color definition.
     * @param {String,Array,Number} color A valid HTML or CSS styled color value, e.g. '#12ab21', '#abc', 'black',
     * or 'rgb(12, 132, 233)'. This can also be an array containing three color values either from 0.0 to 1.0 or
     * from 0 to 255. They will be interpreted as red, green, and blue values. In case this is a number this method
     * expects the parameters ag and ab.
     * @param {Number} ag
     * @param {Number} ab
     * @returns {Array} Contains the l, m, and s value in this order.
     */
    JXG.rgb2LMS = function (color, ag, ab) {
        var r, g, b, l, m, s, ret,
            // constants
            matrix = [[0.05059983, 0.08585369, 0.00952420],
                [0.01893033, 0.08925308, 0.01370054],
                [0.00292202, 0.00975732, 0.07145979]];

        r = JXG.rgbParser(color, ag, ab);
        g = r[1];
        b = r[2];
        r = r[0];

        // de-gamma
        // Maybe this can be made faster by using a cache
        r = Math.pow(r, 0.476190476);
        g = Math.pow(g, 0.476190476);
        b = Math.pow(b, 0.476190476);

        l = r * matrix[0][0] + g * matrix[0][1] + b * matrix[0][2];
        m = r * matrix[1][0] + g * matrix[1][1] + b * matrix[1][2];
        s = r * matrix[2][0] + g * matrix[2][1] + b * matrix[2][2];

        ret = [l, m, s];
        ret.l = l;
        ret.m = m;
        ret.s = s;

        return ret;
    };

    /**
     * Convert color information from LMS to RGB color space.
     * @param {Number} l
     * @param {Number} m
     * @param {Number} s
     * @returns {Array} Contains the r, g, and b value in this order.
     */
    JXG.LMS2rgb = function (l, m, s) {
        var r, g, b, ret,
            // constants
            matrix = [[30.830854, -29.832659, 1.610474],
                [-6.481468, 17.715578, -2.532642],
                [-0.375690, -1.199062, 14.273846]],

            // re-gamma, inspired by GIMP modules/display-filter-color-blind.c:
            // Copyright (C) 2002-2003 Michael Natterer <mitch@gimp.org>,
            //                         Sven Neumann <sven@gimp.org>,
            //                         Robert Dougherty <bob@vischeck.com> and
            //                         Alex Wade <alex@vischeck.com>
            // This code is an implementation of an algorithm described by Hans Brettel,
            // Francoise Vienot and John Mollon in the Journal of the Optical Society of
            // America V14(10), pg 2647. (See http://vischeck.com/ for more info.)
            lut_lookup = function (value) {
                var offset = 127, step = 64;

                while (step > 0) {
                    if (Math.pow(offset, 0.476190476) > value) {
                        offset -= step;
                    } else {
                        if (Math.pow(offset + 1, 0.476190476) > value) {
                            return offset;
                        }

                        offset += step;
                    }

                    step /= 2;
                }

                /*  the algorithm above can't reach 255  */
                if (offset === 254 && 13.994955247 < value) {
                    return 255;
                }

                return offset;
            };

        // transform back to rgb
        r = l * matrix[0][0] + m * matrix[0][1] + s * matrix[0][2];
        g = l * matrix[1][0] + m * matrix[1][1] + s * matrix[1][2];
        b = l * matrix[2][0] + m * matrix[2][1] + s * matrix[2][2];

        r = lut_lookup(r);
        g = lut_lookup(g);
        b = lut_lookup(b);

        ret = [r, g, b];
        ret.r = r;
        ret.g = g;
        ret.b = b;

        return ret;
    };

    /**
     * Splits a RGBA color value like #112233AA into it's RGB and opacity parts.
     * @param {String} rgba A RGBA color value
     * @returns {Array} An array containing the rgb color value in the first and the opacity in the second field.
     */
    JXG.rgba2rgbo = function (rgba) {
        var opacity;

        if (rgba.length === 9 && rgba.charAt(0) === '#') {
            opacity = parseInt(rgba.substr(7, 2).toUpperCase(), 16) / 255;
            rgba = rgba.substr(0, 7);
        } else {
            opacity = 1;
        }

        return [rgba, opacity];
    };

    /**
     * Generates a RGBA color value like #112233AA from it's RGB and opacity parts.
     * @param {String} rgb A RGB color value.
     * @param {Number} o The desired opacity >=0, <=1.
     * @returns {String} The RGBA color value.
     */
    JXG.rgbo2rgba = function (rgb, o) {
        var rgba;

        if (rgb === 'none') {
            return rgb;
        }

        rgba = Math.round(o * 255).toString(16);
        if (rgba.length === 1) {
            rgba = "0" + rgba;
        }

        return rgb + rgba;
    };

    /**
     * Decolorizes the given color.
     * @param {String} color HTML string containing the HTML color code.
     * @returns {String} Returns a HTML color string
     */
    JXG.rgb2bw = function (color) {
        var x, tmp, arr,
            HexChars = "0123456789ABCDEF";

        if (color === 'none') {
            return color;
        }

        arr = JXG.rgbParser(color);
        x = Math.floor(0.3 * arr[0] + 0.59 * arr[1] + 0.11 * arr[2]);

        // rgbParser and Math.floor ensure that x is 0 <= x <= 255.
        // Bitwise operators can be used.
        /*jslint bitwise: true*/
        tmp = HexChars.charAt((x >> 4) & 0xf) + HexChars.charAt(x & 0xf);

        color = "#" + tmp + tmp + tmp;

        return color;
    };

    /**
     * Converts a color into how a colorblind human approximately would see it.
     * @param {String} color HTML string containing the HTML color code.
     * @param {String} deficiency The type of color blindness. Possible
     * options are <i>protanopia</i>, <i>deuteranopia</i>, and <i>tritanopia</i>.
     * @returns {String} Returns a HTML color string
     */
    JXG.rgb2cb = function (color, deficiency) {
        var rgb, l, m, s, lms, tmp,
            a1, b1, c1, a2, b2, c2,
            inflection,
            HexChars = "0123456789ABCDEF";

        if (color === 'none') {
            return color;
        }

        lms = JXG.rgb2LMS(color);
        l = lms[0];
        m = lms[1];
        s = lms[2];

        deficiency = deficiency.toLowerCase();

        switch (deficiency) {
            case "protanopia":
                a1 = -0.06150039994295001;
                b1 = 0.08277001656812001;
                c1 = -0.013200141220000003;
                a2 = 0.05858939668799999;
                b2 = -0.07934519995360001;
                c2 = 0.013289415272000003;
                inflection = 0.6903216543277437;

                tmp = s / m;

                if (tmp < inflection) {
                    l = -(b1 * m + c1 * s) / a1;
                } else {
                    l = -(b2 * m + c2 * s) / a2;
                }
                break;
            case "tritanopia":
                a1 = -0.00058973116217;
                b1 = 0.007690316482;
                c1 = -0.01011703519052;
                a2 = 0.025495080838999994;
                b2 = -0.0422740347;
                c2 = 0.017005316784;
                inflection = 0.8349489908460004;

                tmp = m / l;

                if (tmp < inflection) {
                    s = -(a1 * l + b1 * m) / c1;
                } else {
                    s = -(a2 * l + b2 * m) / c2;
                }
                break;
            default:
                a1 = -0.06150039994295001;
                b1 = 0.08277001656812001;
                c1 = -0.013200141220000003;
                a2 = 0.05858939668799999;
                b2 = -0.07934519995360001;
                c2 = 0.013289415272000003;
                inflection = 0.5763833686400911;

                tmp = s / l;

                if (tmp < inflection) {
                    m = -(a1 * l + c1 * s) / b1;
                } else {
                    m = -(a2 * l + c2 * s) / b2;
                }
                break;
        }

        rgb = JXG.LMS2rgb(l, m, s);

        // LMS2rgb returns an array of values ranging from 0 to 255 (both included)
        // bitwise operators are safe to use.
        /*jslint bitwise: true*/
        tmp = HexChars.charAt((rgb[0] >> 4) & 0xf) + HexChars.charAt(rgb[0] & 0xf);
        color = "#" + tmp;
        tmp = HexChars.charAt((rgb[1] >> 4) & 0xf) + HexChars.charAt(rgb[1] & 0xf);
        color += tmp;
        tmp = HexChars.charAt((rgb[2] >> 4) & 0xf) + HexChars.charAt(rgb[2] & 0xf);
        color += tmp;

        return color;
    };

    /**
     * Determines highlight color to a given color. Done by reducing (or increasing) the opacity.
     * @param {String} color HTML RGBA string containing the HTML color code.
     * @returns {String} Returns a HTML RGBA color string
     */
    JXG.autoHighlight = function (colstr) {
        var col = JXG.rgba2rgbo(colstr),
            c = col[0],
            opa = col[1];

        if (colstr.charAt(0) === '#') {
            if (opa < 0.3) {
                opa *= 1.8;
            } else {
                opa *= 0.4;
            }

            return JXG.rgbo2rgba(c, opa);
        }

        return colstr;
    };

    /**
     * Calculate whether a light or a dark color is needed as a contrast.
     * Especially useful to determine whether white or black font goes
     * better with a given background color.
     * @param {String} hexColor HEX value of color.
     * @param {String} [darkColor="#000000"] HEX string for a dark color.
     * @param {String} [lightColor="#ffffff"] HEX string for a light color.
     * @param {Number} [threshold=8]
     * @returns {String} Returns darkColor or lightColor.
     */
    JXG.contrast = function (hexColor, darkColor, lightColor, threshold) {
        var rgb,
            black = '#000000',
            rgbBlack,
            l1, l2,
            contrastRatio;

        darkColor = darkColor || '#000000';
        lightColor = lightColor || '#ffffff';
        threshold = threshold || 7;

        // hexColor RGB
        rgb = JXG.rgbParser(hexColor);

        // Black RGB
        rgbBlack = JXG.rgbParser(black);

        // Calc contrast ratio
        l1 = 0.2126 * Math.pow(rgb[0] / 255, 2.2) +
            0.7152 * Math.pow(rgb[1] / 255, 2.2) +
            0.0722 * Math.pow(rgb[2] / 255, 2.2);

        l2 = 0.2126 * Math.pow(rgbBlack[0] / 255, 2.2) +
            0.7152 * Math.pow(rgbBlack[1] / 255, 2.2) +
            0.0722 * Math.pow(rgbBlack[2] / 255, 2.2);

        if (l1 > l2) {
            contrastRatio = Math.floor((l1 + 0.05) / (l2 + 0.05));
        } else {
            contrastRatio = Math.floor((l2 + 0.05) / (l1 + 0.05));
        }
        contrastRatio = contrastRatio - 1;

        // If contrast is more than threshold, return darkColor
        if (contrastRatio > threshold) {
            return darkColor;
        }
        // if not, return lightColor.
        return lightColor;
    };

    /**
     * Use the color scheme of JSXGraph up to version 1.3.2.
     * This method has to be called before JXG.JSXGraph.initBoard();
     *
     * @see JXG.palette
     * @see JXG.paletteWong
     *
     * @example
     *
     * JXG.setClassicColors();
     * var board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-5, 5, 5,-5]});
     *
     */
    JXG.setClassicColors = function() {
        JXG.Options.elements.strokeColor = 'blue';
        JXG.Options.elements.fillColor = 'red';
        JXG.Options.hatch.strokeColor = 'blue';
        JXG.Options.angle.fillColor = '#ff7f00';
        JXG.Options.angle.highlightFillColor = '#ff7f00';
        JXG.Options.angle.strokeColor = '#ff7f00';
        JXG.Options.angle.label.strokeColor = 'blue';
        JXG.Options.arc.strokeColor = 'blue';
        JXG.Options.circle.center.fillColor = 'red';
        JXG.Options.circle.center.strokeColor = 'blue';
        JXG.Options.circumcircle.strokeColor = 'blue';
        JXG.Options.circumcircle.center.fillColor = 'red';
        JXG.Options.circumcircle.center.strokeColor = 'blue';
        JXG.Options.circumcirclearc.strokeColor = 'blue';
        JXG.Options.circumcirclesector.strokeColor = 'blue';
        JXG.Options.circumcirclesector.fillColor = 'green';
        JXG.Options.circumcirclesector.highlightFillColor = 'green';
        JXG.Options.conic.strokeColor = 'blue';
        JXG.Options.curve.strokeColor = 'blue';
        JXG.Options.incircle.strokeColor = 'blue';
        JXG.Options.incircle.center.fillColor = 'red';
        JXG.Options.incircle.center.strokeColor = 'blue';
        JXG.Options.inequality.fillColor = 'red';
        JXG.Options.integral.fillColor = 'red';
        JXG.Options.integral.curveLeft.color = 'red';
        JXG.Options.integral.curveRight.color = 'red';
        JXG.Options.line.strokeColor = 'blue';
        JXG.Options.point.fillColor = 'red';
        JXG.Options.point.strokeColor = 'red';
        JXG.Options.polygon.fillColor = 'green';
        JXG.Options.polygon.highlightFillColor = 'green';
        JXG.Options.polygon.vertices.strokeColor = 'red';
        JXG.Options.polygon.vertices.fillColor = 'red';
        JXG.Options.regularpolygon.fillColor = 'green';
        JXG.Options.regularpolygon.highlightFillColor = 'green';
        JXG.Options.regularpolygon.vertices.strokeColor = 'red';
        JXG.Options.regularpolygon.vertices.fillColor = 'red';
        JXG.Options.riemannsum.fillColor = 'yellow';
        JXG.Options.sector.fillColor = 'green';
        JXG.Options.sector.highlightFillColor = 'green';
        JXG.Options.semicircle.center.fillColor = 'red';
        JXG.Options.semicircle.center.strokeColor = 'blue';
        JXG.Options.slopetriangle.fillColor = 'red';
        JXG.Options.slopetriangle.highlightFillColor = 'red';
        JXG.Options.turtle.arrow.strokeColor = 'blue';
    };

    JXG.extend(JXG, /** @lends JXG */ {
        /**
         * Bang Wong color palette,
         * optimized for various type
         * of color blindness.
         * It contains values for
         * <ul>
         * <li> 'black'
         * <li> 'orange'
         * <li> 'skyblue'
         * <li> 'bluishgreen'
         * <li> 'yellow'
         * <li> 'darkblue'
         * <li> 'vermillion'
         * <li> 'reddishpurple'
         * </ul>
         *
         * As substitutes for standard colors, it contains the following aliases:
         *
         * <ul>
         * <li> black (= #000000)
         * <li> blue (= darkblue)
         * <li> green (= bluishgreen)
         * <li> purple (= reddishpurple)
         * <li> red (= vermillion)
         * <li> white (= #ffffff)
         * </ul>
         *
         * See <a href="https://www.nature.com/articles/nmeth.1618">Bang Wong: "Points of view: Color blindness"</a>
         * and
         * <a href="https://davidmathlogic.com/colorblind/">https://davidmathlogic.com/colorblind/</a>.
         *
         * @name JXG.paletteWong
         * @type Object
         * @see JXG.palette
         * @example
         * var p = board.create('line', [[-1, 1], [2, -3]], {strokeColor: JXG.paletteWong.yellow});
         */
        paletteWong: {
            black: '#000000',
            orange: '#E69F00',
            skyblue: '#56B4E9',
            bluishgreen: '#009E73',
            yellow: '#F0E442',
            darkblue: '#0072B2',
            vermillion: '#D55E00',
            reddishpurple: '#CC79A7',

            blue: '#0072B2',
            red: '#D55E00',   // vermillion
            green: '#009E73', // bluishgreen
            purple: '#CC79A7', // reddishpurple
            white: '#ffffff'
        }
    });

    /**
     * Default color palette.
     * Contains at least color values for
     * <ul>
     * <li> black
     * <li> blue
     * <li> green
     * <li> purple
     * <li> red
     * <li> white
     * <li> yellow
     * </ul>
     *
     * @name JXG.palette
     * @type Object
     * @default JXG.paletteWong
     * @see JXG.paletteWong
     *
     * @example
     *
     * var p = board.create('line', [[-1, 1], [2, -3]], {strokeColor: JXG.palette.yellow});
     *
     */
    JXG.palette = JXG.paletteWong;

    return JXG;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG:true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 math/math
 utils/color
 utils/type
 */

define('options',[
    'jxg', 'base/constants', 'math/math', 'utils/color', 'utils/type'
], function (JXG, Const, Mat, Color, Type) {

    "use strict";

    /**
     * Options Namespace
     * @description These are the default options of the board and of all geometry elements.
     * @namespace
     * @name JXG.Options
     */
    JXG.Options = {
        jc: {
            enabled: true,
            compile: true
        },

        /*
         * Options that are used directly within the board class
         */
        board: {
            /**#@+
             * @visprop
             */

            //updateType: 'hierarchical', // 'all'

            /**
             * Bounding box of the visible area in user coordinates.
             * It is an array consisting of four values:
             * [x<sub>1</sub>, y<sub>1</sub>, x<sub>2</sub>, y<sub>2</sub>]
             *
             * The canvas will be spanned from the upper left corner (<sub>1</sub>, y<sub>1</sub>)
             * to the lower right corner (x<sub>2</sub>, y<sub>2</sub>).
             *
             * @name JXG.Board#boundingbox
             * @type Array
             * @default [-5, 5, 5, -5]
             * @example
             * var board = JXG.JSXGraph.initBoard('jxgbox', {
             *         boundingbox: [-5, 5, 5, -5],
             *         axis: true
             *     });
             */
            boundingBox: [-5, 5, 5, -5],

            /**
             * Maximal bounding box of the visible area in user coordinates.
             * It is an array consisting of four values:
             * [x<sub>1</sub>, y<sub>1</sub>, x<sub>2</sub>, y<sub>2</sub>]
             *
             * The bounding box of the canvas must be inside of this maximal
             * boundings box.
             * @name JXG.Board#maxboundingbox
             * @type Array
             * @see JXG.Board#boundingbox
             * @default [-Infinity, Infinity, Infinity, -Infinity]
             *
             * @example
             * var board = JXG.JSXGraph.initBoard('jxgbox', {
             *         boundingbox: [-5, 5, 5, -5],
             *         maxboundingbox: [-8, 8, 8, -8],
             *         pan: {enabled: true},
             *         axis: true
             *     });
             *
             * </pre><div id="JXG065e2750-217c-48ed-a52b-7d7df6de7055" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG065e2750-217c-48ed-a52b-7d7df6de7055', {
             *             showcopyright: false, shownavigation: false,
             *             boundingbox: [-5,5,5,-5],
             *             maxboundingbox: [-8,8,8,-8],
             *             pan: {enabled: true},
             *             axis:true
             *         });
             *
             *     })();
             *
             * </script><pre>
             *
             */
            maxBoundingBox: [-Infinity, Infinity, Infinity, -Infinity],

            /**
             * Additional zoom factor multiplied to {@link JXG.Board#zoomX} and {@link JXG.Board#zoomY}.
             *
             * @name JXG.Board#zoomFactor
             * @type Number
             * @default 1.0
             */
            zoomFactor: 1,

            /**
             * Zoom factor in horizontal direction.
             *
             * @name JXG.Board#zoomX
             * @see JXG.Board#zoomY
             * @type Number
             * @default 1.0
             */
            zoomX: 1,

            /**
             * Zoom factor in vertical direction.
             *
             * @name JXG.Board#zoomY
             * @see JXG.Board#zoomX
             * @type Number
             * @default 1.0
             */
            zoomY: 1,

            /**
             * Title string for the board.
             * Primarily used in an invisible text element which is adressed by
             * the attribute 'aria-labelledby' from the JSXGraph container.
             * JSXGraph creates a new div-element with id "{containerid}_ARIAlabel"
             * containing this string.
             *
             * @name JXG.Board#title
             * @see JXG.Board#description
             * @type String
             * @default ''
             *
             */
            title: '',

            /**
             * Description string for the board.
             * Primarily used in an invisible text element which is adressed by
             * the attribute 'aria-describedby' from the JSXGraph container.
             * JSXGraph creates a new div-element with id "{containerid}_ARIAdescription"
             * containing this string.
             *
             * @name JXG.Board#description
             * @see JXG.Board#title
             * @type String
             * @default ''
             *
             */
            description: '',

            /**
             * Show copyright string in canvas.
             *
             * @name JXG.Board#showCopyright
             * @type Boolean
             * @default true
             */
            showCopyright: true,

            /**
             * Show default axis.
             * If shown, the horizontal axis can be accessed via JXG.Board.defaultAxes.x, the
             * vertical axis can be accessed via JXG.Board.defaultAxes.y.
             * Both axes have a sub-element "defaultTicks".
             *
             * Value can be Boolean or an object containing axis attributes.
             *
             * @name JXG.Board#axis
             * @type Boolean
             * @default false
             */
            axis: false,

            /**
             * Attributes for the default axes in case of the attribute
             * axis:true in {@link JXG.JSXGraph#initBoard}.
             *
             * @name JXG.Board#defaultAxes
             * @type Object
             * @default {x: {name:'x'}, y: {name: 'y'}}
             *
             */
            defaultAxes: {
                x: {
                    name: 'x',
                    ticks: {
                        label: {
                            visible: 'inherit',
                            anchorX: 'middle',
                            anchorY: 'top',
                            fontSize: 12,
                            offset: [0, -3]
                        },
                        drawZero: false,
                        visible: 'inherit'
                    }
                },
                y: {
                    name: 'y',
                    ticks: {
                        label: {
                            visible: 'inherit',
                            anchorX: 'right',
                            anchorY: 'middle',
                            fontSize: 12,
                            offset: [-6, 0]
                        },
                        tickEndings: [1, 0],
                        drawZero: false,
                        visible: 'inherit'
                    }
                }
            },

            /**
             * Display of navigation arrows and zoom buttons in the navigation bar.
             *
             * @name JXG.Board#showNavigation
             * @type Boolean
             * @default true
             */
            showNavigation: true,

            /**
             * Display of zoom buttons in the navigation bar. To show zoom buttons, additionally
             * showNavigation has to be set to true.
             *
             * @name JXG.Board#showZoom
             * @type Boolean
             * @default true
             */
            showZoom: true,

            /**
             * Show a button in the navigation bar to force reload of a construction.
             * Works only with the JessieCode tag.
             *
             * @name JXG.Board#showReload
             * @type Boolean
             * @default false
             */
            showReload: false,

            /**
             * Show a button in the navigation bar to enable screenshots.
             *
             * @name JXG.Board#showScreenshot
             * @type Boolean
             * @default false
             */
            showScreenshot: false,

            /**
             * Attributes to control the screenshot function.
             * The following attributes can be set:
             * <ul>
             *  <li>scale: scaling factor (default=1.0)
             *  <li>type: format of the screenshot image. Default: png
             *  <li>symbol: Unicode symbol which is shown in the navigation bar. Default: '\u2318'
             *  <li>css: CSS rules to format the div element containing the screen shot image
             *  <li>cssButton: CSS rules to format the close button of the div element containing the screen shot image
             * </ul>
             *
             * @name JXG.Board#screenshot
             * @type Object
             */
            screenshot: {
                scale: 1.0,
                type: 'png',
                symbol: '\u2318', //'\u22b9', //'\u26f6',
                css: 'background-color:#eeeeee; opacity:1.0; border:2px solid black; border-radius:10px; text-align:center',
                cssButton:  'padding: 4px 10px; border: solid #356AA0 1px; border-radius: 5px; position: absolute; right: 2ex; top: 2ex; background-color: rgba(255, 255, 255, 0.3);'
            },

            /**
             * Show a button in the navigation bar to start fullscreen mode.
             *
             * @name JXG.Board#showFullscreen
             * @type Boolean
             * @see JXG.Board#fullscreen
             * @default false
             */
            showFullscreen: false,

            /**
             * Attribute(s) to control the fullscreen icon. The attribute "showFullscreen"
             * controls if the icon is shown.
             * The following attribute(s) can be set:
             * <ul>
             *  <li>symbol (String): Unicode symbol which is shown in the navigation bar. Default: '\u25a1'
             *  <li>id (String): Id of the HTML element which is brought to full screen or null if the JSXgraph div is taken.
             * It may be an outer div element, e.g. if the old aspect ratio trick is used. Default: null, i.e. use the JSXGraph div.
             * </ul>
             *
             * @example
             * var board = JXG.JSXGraph.initBoard('35bec5a2-fd4d-11e8-ab14-901b0e1b8723',
             *             {boundingbox: [-8, 8, 8,-8], axis: true,
             *             showcopyright: false,
             *             showFullscreen: true,
             *             fullscreen: {
             *                  symbol: '\u22c7'
             *              }
             *             });
             * var pol = board.create('polygon', [[0, 1], [3,4], [1,-4]], {fillColor: 'yellow'});
             *
             * </pre><div id="JXGa35bec5a2-fd4d-11e8-ab14-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXGa35bec5a2-fd4d-11e8-ab14-901b0e1b8723',
             *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false,
             *              showFullscreen: true,
             *              fullscreen: {
             *                  symbol: '\u22c7'
             *                  }
             *             });
             *     var pol = board.create('polygon', [[0, 1], [3,4], [1,-4]], {fillColor: 'yellow'});
             *     })();
             *
             * </script><pre>
             *
             * @name JXG.Board#fullscreen
             * @see JXG.Board#showFullscreen
             * @type Object
             */
            fullscreen: {
                symbol: '\u25a1', // '\u26f6' (not supported by MacOS), // '\u25a1'
                id: null
            },

            /**
             * Show a button which allows to clear all traces of a board.
             *
             * @name JXG.Board#showClearTraces
             * @type Boolean
             * @default false
             */
            showClearTraces: false,

            /**
             * If set to true the bounding box might be changed such that
             * the ratio of width and height of the hosting HTML div is equal
             * to the ratio of width and height of the bounding box.
             *
             * This is necessary if circles should look like circles and not
             * like ellipses. It is recommended to set keepAspectRatio = true
             * for geometric applets. For function plotting keepAspectRatio = false
             * might be the better choice.
             *
             * @name JXG.Board#keepAspectRatio
             * @see JXG.Board#boundingbox
             * @see JXG.Board#setBoundingBox
             * @type Boolean
             * @default false
             */
            keepAspectRatio: false,

            /**
             * If set true and
             * hasPoint() is true for both an element and it's label,
             * the element (and not the label) is taken as drag element.
             *
             * If set false and hasPoint() is true for both an element and it's label,
             * the label is taken (if it is on a higher layer than the element)
             *
             * @name JXG.Board#ignoreLabels
             * @type Booelan
             * @default true
             */
            ignoreLabels: true,

            /**
             * Maximum number of digits in automatic label generation.
             * For example, if set to 1 automatic point labels end at "Z".
             * If set to 2, point labels end at "ZZ".
             *
             * @name JXG.Board#maxNameLength
             * @see JXG.Board#generateName
             * @type Number
             * @default 1
             */
            maxNameLength: 1,

            /**
             * Supply the document object. Defaults to window.document
             *
             * @name JXG.Board#document
             * @type DOM object
             * @default false (meaning window.document)
             */
            document: false,

            /**
             * If true the first element of the set JXG.board.objects having hasPoint==true is taken as drag element.
             *
             * @name JXG.Board#takeFirst
             * @type Boolean
             * @default false
             */
            takeFirst: false,

            /**
            * If true, when read from a file or string - the size of the div can be changed by the construction text.
            *
            * @name JXG.Board#takeSizeFromFile
            * @type Boolean
            * @default false
            */
            takeSizeFromFile: false,

            /**
             * Default rendering engine. Possible values are 'svg', 'canvas', 'vml', 'no', or 'auto'.
             * If the rendering engine is not available JSXGraph tries to detect a different engine.
             *
             * <p>
             * In case of 'canvas' it is advisable to call 'board.update()' after all elements have been
             * constructed. This ensures that all elements are drawn with their intended visual appearance.
             *
             * @name JXG.Board#renderer
             * @type String
             * @default 'auto'
             */
            renderer: 'auto',

            /**
             * Time (in msec) between two animation steps. Used in
             * {@link JXG.CoordsElement#moveAlong}, {@link JXG.CoordsElement#moveTo} and
             * {@link JXG.CoordsElement#visit}.
             *
             * @name JXG.Board#animationDelay
             * @type Number
             * @default 35
             * @see JXG.CoordsElement#moveAlong
             * @see JXG.CoordsElement#moveTo
             * @see JXG.CoordsElement#visit
             */
            animationDelay: 35,

            /**
             * Maximum frame rate of the board, i.e. maximum number of updates per second
             * triggered by move events.
             *
             * @name JXG.Board#maxFrameRate
             * @type Number
             * @default 40
             */
            maxFrameRate: 40,

            /**
             * Allow user interaction by registering mouse, pointer and touch events.
             *
             * @name JXG.Board#registerEvents
             * @type Boolean
             * @default true
             */
            registerEvents: true,

            /**
             * Change redraw strategy in SVG rendering engine.
             * <p>
             * This optimization seems to be <b>obsolete</b> in newer browsers (from 2021 on, at least)
             * and even slow down the constructions. Therefore, the default is set to 'none' since v1.2.4.
             * <p>
             * If set to 'svg', before every redrawing of the JSXGraph construction
             * the SVG sub-tree of the DOM tree is taken out of the DOM.
             *
             * If set to 'all', before every redrawing of the JSXGraph construction the
             * complete DOM tree is taken out of the DOM.
             * If set to 'none' the redrawing is done in-place.
             *
             * Using 'svg' or 'all' speeds up the update process considerably. The risk
             * is that if there is an exception, only a white div or window is left.
             *
             *
             * @name JXG.Board#minimizeReflow
             * @type String
             * @default 'none'
             */
            minimizeReflow: 'none',

            /**
             * A number that will be added to the absolute position of the board used in mouse coordinate
             * calculations in {@link JXG.Board#getCoordsTopLeftCorner}.
             *
             * @name JXG.Board#offsetX
             * @see JXG.Board#offsetY
             * @type Number
             * @default 0
             */
            offsetX: 0,

            /**
             * A number that will be added to the absolute position of the board used in mouse coordinate
             * calculations in {@link JXG.Board#getCoordsTopLeftCorner}.
             *
             * @name JXG.Board#offsetY
             * @see JXG.Board#offsetX
             * @type Number
             * @default 0
             */
            offsetY: 0,

            /**
             * Control the possibilities for zoom interaction.
             *
             * Possible sub-attributes with default values are:
             * <pre>
             * zoom: {
             *   factorX: 1.25,  // horizontal zoom factor (multiplied to {@link JXG.Board#zoomX})
             *   factorY: 1.25,  // vertical zoom factor (multiplied to {@link JXG.Board#zoomY})
             *   wheel: true,     // allow zooming by mouse wheel or
             *   				   // by pinch-to-toom gesture on touch devices
             *   needShift: true,   // mouse wheel zooming needs pressing of the shift key
             *   min: 0.001,        // minimal values of {@link JXG.Board#zoomX} and {@link JXG.Board#zoomY}, limits zoomOut
             *   max: 1000.0,       // maximal values of {@link JXG.Board#zoomX} and {@link JXG.Board#zoomY}, limits zoomIn
             *
             *   pinchHorizontal: true, // Allow pinch-to-zoom to zoom only horizontal axis
             *   pinchVertical: true,   // Allow pinch-to-zoom to zoom only vertical axis
             *   pinchSensitivity: 7    // Sensitivity (in degrees) for recognizing horizontal or vertical pinch-to-zoom gestures.
             * }
             * </pre>
             *
             * Deprecated: zoom.eps which is superseded by zoom.min
             *
             * @name JXG.Board#zoom
             * @type Object
             * @default
             */
            zoom: {
                enabled: true,
                factorX: 1.25,
                factorY: 1.25,
                wheel: true,
                needShift: true,
                min: 0.0001,
                max: 10000.0,
                pinchHorizontal: true,
                pinchVertical: true,
                pinchSensitivity: 7
            },

            /**
             * Control the possibilities for panning interaction (i.e. moving the origin).
             *
             * Possible sub-attributes with default values are:
             * <pre>
             * pan: {
             *   enabled: true   // Allow panning
             *   needTwoFingers: false, // panning is done with two fingers on touch devices
             *   needShift: true, // mouse panning needs pressing of the shift key
             * }
             * </pre>
             *
             * @name JXG.Board#pan
             * @type Object
             */
            pan: {
                needShift: true,
                needTwoFingers: false,
                enabled: true
            },

            /**
             * Control the possibilities for dragging objects.
             *
             * Possible sub-attributes with default values are:
             * <pre>
             * drag: {
             *   enabled: true   // Allow dragging
             * }
             * </pre>
             *
             * @name JXG.Board#drag
             * @type Object
             * @default {enabled: true}
             */
            drag: {
                enabled: true
            },

            /**
             * Control using the keyboard to change the construction.
             * <ul>
             * <li> enabled: true / false
             * <li> dx: horizontal shift amount per key press
             * <li> dy: vertical shift amount per key press
             * <li> panShift: zoom if shift key is pressed
             * <li> panCtrl: zoom if ctrl key is pressed
             * </ul>
             *
             * @example
             * var board = JXG.JSXGraph.initBoard("jxgbox", {boundingbox: [-5,5,5,-5],
             *     axis: true,
             *     showCopyright:true,
             *     showNavigation:true,
             *     keyboard: {
             *         enabled: true,
             *         dy: 30,
             *         panShift: true,
             *         panCtrl: false
             *     }
             * });
             *
             * </pre><div id="JXGb1d3aab6-ced2-4fe9-8fa5-b0accc8c7266" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXGb1d3aab6-ced2-4fe9-8fa5-b0accc8c7266',
             *             {boundingbox: [-5,5,5,-5],
             *         axis: true,
             *         showCopyright:true,
             *         showNavigation:true,
             *         keyboard: {
             *             enabled: true,
             *             dy: 30,
             *             panShift: true,
             *             panCtrl: false
             *         }
             *     });
             *
             *     })();
             *
             * </script><pre>
             *
             *
             * @see JXG.Board#keyDownListener
             * @see JXG.Board#keyFocusInListener
             * @see JXG.Board#keyFocusOutListener
             *
             * @name JXG.Board#keyboard
             * @type Object
             * @default {enabled: true, dx: 10, dy:10, panShift: true, panCtrl: false}
             */
            keyboard: {
                enabled: true,
                dx: 10,
                dy: 10,
                panShift: true,
                panCtrl: false
            },

            /**
             * Control if JSXGraph reacts to resizing of the JSXGraph container element
             * by the user / browser.
             * The attribute "throttle" determines the minimal time in msec between to
             * resize calls.
             *
             * @see JXG.Board#startResizeObserver
             * @see JXG.Board#resizeListener
             *
             * @name JXG.Board#resize
             * @type Object
             * @default {enabled: true, throttle: 10}
             *
             * @example
             *     var board = JXG.JSXGraph.initBoard('jxgbox', {
             *         boundingbox: [-5,5,5,-5],
             *         keepAspectRatio: true,
             *         axis: true,
             *         resize: {enabled: true, throttle: 200}
             *     });
             *
             * </pre><div id="JXGb55d4608-5d71-4bc3-b332-18c15fbda8c3" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXGb55d4608-5d71-4bc3-b332-18c15fbda8c3', {
             *             boundingbox: [-5,5,5,-5],
             *             keepAspectRatio: true,
             *             axis: true,
             *             resize: {enabled: true, throttle: 200}
             *         });
             *
             *     })();
             *
             * </script><pre>
             *
             *
             */
            resize: {
                enabled: true,
                throttle: 10
            },

            /**
             * Element which listens to move events of the pointing device.
             * This allows to drag elements of a JSXGraph construction outside of the board.
             * Especially, on mobile devices this enhances the user experience.
             * However, it is recommended to allow dragging outside of the JSXGraph board only
             * in certain constructions where users may not "loose" points outside of the board.
             * Then points may become unreachable.
             * <p>
             * A situation where dragging outside of the board is uncritical is for example if
             * only sliders are used to interact with the construction.
             * <p>
             * Possible values for this attributes are:
             * <ul>
             * <li> an element specified by document.getElementById('some id');
             * <li> null: to use the JSXgraph container div element
             * <li> document
             * </ul>
             *
             * @name JXG.Board#moveTarget
             * @type HTML node or document
             * @default null
             *
             * @example
             *     var board = JXG.JSXGraph.initBoard('jxgbox', {
             *         boundingbox: [-5,5,5,-5],
             *         axis: true,
             *         moveTarget: document
             *     });
             *
             * </pre><div id="JXG973457e5-c63f-4516-8570-743f2cc560e1" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG973457e5-c63f-4516-8570-743f2cc560e1',
             *             {boundingbox: [-5,5,5,-5],
             *             axis: true,
             *             moveTarget: document
             *         });
             *
             *     })();
             *
             * </script><pre>
             *
             *
             */
            moveTarget: null,

            /**
             * Control the possibilities for a selection rectangle.
             * Starting a selection event triggers the "startselecting" event.
             * When the mouse pointer is released, the "stopselecting" event is fired.
             * The "stopselecting" event must be supplied by the user.
             * <p>
             * Possible sub-attributes with default values are:
             * <pre>
             * selection: {
             *   enabled: false,
             *   name: 'selectionPolygon',
             *   needShift: false,  // mouse selection needs pressing of the shift key
             *   needCtrl: true,    // mouse selection needs pressing of the shift key
             *   withLines: false,  // Selection polygon has border lines
             *   vertices: {
             *       visible: false
             *   },
             *   fillColor: '#ffff00',
             *   visible: false      // Initial visibility. Should be set to false always
             * }
             * </pre>
             *
             * @example
             * board.on('stopselecting', function(){
             *     var box = board.stopSelectionMode(),
             *     // bbox has the coordinates of the selectionr rectangle.
             *     // Attention: box[i].usrCoords have the form [1, x, y], i.e.
             *     // are homogeneous coordinates.
             *     bbox = box[0].usrCoords.slice(1).concat(box[1].usrCoords.slice(1));
             *     // Set a new bounding box
             *     board.setBoundingBox(bbox, false);
             * });
             *
             * @name JXG.Board#selection
             * @see JXG.Board#startselecting
             * @see JXG.Board#stopselecting
             * @see JXG.Board#mousestartselecting
             * @see JXG.Board#pointerstartselecting
             * @see JXG.Board#touchstartselecting
             * @see JXG.Board#mousestopselecting
             * @see JXG.Board#pointerstopselecting
             * @see JXG.Board#touchstopselecting
             * @type Object
             * @default
             */
            selection: {
                enabled: false,
                name: 'selectionPolygon',
                needShift: false,
                needCtrl: true,
                withLines: false,
                vertices: {
                    visible: false
                },
                fillColor: '#ffff00',
                visible: false
            },

            /**
             * If true, the infobox is shown on mouse/pen over for all points
             * which have set their attribute showInfobox to 'inherit'.
             * If a point has set its attribute showInfobox to false or true,
             * that value will have priority over this value.
             *
             * @name JXG.Board#showInfobox
             * @see Point#showInfobox
             * @type Boolean
             * @default true
             */
            showInfobox: true

            /**#@-*/
        },

        /**
         * Options that are used by the navigation bar.
         *
         * Default values are
         * <pre>
         * JXG.Option.navbar: {
         *   strokeColor: '#333333',
         *   fillColor: 'transparent',
         *   highlightFillColor: '#aaaaaa',
         *   padding: '2px',
         *   position: 'absolute',
         *   fontSize: '14px',
         *   cursor: 'pointer',
         *   zIndex: '100',
         *   right: '5px',
         *   bottom: '5px'
         * },
         * </pre>
         * These settings are overruled by the CSS class 'JXG_navigation'.
         * @deprecated
         * @type Object
         * @name JXG.Options#navbar
         *
         */
        navbar: {
            strokeColor: '#333333', //'#aaaaaa',
            fillColor: 'transparent', //#f5f5f5',
            highlightFillColor: '#aaaaaa',
            padding: '2px',
            position: 'absolute',
            fontSize: '14px',
            cursor: 'pointer',
            zIndex: '100',
            right: '5px',
            bottom: '5px'
            //border: 'none 1px black',
            //borderRadius: '4px'
        },

         /*
          *  Generic options used by {@link JXG.GeometryElement}
          */
        elements: {
            // the following tag is a meta tag: http://code.google.com/p/jsdoc-toolkit/wiki/MetaTags

            /**#@+
             * @visprop
             */

            /**
             * The stroke color of the given geometry element.
             * @type String
             * @name JXG.GeometryElement#strokeColor
             * @see JXG.GeometryElement#highlightStrokeColor
             * @see JXG.GeometryElement#strokeWidth
             * @see JXG.GeometryElement#strokeOpacity
             * @see JXG.GeometryElement#highlightStrokeOpacity
             * @default {@link JXG.Options.elements.color#strokeColor}
             */
            strokeColor: Color.palette.blue,

            /**
             * The stroke color of the given geometry element when the user moves the mouse over it.
             * @type String
             * @name JXG.GeometryElement#highlightStrokeColor
             * @see JXG.GeometryElement#strokeColor
             * @see JXG.GeometryElement#strokeWidth
             * @see JXG.GeometryElement#strokeOpacity
             * @see JXG.GeometryElement#highlightStrokeOpacity
             * @default {@link JXG.Options.elements.color#highlightStrokeColor}
             */
            highlightStrokeColor: '#c3d9ff',

            /**
             * The fill color of this geometry element.
             * @type String
             * @name JXG.GeometryElement#fillColor
             * @see JXG.GeometryElement#highlightFillColor
             * @see JXG.GeometryElement#fillOpacity
             * @see JXG.GeometryElement#highlightFillOpacity
             * @default {@link JXG.Options.elements.color#fillColor}
             */
            fillColor: Color.palette.red,

            /**
             * The fill color of the given geometry element when the mouse is pointed over it.
             * @type String
             * @name JXG.GeometryElement#highlightFillColor
             * @see JXG.GeometryElement#fillColor
             * @see JXG.GeometryElement#fillOpacity
             * @see JXG.GeometryElement#highlightFillOpacity
             * @default {@link JXG.Options.elements.color#highlightFillColor}
             */
            highlightFillColor: 'none',

            /**
             * Opacity for element's stroke color.
             * @type Number
             * @name JXG.GeometryElement#strokeOpacity
             * @see JXG.GeometryElement#strokeColor
             * @see JXG.GeometryElement#highlightStrokeColor
             * @see JXG.GeometryElement#strokeWidth
             * @see JXG.GeometryElement#highlightStrokeOpacity
             * @default {@link JXG.Options.elements#strokeOpacity}
             */
            strokeOpacity: 1,

            /**
             * Opacity for stroke color when the object is highlighted.
             * @type Number
             * @name JXG.GeometryElement#highlightStrokeOpacity
             * @see JXG.GeometryElement#strokeColor
             * @see JXG.GeometryElement#highlightStrokeColor
             * @see JXG.GeometryElement#strokeWidth
             * @see JXG.GeometryElement#strokeOpacity
             * @default {@link JXG.Options.elements#highlightStrokeOpacity}
             */
            highlightStrokeOpacity: 1,

            /**
             * Opacity for fill color.
             * @type Number
             * @name JXG.GeometryElement#fillOpacity
             * @see JXG.GeometryElement#fillColor
             * @see JXG.GeometryElement#highlightFillColor
             * @see JXG.GeometryElement#highlightFillOpacity
             * @default {@link JXG.Options.elements.color#fillOpacity}
             */
            fillOpacity: 1,

            /**
             * Opacity for fill color when the object is highlighted.
             * @type Number
             * @name JXG.GeometryElement#highlightFillOpacity
             * @see JXG.GeometryElement#fillColor
             * @see JXG.GeometryElement#highlightFillColor
             * @see JXG.GeometryElement#fillOpacity
             * @default {@link JXG.Options.elements.color#highlightFillOpacity}
             */
            highlightFillOpacity: 1,

            /**
             * Gradient type. Possible values are 'linear'. 'radial' or null.
             *
             * @example
             *     var a = board.create('slider', [[0, -0.2], [3.5, -0.2], [0, 0, 2 * Math.PI]], {name: 'angle'});
             *     var b = board.create('slider', [[0, -0.4], [3.5, -0.4], [0, 0, 1]], {name: 'offset1'});
             *     var c = board.create('slider', [[0, -0.6], [3.5, -0.6], [0, 1, 1]], {name: 'offset2'});
             *
             *     var pol = board.create('polygon', [[0, 0], [4, 0], [4,4], [0,4]], {
             *                 fillOpacity: 1,
             *                 fillColor: 'yellow',
             *                 gradient: 'linear',
             *                 gradientSecondColor: 'blue',
             *                 gradientAngle: function() { return a.Value(); },
             *                 gradientStartOffset: function() { return b.Value(); },
             *                 gradientEndOffset: function() { return c.Value(); },
             *                 hasInnerPoints: true
             *         });
             *
             * </pre><div id="JXG3d04b5fd-0cd4-4f49-8c05-4e9686cd7ff0" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG3d04b5fd-0cd4-4f49-8c05-4e9686cd7ff0',
             *             {boundingbox: [-1.5, 4.5, 5, -1.5], axis: true, showcopyright: false, shownavigation: false});
             *         var a = board.create('slider', [[0, -0.2], [3.5, -0.2], [0, 0, 2 * Math.PI]], {name: 'angle'});
             *         var b = board.create('slider', [[0, -0.4], [3.5, -0.4], [0, 0, 1]], {name: 'offset1'});
             *         var c = board.create('slider', [[0, -0.6], [3.5, -0.6], [0, 1, 1]], {name: 'offset2'});
             *
             *         var pol = board.create('polygon', [[0, 0], [4, 0], [4,4], [0,4]], {
             *                     fillOpacity: 1,
             *                     fillColor: 'yellow',
             *                     gradient: 'linear',
             *                     gradientSecondColor: 'blue',
             *                     gradientAngle: function() { return a.Value(); },
             *                     gradientStartOffset: function() { return b.Value(); },
             *                     gradientEndOffset: function() { return c.Value(); },
             *                     hasInnerPoints: true
             *             });
             *
             *     })();
             *
             * </script><pre>
             *
             * @example
             *     var cx = board.create('slider', [[0, -.2], [3.5, -.2], [0, 0.5, 1]], {name: 'cx, cy'});
             *     var fx = board.create('slider', [[0, -.4], [3.5, -.4], [0, 0.5, 1]], {name: 'fx, fy'});
             *     var o1 = board.create('slider', [[0, -.6], [3.5, -.6], [0, 0.0, 1]], {name: 'offset1'});
             *     var o2 = board.create('slider', [[0, -.8], [3.5, -.8], [0, 1, 1]], {name: 'offset2'});
             *     var r = board.create('slider', [[0, -1], [3.5, -1], [0, 0.5, 1]], {name: 'r'});
             *     var fr = board.create('slider', [[0, -1.2], [3.5, -1.2], [0, 0, 1]], {name: 'fr'});
             *
             *     var pol = board.create('polygon', [[0, 0], [4, 0], [4,4], [0,4]], {
             *                 fillOpacity: 1,
             *                 fillColor: 'yellow',
             *                 gradient: 'radial',
             *                 gradientSecondColor: 'blue',
             *                 gradientCX: function() { return cx.Value(); },
             *                 gradientCY: function() { return cx.Value(); },
             *                 gradientR: function() { return r.Value(); },
             *                 gradientFX: function() { return fx.Value(); },
             *                 gradientFY: function() { return fx.Value(); },
             *                 gradientFR: function() { return fr.Value(); },
             *                 gradientStartOffset: function() { return o1.Value(); },
             *                 gradientEndOffset: function() { return o2.Value(); },
             *                 hasInnerPoints: true
             *     });
             *
             * </pre><div id="JXG6081ca7f-0d09-4525-87ac-325a02fe2225" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG6081ca7f-0d09-4525-87ac-325a02fe2225',
             *             {boundingbox: [-1.5, 4.5, 5, -1.5], axis: true, showcopyright: false, shownavigation: false});
             *         var cx = board.create('slider', [[0, -.2], [3.5, -.2], [0, 0.5, 1]], {name: 'cx, cy'});
             *         var fx = board.create('slider', [[0, -.4], [3.5, -.4], [0, 0.5, 1]], {name: 'fx, fy'});
             *         var o1 = board.create('slider', [[0, -.6], [3.5, -.6], [0, 0.0, 1]], {name: 'offset1'});
             *         var o2 = board.create('slider', [[0, -.8], [3.5, -.8], [0, 1, 1]], {name: 'offset2'});
             *         var r = board.create('slider', [[0, -1], [3.5, -1], [0, 0.5, 1]], {name: 'r'});
             *         var fr = board.create('slider', [[0, -1.2], [3.5, -1.2], [0, 0, 1]], {name: 'fr'});
             *
             *         var pol = board.create('polygon', [[0, 0], [4, 0], [4,4], [0,4]], {
             *                     fillOpacity: 1,
             *                     fillColor: 'yellow',
             *                     gradient: 'radial',
             *                     gradientSecondColor: 'blue',
             *                     gradientCX: function() { return cx.Value(); },
             *                     gradientCY: function() { return cx.Value(); },
             *                     gradientR: function() { return r.Value(); },
             *                     gradientFX: function() { return fx.Value(); },
             *                     gradientFY: function() { return fx.Value(); },
             *                     gradientFR: function() { return fr.Value(); },
             *                     gradientStartOffset: function() { return o1.Value(); },
             *                     gradientEndOffset: function() { return o2.Value(); },
             *                     hasInnerPoints: true
             *         });
             *
             *     })();
             *
             * </script><pre>
             *
             *
             * @type String
             * @name JXG.GeometryElement#gradient
             * @see JXG.GeometryElement#gradientSecondColor
             * @see JXG.GeometryElement#gradientSecondOpacity
             * @default null
             */
            gradient: null,

            /**
             * Second color for gradient.
             * @type String
             * @name JXG.GeometryElement#gradientSecondColor
             * @see JXG.GeometryElement#gradient
             * @see JXG.GeometryElement#gradientSecondOpacity
             * @default '#ffffff'
             */
            gradientSecondColor: '#ffffff',

            /**
             * Opacity of second gradient color. Takes a value between 0 and 1.
             * @type Number
             * @name JXG.GeometryElement#gradientSecondOpacity
             * @see JXG.GeometryElement#gradient
             * @see JXG.GeometryElement#gradientSecondColor
             * @default 1
             */
            gradientSecondOpacity: 1,

            /**
             * The gradientStartOffset attribute is a number (ranging from 0 to 1) which indicates where the first gradient stop is placed,
             * see the SVG specification for more information.
             * For linear gradients, this attribute represents a location along the gradient vector.
             * For radial gradients, it represents a percentage distance from (fx,fy) to the edge of the outermost/largest circle.
             * @type Number
             * @name JXG.GeometryElement#gradientStartOffset
             * @see JXG.GeometryElement#gradient
             * @see JXG.GeometryElement#gradientEndOffset
             * @default 0.0
             */
            gradientStartOffset: 0.0,

            /**
             * The gradientEndOffset attribute is a number (ranging from 0 to 1) which indicates where the second gradient stop is placed,
             * see the SVG specification for more information.
             * For linear gradients, this attribute represents a location along the gradient vector.
             * For radial gradients, it represents a percentage distance from (fx,fy) to the edge of the outermost/largest circle.
             * @type Number
             * @name JXG.GeometryElement#gradientEndOffset
             * @see JXG.GeometryElement#gradient
             * @see JXG.GeometryElement#gradientStartOffset
             * @default 1.0
             */
            gradientEndOffset: 1.0,


            /**
             * Angle (in radians) of the gradiant in case the gradient is of type 'linear'.
             * If the angle is 0, the first color is on the left and the second color is on the right.
             * If the angle is pi/4 the first color is on top and the second color at the
             * bottom.
             * @type Number
             * @name JXG.GeometryElement#gradientAngle
             * @see JXG.GeometryElement#gradient
             * @default 0
             */
            gradientAngle: 0,

            /**
             * From the SVG specification: ‘cx’, ‘cy’ and ‘r’ define the largest (i.e., outermost) circle for the radial gradient.
             * The gradient will be drawn such that the 100% gradient stop is mapped to the perimeter of this largest (i.e., outermost) circle.
             * For radial gradients in canvas this is the value 'x1'.
             * Takes a value between 0 and 1.
             * @type Number
             * @name JXG.GeometryElement#gradientCX
             * @see JXG.GeometryElement#gradient
             * @see JXG.GeometryElement#gradientCY
             * @see JXG.GeometryElement#gradientR
             * @default 0.5
             */
            gradientCX: 0.5,

            /**
             * From the SVG specification: ‘cx’, ‘cy’ and ‘r’ define the largest (i.e., outermost) circle for the radial gradient.
             * The gradient will be drawn such that the 100% gradient stop is mapped to the perimeter of this largest (i.e., outermost) circle.
             * For radial gradients in canvas this is the value 'y1'.
             * Takes a value between 0 and 1.
             * @type Number
             * @name JXG.GeometryElement#gradientCY
             * @see JXG.GeometryElement#gradient
             * @see JXG.GeometryElement#gradientCX
             * @see JXG.GeometryElement#gradientR
             * @default 0.5
             */
            gradientCY: 0.5,

            /**
             * From the SVG specification: ‘cx’, ‘cy’ and ‘r’ define the largest (i.e., outermost) circle for the radial gradient.
             * The gradient will be drawn such that the 100% gradient stop is mapped to the perimeter of this largest (i.e., outermost) circle.
             * For radial gradients in canvas this is the value 'r1'.
             * Takes a value between 0 and 1.
             * @type Number
             * @name JXG.GeometryElement#gradientR
             * @see JXG.GeometryElement#gradient
             * @see JXG.GeometryElement#gradientCX
             * @see JXG.GeometryElement#gradientCY
             * @default 0.5
             */
            gradientR: 0.5,

            /**
             * ‘fx’ and ‘fy’ define the focal point for the radial gradient.
             * The gradient will be drawn such that the 0% gradient stop is mapped to (fx, fy).
             * For radial gradients in canvas this is the value 'x0'.
             * Takes a value between 0 and 1.
             * @type Number
             * @name JXG.GeometryElement#gradientFX
             * @see JXG.GeometryElement#gradient
             * @see JXG.GeometryElement#gradientFY
             * @see JXG.GeometryElement#gradientFR
             * @default 0.5
             */
            gradientFX: 0.5,

            /**
             * y-coordinate of the circle center for the second color in case of gradient 'radial'. (The attribute fy in SVG)
             * For radial gradients in canvas this is the value 'y0'.
             * Takes a value between 0 and 1.
             * @type Number
             * @name JXG.GeometryElement#gradientFY
             * @see JXG.GeometryElement#gradient
             * @see JXG.GeometryElement#gradientFX
             * @see JXG.GeometryElement#gradientFR
             * @default 0.5
             */
            gradientFY: 0.5,

            /**
             * This attribute defines the radius of the start circle of the radial gradient.
             * The gradient will be drawn such that the 0% &lt;stop&gt; is mapped to the perimeter of the start circle.
             * For radial gradients in canvas this is the value 'r0'.
             * Takes a value between 0 and 1.
             * @type Number
             * @name JXG.GeometryElement#gradientFR
             * @see JXG.GeometryElement#gradient
             * @see JXG.GeometryElement#gradientFX
             * @see JXG.GeometryElement#gradientFY
             * @default 0.0
             */
            gradientFR: 0.0,

            /**
             * Transition duration (in milliseconds) for color and opacity
             * changes. Works in SVG renderer, only.
             * @type Number
             * @name JXG.GeometryElement#transitionDuration
             * @see JXG.GeometryElement#strokeColor
             * @see JXG.GeometryElement#highlightStrokeColor
             * @see JXG.GeometryElement#strokeOpacity
             * @see JXG.GeometryElement#highlightStrokeOpacity
             * @see JXG.GeometryElement#fillColor
             * @see JXG.GeometryElement#highlightFillColor
             * @see JXG.GeometryElement#fillOpacity
             * @see JXG.GeometryElement#highlightFillOpacity
             * @default {@link JXG.Options.elements#transitionDuration}
             */
            transitionDuration: 100,

            /**
             * Width of the element's stroke.
             * @type Number
             * @name JXG.GeometryElement#strokeWidth
             * @see JXG.GeometryElement#strokeColor
             * @see JXG.GeometryElement#highlightStrokeColor
             * @see JXG.GeometryElement#strokeOpacity
             * @see JXG.GeometryElement#highlightStrokeOpacity
             * @default {@link JXG.Options.elements#strokeWidth}
             */
            strokeWidth: 2,

            /**
             * Width of the element's stroke when the mouse is pointed over it.
             * @type Number
             * @name JXG.GeometryElement#highlightStrokeWidth
             * @see JXG.GeometryElement#strokeColor
             * @see JXG.GeometryElement#highlightStrokeColor
             * @see JXG.GeometryElement#strokeOpacity
             * @see JXG.GeometryElement#highlightStrokeOpacity
             * @see JXG.GeometryElement#highlightFillColor
             * @default {@link JXG.Options.elements#strokeWidth}
             */
            highlightStrokeWidth: 2,

            /**
             * If true the element is fixed and can not be dragged around. The element
             * will be repositioned on zoom and moveOrigin events.
             * @type Boolean
             * @default false
             * @name JXG.GeometryElement#fixed
             */
            fixed: false,

            /**
             * If true the element is fixed and can not be dragged around. The element
             * will even stay at its position on zoom and moveOrigin events.
             * Only free elements like points, texts, curves can be frozen.
             * @type Boolean
             * @default false
             * @name JXG.GeometryElement#frozen
             */
            frozen: false,

            /**
             * If true a label will display the element's name.
             * @type Boolean
             * @default false
             * @name JXG.GeometryElement#withLabel
             */
            withLabel: false,

            /**
             * If false the element won't be visible on the board, otherwise it is shown.
             * @type Boolean
             * @name JXG.GeometryElement#visible
             * @see JXG.GeometryElement#hideElement
             * @see JXG.GeometryElement#showElement
             * @default true
             */
            visible: true,

            /**
             * A private element will be inaccessible in certain environments, e.g. a graphical user interface.
             * @default false
             */
            priv: false,

            /**
             * Display layer which will contain the element.
             * @see JXG.Options#layer
             * @default See {@link JXG.Options#layer}
             */
            layer: 0,

            /**
             * Determines the elements border-style.
             * Possible values are:
             * <ul><li>0 for a solid line</li>
             * <li>1 for a dotted line</li>
             * <li>2 for a line with small dashes</li>


             * <li>3 for a line with medium dashes</li>
             * <li>4 for a line with big dashes</li>
             * <li>5 for a line with alternating medium and big dashes and large gaps</li>
             * <li>6 for a line with alternating medium and big dashes and small gaps</li></ul>
             * @type Number
             * @name JXG.GeometryElement#dash
             * @default 0
             */
            dash: 0,

            /**
             * If true the element will get a shadow.
             * @type Boolean
             * @name JXG.GeometryElement#shadow
             * @default false
             */
            shadow: false,

            /**
             * If true the element will be traced, i.e. on every movement the element will be copied
             * to the background. Use {@link JXG.GeometryElement#clearTrace} to delete the trace elements.
             *
             * The calling of element.setAttribute({trace:false}) additionally
             * deletes all traces of this element. By calling
             * element.setAttribute({trace:'pause'})
             * the removal of already existing traces can be prevented.
             * @see JXG.GeometryElement#clearTrace
             * @see JXG.GeometryElement#traces
             * @see JXG.GeometryElement#numTraces
             * @type Boolean|String
             * @default false
             * @name JXG.GeometryElement#trace
             */
            trace: false,

            /**
             * Extra visual properties for traces of an element
             * @type Object
             * @see JXG.GeometryElement#trace
             * @name JXG.GeometryElement#traceAttributes
             */
            traceAttributes: {},

            /**
             *
             * @type Boolean
             * @default true
             * @name JXG.GeometryElement#highlight
             */
            highlight: true,

            /**
             * If this is set to true, the element is updated in every update
             * call of the board. If set to false, the element is updated only after
             * zoom events or more generally, when the bounding box has been changed.
             * Examples for the latter behaviour should be axes.
             * @type Boolean
             * @default true
             * @see JXG.GeometryElement#needsRegularUpdate
             * @name JXG.GeometryElement#needsRegularUpdate
             */
            needsRegularUpdate: true,

            /**
             * Snaps the element or its parents to the grid. Currently only relevant for points, circles,
             * and lines. Points are snapped to grid directly, on circles and lines it's only the parent
             * points that are snapped
             * @type Boolean
             * @default false
             * @name JXG.GeometryElement#snapToGrid
             */
            snapToGrid: false,

            /**
             * Determines whether two-finger manipulation of this object may change its size.
             * If set to false, the object is only rotated and translated.
             * <p>
             * In case the element is a horizontal or vertical line having ticks, "scalable:true"
             * enables zooming of the board by dragging ticks lines. This feature is enabled,
             * for the ticks element of the line element the attribute "fixed" has to be false
             * and the line element's scalable attribute has to be true.
             * <p>
             * In case the element is a polygon or line and it has the attribute "scalable:false",
             * moving the element with two fingers results in a rotation or translation.
             *
             * @type Boolean
             * @default true
             * @name JXG.GeometryElement#scalable
             * @see JXG.Ticks#fixed
             */
            scalable: true,

            /**
             * If the element is dragged it will be moved on mousedown or touchstart to the
             * top of its layer. Works only for SVG renderer and for simple elements
             * consisting of one SVG node.
             * @example
             * var li1 = board.create('line', [1, 1, 1], {strokeWidth: 20, dragToTopOfLayer: true});
             * var li2 = board.create('line', [1, -1, 1], {strokeWidth: 20, strokeColor: 'red'});
             *
             * </pre><div id="JXG38449fee-1ab4-44de-b7d1-43caa1f50f86" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG38449fee-1ab4-44de-b7d1-43caa1f50f86',
             *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
             *     var li1 = board.create('line', [1, 1, 1], {strokeWidth: 20, dragToTopOfLayer: true});
             *     var li2 = board.create('line', [1, -1, 1], {strokeWidth: 20, strokeColor: 'red'});
             *
             *     })();
             *
             * </script><pre>
             *
             * @type Boolean
             * @default false
             * @name JXG.GeometryElement#dragToTopOfLayer
             */
            dragToTopOfLayer: false,

            /**
             * Precision options for JSXGraph elements.
             * This attributes takes either the value 'inherit' or an object of the form:
             * <pre>
             * precision: {
             *      touch: 30,
             *      mouse: 4,
             *      pen: 4
             * }
             * </pre>
             *
             * In the first case, the global, JSXGraph-wide values of JXGraph.Options.precision
             * are taken.
             *
             * @type {String|Object}
             * @name JXG.GeometryElement#precision
             * @see JXG.Options#precision
             * @default 'inherit'
             */
            precision: 'inherit',

            /*draft options */
            draft: {
                /**
                 * If true the element will be drawn in grey scale colors to visualize that it's only a draft.
                 * @type Boolean
                 * @name JXG.GeometryElement#draft
                 * @default {@link JXG.Options.elements.draft#draft}
                 */
                draft: false,
                strokeColor: '#565656',
                fillColor: '#565656',
                strokeOpacity: 0.8,
                fillOpacity: 0.8,
                strokeWidth: 1
            },

            /**
             * @private
             * By default, an element is not a label. Do not change this.
             */
            isLabel: false,

            /**
             * Controls if an element can get the focus with the tab key.
             * See <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex">descriptiona at MDN</a>.
             * The additional value null completely disables focus of an element.
             * The value will be ignored if keyboard control of the board is not enabled or
             * the element is fixed or not visible.
             *
             * @name JXG.GeometryElement#tabindex
             * @type Number
             * @default 0
             * @see JXG.Board#keyboard
             * @see JXG.GeometryElement#fixed
             * @see JXG.GeometryElement#visible
             */
            tabindex: 0

            // close the meta tag
            /**#@-*/
        },

         /*
          *  Generic options used by {@link JXG.Ticks}
          */
        ticks: {
            /**#@+
             * @visprop
             */

            /**
             * A function that expects two {@link JXG.Coords}, the first one representing the coordinates of the
             * tick that is to be labeled, the second one the coordinates of the center (the tick with position 0).
             *
             * @type function
             * @name Ticks#generateLabelText
             */
            generateLabelText: null,

            /**
             * A function that expects two {@link JXG.Coords}, the first one representing the coordinates of the
             * tick that is to be labeled, the second one the coordinates of the center (the tick with position 0).
             *
             * @deprecated Use {@link JGX.Options@generateLabelValue}
             * @type function
             * @name Ticks#generateLabelValue
             */
            generateLabelValue: null,

            /**
             * Draw labels yes/no
             *
             * @type Boolean
             * @name Ticks#drawLabels
             * @default false
             */
            drawLabels: false,

            /**
             * Attributes for the ticks labels
             *
             * @name Ticks#label
             * @type Object
             * @default {}
             *
             */
            label: {
            },

            /**
            * Format tick labels that were going to have scientific notation
            * like 5.00e+6 to look like 5•10⁶.
            *
            * @example
            * var board = JXG.JSXGraph.initBoard("jxgbox", {
            *     boundingbox: [-500000, 500000, 500000, -500000],
            *     axis: true,
            *     defaultAxes: {
            *         x: {
            *             scalable: true,
            *             ticks: {
            *                 beautifulScientificTickLabels: true
            *           },
            *         },
            *         y: {
            *             scalable: true,
            *             ticks: {
            *                 beautifulScientificTickLabels: true
            *           },
            *         }
            *     },
            * });
            *
            * </pre><div id="JXGc1e46cd1-e025-4002-80aa-b450869fdaa2" class="jxgbox" style="width: 300px; height: 300px;"></div>
            * <script type="text/javascript">
            *     (function() {
            *     var board = JXG.JSXGraph.initBoard('JXGc1e46cd1-e025-4002-80aa-b450869fdaa2', {
            *         boundingbox: [-500000, 500000, 500000, -500000],
            *         showcopyright: false, shownavigation: false,
            *         axis: true,
            *         defaultAxes: {
            *             x: {
            *                 scalable: true,
            *                 ticks: {
            *                     beautifulScientificTickLabels: true
            *               },
            *             },
            *             y: {
            *                 scalable: true,
            *                 ticks: {
            *                     beautifulScientificTickLabels: true
            *               },
            *             }
            *         },
            *     });
            *
            *     })();
            *
            * </script><pre>
            *
            * @name Ticks#beautifulScientificTickLabels
            * @type Boolean
            * @default false
            */
            beautifulScientificTickLabels: false,

            /**
             * Use the unicode character 0x2212, i.e. the HTML entity &amp;minus; as minus sign.
             * That is &minus;1 instead of -1.
             *
             * @type Boolean
             * @name Ticks#useUnicodeMinus
             * @default true
             */
            useUnicodeMinus: true,

            /**
             * Determine the position of the tick with value 0. 'left' means point1 of the line, 'right' means point2,
             * and 'middle' is equivalent to the midpoint of the defining points. This attribute is ignored if the parent
             * line is of type axis.
             *
             * @type String
             * @name Ticks#anchor
             * @default 'left'
             */
            anchor: 'left',

            /**
             * Draw the zero tick, that lies at line.point1?
             *
             * @type Boolean
             * @name Ticks#drawZero
             * @default false
             */
            drawZero: false,

            /**
             * If the distance between two ticks is too big we could insert new ticks. If insertTicks
             * is <tt>true</tt>, we'll do so, otherwise we leave the distance as is.
             * This option is ignored if equidistant is false. In the example below the distance between
             * two ticks is given as <tt>1</tt> but because insertTicks is set to true many ticks will
             * be omitted in the rendering process to keep the display clear.
             *
             * @type Boolean
             * @name Ticks#insertTicks
             * @see Ticks#minTicksDistance
             * @default false
             * @example
             * // Create an axis providing two coord pairs.
             *   var p1 = board.create('point', [0, 0]);
             *   var p2 = board.create('point', [50, 25]);
             *   var l1 = board.create('line', [p1, p2]);
             *   var t = board.create('ticks', [l1, 1], {
             *      insertTicks: true,
             *      majorHeight: -1,
             *      label: {
             *          offset: [4, -9]
             *      },
             *      drawLabels: true
             *  });
             * </pre><div class="jxgbox" id="JXG2f6fb842-40bd-4223-aa28-3e9369d2097f" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             * (function () {
             *   var board = JXG.JSXGraph.initBoard('JXG2f6fb842-40bd-4223-aa28-3e9369d2097f', {boundingbox: [-100, 70, 70, -100], showcopyright: false, shownavigation: false});
             *   var p1 = board.create('point', [0, 0]);
             *   var p2 = board.create('point', [50, 25]);
             *   var l1 = board.create('line', [p1, p2]);
             *   var t = board.create('ticks', [l1, 1], {insertTicks: true, majorHeight: -1, label: {offset: [4, -9]}, drawLabels: true});
             * })();
             * </script><pre>
             */
            insertTicks: false,

            /**
             * Minimum distance in pixel of equidistant ticks in case insertTicks==true.
             * @name Ticks#minTicksDistance
             * @type: Number
             * @default: 10
             * @see Ticks#insertTicks
             */
            minTicksDistance: 10,

            /**
             * Total height of a minor tick. If negative the full height of the board is taken.
             *
             * @type Number
             * @name Ticks#minorHeight
             * @default 4
             */
            minorHeight: 4,

            /**
             * Total height of a major tick. If negative the full height of the board is taken.
             *
             * @type Number
             * @name Ticks#majorHeight
             * @default 10
             */
            majorHeight: 10,

            /**
             * Decides in which direction finite ticks are visible. Possible values are either the constants
             * 0=false or 1=true or a function returning 0 or 1.
             *
             * In case of [0,1] the tick is only visible to the right of the line. In case of
             * [1,0] the tick is only visible to the left of the line.
             *
             * @type Array
             * @name Ticks#tickEndings
             * @default [1, 1]
             */
            tickEndings: [1, 1],

            /**
             * The number of minor ticks between two major ticks.
             * @type Number
             * @name Ticks#minorTicks
             * @default 4
             */
            minorTicks: 4,

            /**
             * Scale the ticks but not the tick labels.
             * @type Number
             * @default 1
             * @name Ticks#scale
             * @see Ticks#scaleSymbol
             */
            scale: 1,

            /**
             * A string that is appended to every tick, used to represent the scale
             * factor given in {@link Ticks#scaleSymbol}.
             *
             * @type String
             * @default ''
             * @name Ticks#scaleSymbol
             * @see Ticks#scale
             */
            scaleSymbol: '',

            /**
             * User defined labels for special ticks. Instead of the i-th tick's position, the i-th string stored in this array
             * is shown. If the number of strings in this array is less than the number of special ticks, the tick's position is
             * shown as a fallback.
             *
             * @type Array
             * @name Ticks#labels
             * @default []
             */
            labels: [],

            /**
             * The maximum number of characters a tick label can use.
             *
             * @type Number
             * @name Ticks#maxLabelLength
             * @see Ticks#precision
             * @default 5
             */
            maxLabelLength: 5,

            /**
             * If a label exceeds {@link Ticks#maxLabelLength} this determines the precision used to shorten the tick label.
             * Replaced by the digits attribute.
             *
             * @type Number
             * @name Ticks#precision
             * @see Ticks#maxLabelLength
             * @see Ticks#digits
             * @deprecated
             * @default 3
             */
            precision: 3,

            /**
             * If a label exceeds {@link Ticks#maxLabelLength} this determines the number of digits used to shorten the tick label.
             *
             * @type Number
             * @name Ticks#digits
             * @see Ticks#maxLabelLength
             * @deprecated
             * @default 3
             */
            digits: 3,

            /**
             * The default distance between two ticks. Please be aware that this value does not have
             * to be used if {@link Ticks#insertTicks} is set to true.
             *
             * @type Number
             * @name Ticks#ticksDistance
             * @see Ticks#insertTicks
             * @default 1
             */
            ticksDistance: 1,

            /**
             * Tick face for ticks of finite length.  By default (face: '|') this is a straight line.
             * Possible other values are '<' and '>'. These faces are used in
             * {@link JXG.Hatch} for hatch marking parallel lines.
             * @type String
             * @name{Ticks#face}
             * @see hatch
             * @default '|'
             * @example
             *   var p1 = board.create('point', [0, 3]);
             *   var p2 = board.create('point', [1, 3]);
             *   var l1 = board.create('line', [p1, p2]);
             *   var t = board.create('ticks', [l1], {ticksDistance: 2, face: '>'});
             *
             * </pre><div id="JXG950a568a-1264-4e3a-b61d-b6881feecf4b" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG950a568a-1264-4e3a-b61d-b6881feecf4b',
             *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
             *       var p1 = board.create('point', [0, 3]);
             *       var p2 = board.create('point', [1, 3]);
             *       var l1 = board.create('line', [p1, p2]);
             *       var t = board.create('ticks', [l1], {ticksDistance: 2, face: '>'});
             *
             *     })();
             *
             * </script><pre>
             *
             */
            face: '|',

            strokeOpacity: 1,
            strokeWidth: 1,
            strokeColor: '#000000',
            highlightStrokeColor: '#888888',
            fillColor: 'none',
            highlightFillColor: 'none',
            visible: 'inherit',

            /**
             * Whether line boundaries should be counted or not in the lower and upper bounds when
             * creating ticks.
             *
             * @type Boolean
             * @name Ticks#includeBoundaries
             * @default false
             */
            includeBoundaries: false,

            /**
             * Set the ticks type.
             * Possible values are 'linear' or 'polar'.
             *
             * @type String
             * @name Ticks#type
             * @default 'linear'
             */
            type: 'linear'

            // close the meta tag
            /**#@-*/
        },

         /*
          *  Generic options used by {@link JXG.Hatch}
          */
        hatch: {
            drawLabels: false,
            drawZero: true,
            majorHeight: 20,
            anchor: 'middle',
            face: '|',
            strokeWidth: 2,
            strokeColor: Color.palette.blue,
            ticksDistance: 0.2
        },

        /**
         * Precision options, defining how close a pointer device (mouse, finger, pen) has to be
         * to an object such that the object is highlighted or can be dragged.
         * These values are board-wide and can be overwritten for individual elements by
         * changing their precision attribute.
         *
         * The default values are
         * <pre>
         * JXG.Options.precision: {
         *   touch: 30,
         *   touchMax: 100,
         *   mouse: 4,
         *   pen: 4,
         *   epsilon: 0.0001,
         *   hasPoint: 4
         * }
         * </pre>
         *
         * @type Object
         * @name JXG.Options#precision
         * @see JXG.GeometryElement#precision
         */
        precision: {
            touch: 30,
            touchMax: 100,
            mouse: 4,
            pen: 4,
            epsilon: 0.0001, // Unused
            hasPoint: 4
        },

        /**
         * Default ordering of the layers.
         * The numbering starts from 0 and the highest layer number is numlayers-1.
         *
         * The default values are
         * <pre>
         * JXG.Options.layer: {
         *   numlayers: 20, // only important in SVG
         *   text: 9,
         *   point: 9,
         *   glider: 9,
         *   arc: 8,
         *   line: 7,
         *   circle: 6,
         *   curve: 5,
         *   turtle: 5,
         *   polygon: 3,
         *   sector: 3,
         *   angle: 3,
         *   integral: 3,
         *   axis: 2,
         *   ticks: 2,
         *   grid: 1,
         *   image: 0,
         *   trace: 0
         * }
         * </pre>
         * @type Object
         * @name JXG.Options#layer
         */
        layer: {
            numlayers: 20, // only important in SVG
            unused9: 19,
            unused8: 18,
            unused7: 17,
            unused6: 16,
            unused5: 15,
            unused4: 14,
            unused3: 13,
            unused2: 12,
            unused1: 11,
            unused0: 10,
            text: 9,
            point: 9,
            glider: 9,
            arc: 8,
            line: 7,
            circle: 6,
            curve: 5,
            turtle: 5,
            polygon: 3,
            sector: 3,
            angle: 3,
            integral: 3,
            axis: 2,
            ticks: 2,
            grid: 1,
            image: 0,
            trace: 0
        },

        /* special angle options */
        angle: {
            /**#@+
             * @visprop
             */

            withLabel: true,

            /**
             * Radius of the sector, displaying the angle.
             * The radius can be given as number (in user coordinates)
             * or as string 'auto'. In the latter case, the angle
             * is set to an value between 20 and 50 px.
             *
             * @type {Number|String}
             * @name Angle#radius
             * @default 'auto'
             * @visprop
             */
            radius: 'auto',

            /**
             * Display type of the angle field. Possible values are
             * 'sector' or 'sectordot' or 'square' or 'none'.
             *
             * @type String
             * @default 'sector'
             * @name Angle#type
             * @visprop
             */
            type: 'sector',

            /**
             * Display type of the angle field in case of a right angle. Possible values are
             * 'sector' or 'sectordot' or 'square' or 'none'.
             *
             * @type String
             * @default square
             * @name Angle#orthoType
             * @see Angle#orthoSensitivity
             * @visprop
             */
            orthoType: 'square',

            /**
             * Sensitivity (in degrees) to declare an angle as right angle.
             * If the angle measure is inside this distance from a rigth angle, the orthoType
             * of the angle is used for display.
             *
             * @type Number
             * @default 1.0
             * @name Angle#orthoSensitivity
             * @see Angle#orthoType
             * @visprop
             */
            orthoSensitivity: 1.0,

            fillColor: Color.palette.orange,
            highlightFillColor: Color.palette.orange,
            strokeColor: Color.palette.orange,
            // fillColor: '#ff7f00',
            // highlightFillColor: '#ff7f00',
            // strokeColor: '#ff7f00',

            fillOpacity: 0.3,
            highlightFillOpacity: 0.3,

            /**
             * @deprecated
             */
            radiuspoint: {
                withLabel: false,
                visible: false,
                name: ''
            },

            /**
             * @deprecated
             */
            pointsquare: {
                withLabel: false,
                visible: false,
                name: ''
            },

            dot: {
                visible: false,
                strokeColor: 'none',
                fillColor: '#000000',
                size: 2,
                face: 'o',
                withLabel: false,
                name: ''
            },

            label: {
                position: 'top',
                offset: [0, 0],
                strokeColor: Color.palette.blue
            },

            /**
             * Attributes for sub-element arc. In general, the arc will run through the first point and
             * thus will not have the same radius as the angle sector.
             *
             * @type Arc
             * @name Angle#arc
             * @default '{visible:false}'
             */
            arc: {
                visible: false,
                fillColor: 'none'
            },

            /**#@-*/
        },

        /* special arc options */
        arc: {
            /**#@+
             * @visprop
             */

            /**
             * Type of arc. Possible values are 'minor', 'major', and 'auto'.
             *
             * @type String
             * @name Arc#selection
             * @default 'auto'
             */
            selection: 'auto',

            /**
             * If <tt>true</tt>, moving the mouse over inner points triggers hasPoint.
             *
             * @see JXG.GeometryElement#hasPoint
             * @name Arc#hasInnerPoints
             * @type Boolean
             * @default false
             */
            hasInnerPoints: false,

            label: {
                anchorX: 'auto',
                anchorY: 'auto'
            },
            firstArrow: false,
            lastArrow: false,
            fillColor: 'none',
            highlightFillColor: 'none',
            strokeColor: Color.palette.blue,
            highlightStrokeColor: '#c3d9ff',
            useDirection: false,

            /**
             * Attributes for center point.
             *
             * @type Point
             * @name Arc#center
             */
            center: {
            },

            /**
             * Attributes for radius point.
             *
             * @type Point
             * @name Arc#radiusPoint
             */
            radiusPoint: {
            },

            /**
             * Attributes for angle point.
             *
             * @type Point
             * @name Arc#anglePoint
             */
            anglePoint: {
            }

            /**#@-*/
        },

        /* special arrow options */
        arrow: {
            /**#@+
             * @visprop
             */

            firstArrow: false,

            lastArrow: {
                type: 1,
                highlightSize: 6,
                size: 6
            }

            /**#@-*/
        },

        /* special axis options */
        axis: {
            /**#@+
             * @visprop
             */

            name: '',                            // By default, do not generate names for axes.
            needsRegularUpdate: false,           // Axes only updated after zooming and moving of the origin.
            strokeWidth: 1,
            lastArrow: {
                type: 1,
                highlightSize: 8,
                size: 8
            },
            strokeColor: '#666666',
            highlightStrokeWidth: 1,
            highlightStrokeColor: '#888888',


            /**
             * Show / hide ticks.
             *
             * Deprecated. Suggested alternative is "ticks: {visible: false}"
             *
             * @type Boolean
             * @name Axis#withTicks
             * @default true
             * @deprecated
             */
            withTicks: true,
            straightFirst: true,
            straightLast: true,
            margin: -4,
            withLabel: false,
            scalable: false,

            /**
             * Attributes for ticks of the axis.
             *
             * @type Ticks
             * @name Axis#ticks
             */
            ticks: {
                label: {
                    offset: [4, -12 + 3],     // This seems to be a good offset for 12 point fonts
                    parse: false,
                    needsRegularUpdate: false,
                    display: 'internal',
                    visible: 'inherit',
                    layer: 9
                },
                visible: 'inherit',
                needsRegularUpdate: false,
                strokeWidth: 1,
                strokeColor: '#666666',
                highlightStrokeColor: '#888888',
                drawLabels: true,
                drawZero: false,
                insertTicks: true,
                minTicksDistance: 5,
                minorHeight: 10,          // if <0: full width and height
                majorHeight: -1,          // if <0: full width and height
                tickEndings: [0, 1],
                minorTicks: 4,
                ticksDistance: 1,         // TODO doc
                strokeOpacity: 0.25
            },

            /**
             * Attributes for first point the axis.
             *
             * @type Point
             * @name Axis#point1
             */
            point1: {                  // Default values for point1 if created by line
                needsRegularUpdate: false,
                visible: false
            },

            /**
             * Attributes for second point the axis.
             *
             * @type Point
             * @name Axis#point2
             */
            point2: {                  // Default values for point2 if created by line
                needsRegularUpdate: false,
                visible: false
            },

            tabindex: -1,

            /**
             * Attributes for the axis label.
             *
             * @type Label
             * @name Axis#label
             */
            label: {
                position: 'lft',
                offset: [10, 10]
            }
            /**#@-*/
        },

        /* special options for angle bisector of 3 points */
        bisector: {
            /**#@+
             * @visprop
             */

            strokeColor: '#000000', // Bisector line

            /**
             * Attributes for the helper point of the bisector.
             *
             * @type Point
             * @name Bisector#point
             */
            point: {               // Bisector point
                visible: false,
                fixed: false,
                withLabel: false,
                name: ''
            }

            /**#@-*/
        },

        /* special options for the 2 bisectors of 2 lines */
        bisectorlines: {
            /**#@+
             * @visprop
             */

            /**
             * Attributes for first line.
             *
             * @type Line
             * @name Bisectorlines#line1
             */
            line1: {               //
                strokeColor: '#000000'
            },

            /**
             * Attributes for second line.
             *
             * @type Line
             * @name Bisectorlines#line2
             */
            line2: {               //
                strokeColor: '#000000'
            }

            /**#@-*/
        },

        /* special options for boxplot curves */
        boxplot: {
            /**#@+
             * @visprop
             */

            /**
             *  Direction of the box plot: 'vertical' or 'horizontal'
             *
             * @type String
             * @name Boxplot#dir
             * @default: 'vertical'
             */
            dir: 'vertical',

            /**
             * Relative width of the maximum and minimum quantile
             *
             * @type Number
             * @name Boxplot#smallWidth
             * @default: 0.5
             */
            smallWidth: 0.5,

            strokeWidth: 2,
            strokeColor: Color.palette.blue,
            fillColor: Color.palette.blue,
            fillOpacity: 0.2,
            highlightStrokeWidth: 2,
            highlightStrokeColor: Color.palette.blue,
            highlightFillColor: Color.palette.blue,
            highlightFillOpacity: 0.1

            /**#@-*/
        },

        /* special button options */
        button: {
            /**#@+
             * @visprop
             */

            /**
             * Control the attribute "disabled" of the HTML button.
             *
             * @name disabled
             * @memberOf Button.prototype
             *
             * @type Boolean
             * @default false
             */
            disabled: false,

            display: 'html'

            /**#@-*/
        },

        /* special cardinal spline options */
        cardinalspline: {
            /**#@+
             * @visprop
             */

             /**
              * Controls if the data points of the cardinal spline when given as
              * arrays should be converted into {@link JXG.Points}.
              *
              * @name createPoints
              * @memberOf Cardinalspline.prototype
              *
              * @see Cardinalspline#points
              *
              * @type Boolean
              * @default true
              */
            createPoints: true,

            /**
             * If set to true, the supplied coordinates are interpreted as
             * [[x_0, y_0], [x_1, y_1], p, ...].
             * Otherwise, if the data consists of two arrays of equal length,
             * it is interpreted as
             * [[x_o x_1, ..., x_n], [y_0, y_1, ..., y_n]]
             *
             * @name isArrayOfCoordinates
             * @memberOf Cardinalspline.prototype
             * @type Boolean
             * @default false
             */
            isArrayOfCoordinates: false,

            /**
             * Attributes for the points generated by Cardinalspline in cases
             * {@link createPoints} is set to true
             *
             * @name points
             * @memberOf Cardinalspline.prototype
             *
             * @see Cardinalspline#createPoints
             * @type Object
             */
            points: {
                strokeOpacity: 0.05,
                fillOpacity: 0.05,
                highlightStrokeOpacity: 1.0,
                highlightFillOpacity: 1.0,
                withLabel: false,
                name: '',
                fixed: false
            }

            /**#@-*/
        },

        /* special chart options */
        chart: {
            /**#@+
             * @visprop
             */

            chartStyle: 'line',
            colors: ['#B02B2C', '#3F4C6B', '#C79810', '#D15600', '#FFFF88', '#c3d9ff', '#4096EE', '#008C00'],
            highlightcolors: null,
            fillcolor: null,
            highlightonsector: false,
            highlightbysize: false,

            fillOpacity: 0.6,
            withLines:  false,

            label: {
            }
            /**#@-*/
        },

        /* special html slider options */
        checkbox: {
            /**#@+
             * @visprop
             */

            /**
             * Control the attribute "disabled" of the HTML checkbox.
             *
             * @name disabled
             * @memberOf Checkbox.prototype
             *
             * @type Boolean
             * @default false
             */
            disabled: false,

            /**
             * Control the attribute "checked" of the HTML checkbox.
             *
             * @name checked
             * @memberOf Checkbox.prototype
             *
             * @type Boolean
             * @default false
             */
            checked: false,

            display: 'html'

            /**#@-*/
        },

        /*special circle options */
        circle: {
            /**#@+
             * @visprop
             */

            /**
             * If <tt>true</tt>, moving the mouse over inner points triggers hasPoint.
             *
             * @see JXG.GeometryElement#hasPoint
             * @name Circle#hasInnerPoints
             * @type Boolean
             * @default false
             */
            hasInnerPoints: false,

            fillColor: 'none',
            highlightFillColor: 'none',
            strokeColor: Color.palette.blue,
            highlightStrokeColor: '#c3d9ff',

            /**
             * Attributes for center point.
             *
             * @type Point
             * @name Circle#center
             */
            center: {
                visible: false,
                withLabel: false,
                fixed: false,

                fillColor: Color.palette.red,
                strokeColor: Color.palette.red,
                highlightFillColor: '#c3d9ff',
                highlightStrokeColor: '#c3d9ff',

                name: ''
            },

            /**
             * Attributes for center point.
             *
             * @type Point
             * @name Circle#center
             */
            point2: {
                visible: false,
                withLabel: false,
                fixed: false,
                name: ''
            },

            /**
             * Attributes for circle label.
             *
             * @type Label
             * @name Circle#label
             */
            label: {
                position: 'urt'
            }
            /**#@-*/
        },

        /* special options for circumcircle of 3 points */
        circumcircle: {
            /**#@+
             * @visprop
             */

            fillColor: 'none',
            highlightFillColor: 'none',
            strokeColor: Color.palette.blue,
            highlightStrokeColor: '#c3d9ff',

            /**
             * Attributes for center point.
             *
             * @type Point
             * @name Circumcircle#center
             */
            center: {               // center point
                visible: false,
                fixed: false,
                withLabel: false,
                fillColor: Color.palette.red,
                strokeColor: Color.palette.red,
                highlightFillColor: '#c3d9ff',
                highlightStrokeColor: '#c3d9ff',
                name: ''
            }
            /**#@-*/
        },

        circumcirclearc: {
            /**#@+
             * @visprop
             */

            fillColor: 'none',
            highlightFillColor: 'none',
            strokeColor: Color.palette.blue,
            highlightStrokeColor: '#c3d9ff',

            /**
             * Attributes for center point.
             *
             * @type Point
             * @name CircumcircleArc#center
             */
            center: {
                visible: false,
                withLabel: false,
                fixed: false,
                name: ''
            }
            /**#@-*/
        },

        /* special options for circumcircle sector of 3 points */
        circumcirclesector: {
            /**#@+
             * @visprop
             */

            useDirection: true,
            fillColor: Color.palette.yellow,
            highlightFillColor: Color.palette.yellow,
            fillOpacity: 0.3,
            highlightFillOpacity: 0.3,
            strokeColor: Color.palette.blue,
            highlightStrokeColor: '#c3d9ff',

            /**
             * Attributes for center point.
             *
             * @type Point
             * @name Circle#point
             */
            point: {
                visible: false,
                fixed: false,
                withLabel: false,
                name: ''
            }
            /**#@-*/
        },

        /* special conic options */
        conic: {
            /**#@+
             * @visprop
             */

            fillColor: 'none',
            highlightFillColor: 'none',
            strokeColor: Color.palette.blue,
            highlightStrokeColor: '#c3d9ff',

            /**
             * Attributes for foci points.
             *
             * @type Point
             * @name Conic#foci
             */
            foci: {
                // points
                fixed: false,
                visible: false,
                withLabel: false,
                name: ''
            },

            /**
             * Attributes for center point.
             *
             * @type Point
             * @name Conic#center
             */
            center: {
                visible: false,
                withLabel: false,
                name: ''
            },

            /**
             * Attributes for five points defining the conic, if some of them are given as coordinates.
             *
             * @type Point
             * @name Conic#point
             */
            point: {
                withLabel: false,
                name: ''
            },

            /**
             * Attributes for parabola line in case the line is given by two
             * points or coordinate pairs.
             *
             * @type Line
             * @name Conic#line
             */
            line: {
                visible: false
            }

            /**#@-*/
        },

        /* special curve options */
        curve: {
            strokeWidth: 1,
            strokeColor: Color.palette.blue,
            fillColor: 'none',
            fixed: true,

            useQDT: false,

            /**#@+
             * @visprop
             */

            /**
             * The data points of the curve are not connected with straight lines but with bezier curves.
             * @name Curve#handDrawing
             * @type Boolean
             * @default false
             */
            handDrawing: false,

            /**
             * The curveType is set in {@link JXG.Curve#generateTerm} and used in {@link JXG.Curve#updateCurve}.
             * Possible values are <ul>
             * <li>'none'</li>
             * <li>'plot': Data plot</li>
             * <li>'parameter': we can not distinguish function graphs and parameter curves</li>
             * <li>'functiongraph': function graph</li>
             * <li>'polar'</li>
             * <li>'implicit' (not yet)</li></ul>
             * Only parameter and plot are set directly. Polar is set with {@link JXG.GeometryElement#setAttribute} only.
             * @name Curve#curveType
             * @type String
             * @default null
             */
            curveType: null,

            /**
             * Apply Ramer-Douglas-Peuker smoothing.
             *
             * @type Boolean
             * @name Curve#RDPsmoothing
             * @default false
             */
            RDPsmoothing: false,     // Apply the Ramer-Douglas-Peuker algorithm

            /**
             * Number of points used for plotting triggered by up events
             * (i.e. high quality plotting) in case
             * {@link Curve#doAdvancedPlot} is false.
             *
             * @name Curve#numberPointsHigh
             * @see Curve#doAdvancedPlot
             * @type Number
             * @default 1600
             */
            numberPointsHigh: 1600,  // Number of points on curves after mouseUp

            /**
             * Number of points used for plotting triggered by move events
             * (i.e. lower quality plotting but fast) in case
             * {@link Curve#doAdvancedPlot} is false.
             *
             * @name Curve#numberPointsLow
             * @see Curve#doAdvancedPlot
             * @type Number
             * @default 400
             */
            numberPointsLow: 400,    // Number of points on curves after mousemove

            /**
             * If true use a recursive bisection algorithm.
             * It is slower, but usually the result is better. It tries to detect jumps
             * and singularities.
             *
             * @name Curve#doAdvancedPlot
             * @type Boolean
             * @default true
             */
            doAdvancedPlot: true,

            /**
             *
             * Recursion depth used for plotting triggered by up events
             * (i.e. high quality plotting) in case
             * {@link Curve#doAdvancedPlot} is true.
             *
             * @name Curve#recursionDepthHigh
             * @see Curve#doAdvancedPlot
             * @type Number
             * @default 17
             */
             recursionDepthHigh: 17,

            /**
             * Number of points used for plotting triggered by move events in case
             * (i.e. lower quality plotting but fast)
             * {@link Curve#doAdvancedPlot} is true.
             *
             * @name Curve#recursionDepthLow
             * @see Curve#doAdvancedPlot
             * @type Number
             * @default 13
             */
             recursionDepthLow: 15,

            /**
             * If true use the algorithm by Gillam and Hohenwarter, which was default until version 0.98.
             *
             * @name Curve#doAdvancedPlotOld
             * @see Curve#doAdvancedPlot
             * @type Boolean
             * @default false
             * @deprecated
             */
            doAdvancedPlotOld: false,   // v1

            /**
             * Select the version of the plot algorithm.
             * <ul>
             * <li> Version 1 is very outdated
             * <li> Version 2 is the default version in JSXGraph v0.99.*, v1.0, and v1.1, v1.2.0
             * <li> Version 3 is an internal version that was never published in  a stable version.
             * <li> Version 4 is available since JSXGraph v1.2.0
             * </ul>
             * Version 4 plots correctly logarithms if the function term is supplied as string (i.e. as JessieCode)
             *
             * @example
             *   var c = board.create('functiongraph', ["log(x)"]);
             *
             * @name Curve#plotVersion
             * @type Number
             * @default 2
             */
            plotVersion: 2,

            /**
             * Attributes for circle label.
             *
             * @type Label
             * @name Circle#label
             */
            label: {
                position: 'lft'
            },

            /**
             * Configure arrow head at the start position for curve.
             * Recommended arrow head type is 7.
             *
             * @name Curve#firstArrow
             * @type Boolean / Object
             * @default false
             * @see Line#firstArrow for options
             */
            firstArrow: false,

            /**
             * Configure arrow head at the end position for curve.
             * Recommended arrow head type is 7.
             *
             * @name Curve#lastArrow
             * @see Line#lastArrow for options
             * @type Boolean / Object
             * @default false
             */
            lastArrow: false

            /**#@-*/
        },

        /* special foreignObject options */
        foreignobject: {

            /**#@+
             * @visprop
             */
            attractors: [],
            fixed: true,
            visible: true

            /**#@-*/
        },

        glider: {
            /**#@+
             * @visprop
             */

            label: {}
            /**#@-*/
        },

        /* special grid options */
        grid: {
            /**#@+
             * @visprop
             */

            /* grid styles */
            needsRegularUpdate: false,
            hasGrid: false,
            gridX: 1,
            gridY: 1,
            //strokeColor: '#c0c0c0',
            strokeColor: '#c0c0c0',
            strokeOpacity: 0.5,
            strokeWidth: 1,
            dash: 0,    // dashed grids slow down the iPad considerably
            /* snap to grid options */

            /**
             * @deprecated
             */
            snapToGrid: false,
            /**
             * @deprecated
             */
            snapSizeX: 10,
            /**
             * @deprecated
             */
            snapSizeY: 10

            /**#@-*/
        },

        group: {
            needsRegularUpdate: true
        },

        /* special html slider options */
        htmlslider: {
            /**#@+
             * @visprop
             */

            /**
             *
             * These affect the DOM element input type="range".
             * The other attributes affect the DOM element div containing the range element.
             */
            widthRange: 100,
            widthOut: 34,
            step: 0.01,

            frozen: true,
            isLabel: false,
            strokeColor: '#000000',
            display: 'html',
            anchorX: 'left',
            anchorY: 'middle',
            withLabel: false

            /**#@-*/
        },

        /* special image options */
        image: {
            /**#@+
             * @visprop
             */

            imageString: null,
            fillOpacity: 1.0,
            highlightFillOpacity: 0.6,


            /**
             * Defines the CSS class used by the image. CSS attributes defined in
             * this class will overwrite the corresponding JSXGraph attributes, e.g.
             * opacity.
             * The default CSS class is defined in jsxgraph.css.
             *
             * @name Image#cssClass
             *
             * @see Image#highlightCssClass
             * @type String
             * @default 'JXGimage'
             */
            cssClass: 'JXGimage',

            /**
             * Defines the CSS class used by the image when highlighted.
             * CSS attributes defined in this class will overwrite the
             * corresponding JSXGraph attributes, e.g. highlightFillOpacity.
             * The default CSS class is defined in jsxgraph.css.
             *
             * @name Image#highlightCssClass
             *
             * @see Image#cssClass
             * @type String
             * @default 'JXGimageHighlight'
             */
            highlightCssClass: 'JXGimageHighlight',

            /**
             * Image rotation in degrees.
             *
             * @name Image#rotate
             * @type Number
             * @default 0
             */
            rotate: 0,

            /**
             * Defines together with {@link Image#snapSizeY} the grid the image snaps on to.
             * The image will only snap on user coordinates which are
             * integer multiples to snapSizeX in x and snapSizeY in y direction.
             * If this value is equal to or less than <tt>0</tt>, it will use the grid displayed by the major ticks
             * of the default ticks of the default x axes of the board.
             *
             * @name Image#snapSizeX
             *
             * @see Point#snapToGrid
             * @see Image#snapSizeY
             * @see JXG.Board#defaultAxes
             * @type Number
             * @default 1
             */
            snapSizeX: 1,

            /**
             * Defines together with {@link Image#snapSizeX} the grid the image snaps on to.
             * The image will only snap on integer multiples to snapSizeX in x and snapSizeY in y direction.
             * If this value is equal to or less than <tt>0</tt>, it will use the grid displayed by the major ticks
             * of the default ticks of the default y axes of the board.
             *
             * @name Image#snapSizeY
             *
             * @see Point#snapToGrid
             * @see Image#snapSizeX
             * @see JXG.Board#defaultAxes
             * @type Number
             * @default 1
             */
            snapSizeY: 1,

            /**
             * List of attractor elements. If the distance of the image is less than
             * attractorDistance the image is made to glider of this element.
             *
             * @name Image#attractors
             *
             * @type Array
             * @default empty
             */
            attractors: []

            /**#@-*/
        },

        /* special options for incircle of 3 points */
        incircle: {
            /**#@+
             * @visprop
             */

            fillColor: 'none',
            highlightFillColor: 'none',
            strokeColor: Color.palette.blue,
            highlightStrokeColor: '#c3d9ff',

            /**
             * Attributes of circle center.
             *
             * @type Point
             * @name Incircle#center
             */
            center: {               // center point
                visible: false,
                fixed: false,
                withLabel: false,
                fillColor: Color.palette.red,
                strokeColor: Color.palette.red,
                highlightFillColor: '#c3d9ff',
                highlightStrokeColor: '#c3d9ff',
                name: ''
            }
            /**#@-*/
        },

        inequality: {
            /**#@+
             * @visprop
             */

            fillColor: Color.palette.red,
            fillOpacity: 0.2,
            strokeColor: 'none',

            /**
             * By default an inequality is less (or equal) than. Set inverse to <tt>true</tt> will consider the inequality
             * greater (or equal) than.
             *
             * @type Boolean
             * @default false
             * @name Inequality#inverse
             * @visprop
             */
            inverse: false
            /**#@-*/
        },

        infobox: {
            /**#@+
             * @visprop
             */

            fontSize: 12,
            isLabel: false,
            strokeColor: '#bbbbbb',
            display: 'html',             // 'html' or 'internal'
            anchorX: 'left',             //  'left', 'middle', or 'right': horizontal alignment
                                         //  of the text.
            anchorY: 'middle',           //  'top', 'middle', or 'bottom': vertical alignment
                                         //  of the text.
            cssClass: 'JXGinfobox',
            rotate: 0,                   // works for non-zero values only in combination
                                         // with display=='internal'
            visible: true,
            parse: false,
            transitionDuration: 0,
            needsRegularUpdate: false

            /**#@-*/
        },

        /* special options for integral */
        integral: {
            /**#@+
             * @visprop
             */

            axis: 'x',        // 'x' or 'y'
            withLabel: true,    // Show integral value as text
            fixed: true,
            strokeWidth: 0,
            strokeOpacity: 0,
            fillColor: Color.palette.red,
            fillOpacity: 0.3,
            highlightFillColor: Color.palette.red,
            highlightFillOpacity: 0.2,

            /**
             * Attributes of the (left) starting point of the integral.
             *
             * @type Point
             * @name Integral#curveLeft
             * @see Integral#baseLeft
             */
            curveLeft: {    // Start point
                visible: true,
                withLabel: false,
                color: Color.palette.red,
                fillOpacity: 0.8,
                layer: 9
            },

            /**
             * Attributes of the (left) base point of the integral.
             *
             * @type Point
             * @name Integral#baseLeft
             * @see Integral#curveLeft
             */
            baseLeft: {    // Start point
                visible: false,
                fixed: false,
                withLabel: false,
                name: ''
            },

            /**
             * Attributes of the (right) end point of the integral.
             *
             * @type Point
             * @name Integral#curveRight
             * @see Integral#baseRight
             */
            curveRight: {      // End point
                visible: true,
                withLabel: false,
                color: Color.palette.red,
                fillOpacity: 0.8,
                layer: 9
            },

            /**
             * Attributes of the (right) base point of the integral.
             *
             * @type Point
             * @name Integral#baseRight
             * @see Integral#curveRight
             */
            baseRight: {      // End point
                visible: false,
                fixed: false,
                withLabel: false,
                name: ''
            },

            /**
             * Attributes for integral label.
             *
             * @type Label
             * @name Integral#label
             */
            label: {
                fontSize: 20
            }
            /**#@-*/
        },

        /* special input options */
        input: {
            /**#@+
             * @visprop
             */

            /**
             * Control the attribute "disabled" of the HTML input field.
             *
             * @name disabled
             * @memberOf Input.prototype
             *
             * @type Boolean
             * @default false
             */
            disabled: false,

            /**
             * Control the attribute "maxlength" of the HTML input field.
             *
             * @name maxlength
             * @memberOf Input.prototype
             *
             * @type Number
             * @default 524288 (as in HTML)
             */
            maxlength: 524288,

            display: 'html'

            /**#@-*/
        },

        /* special intersection point options */
        intersection: {
            /**#@+
             * @visprop
             */

            /**
             * Used in {@link JXG.Intersection}.
             * This flag sets the behaviour of intersection points of e.g.
             * two segments. If true, the intersection is treated as intersection of lines. If false
             * the intersection point exists if the segments intersect setwise.
             *
             * @name Intersection.alwaysIntersect
             * @type Boolean
             * @default true
             */
            alwaysIntersect: true

            /**#@-*/
        },

        /* special label options */
        label: {
            /**#@+
             * @visprop
             */

            visible: 'inherit',
            strokeColor: '#000000',
            strokeOpacity: 1,
            highlightStrokeOpacity: 0.666666,
            highlightStrokeColor: '#000000',

            fixed: true,

            /**
             * Possible string values for the position of a label for
             * label anchor points are:
             * <ul>
             * <li> 'lft'
             * <li> 'rt'
             * <li> 'top'
             * <li> 'bot'
             * <li> 'ulft'
             * <li> 'urt'
             * <li> 'llft'
             * <li> 'lrt'
             * </ul>
             * This is relevant for non-points: line, circle, curve.
             *
             * The names have been borrowed from <a href="https://www.tug.org/metapost.html">MetaPost</a>.
             *
             * @name Label#position
             * @see Label#offset
             * @type String
             * @default 'urt'
             */
            position: 'urt',

            /**
             *  Label offset from label anchor.
             *  The label anchor is determined by {@link Label#position}
             *
             * @name Label#offset
             * @see Label#position
             * @type Array
             * @default [10,10]
             */
            offset: [10, 10],

            /**
             * Automatic position of label text. When called first, the positioning algorithm
             * starts at the position defined by offset.
             * The algorithm tries to find a position with the least number of
             * overlappings with other elements, while retaining the distance
             * to the anchor element.
             *
             * @name Label#autoPosition
             * @see Label#offset
             * @type Boolean
             * @default false
             *
             * @example
             * 	var p1 = board.create('point', [-2, 1], {id: 'A'});
             * 	var p2 = board.create('point', [-0.85, 1], {
             *      name: 'B', id: 'B', label:{autoPosition: true, offset:[10, 10]}
             *  });
             * 	var p3 = board.create('point', [-1, 1.2], {
             *      name: 'C', id: 'C', label:{autoPosition: true, offset:[10, 10]}
             *  });
             *  var c = board.create('circle', [p1, p2]);
             * 	var l = board.create('line', [p1, p2]);
             *
             * </pre><div id="JXG7d4dafe7-1a07-4d3f-95cb-bfed9d96dea2" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG7d4dafe7-1a07-4d3f-95cb-bfed9d96dea2',
             *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
             *     	var p1 = board.create('point', [-2, 1], {id: 'A'});
             *     	var p2 = board.create('point', [-0.85, 1], {name: 'B', id: 'B', label:{autoPosition: true, offset:[10, 10]}});
             *     	var p3 = board.create('point', [-1, 1.2], {name: 'C', id: 'C', label:{autoPosition: true, offset:[10, 10]}});
             *      var c = board.create('circle', [p1, p2]);
             *     	var l = board.create('line', [p1, p2]);
             *
             *     })();
             *
             * </script><pre>
             *
             *
             */
            autoPosition: false

            /**#@-*/
        },

        /* special legend options */
        legend: {
            /**
             * @visprop
             */

             /**
              * Default style of a legend element. The only possible value is 'vertical'.
              * @name: Legend#style
              * @type String
              * @default 'vertical'
              */
            style: 'vertical',

            /**
             * Label names of a legend element.
             * @name: Legend#labels
             * @type Array
             * @default "['1', '2', '3', '4', '5', '6', '7', '8']"
             */
            labels: ['1', '2', '3', '4', '5', '6', '7', '8'],

            /**
             * (Circular) array of label colors.
             * @name: Legend#colors
             * @type Array
             * @default "['#B02B2C', '#3F4C6B', '#C79810', '#D15600', '#FFFF88', '#c3d9ff', '#4096EE', '#008C00']"
             */
            colors: ['#B02B2C', '#3F4C6B', '#C79810', '#D15600', '#FFFF88', '#c3d9ff', '#4096EE', '#008C00'],

            /**
             * Height (in px) of one legend entry
             * @name: Legend#rowHeight
             * @type Number
             * @default 20
             *
             */
            rowHeight: 20,

            strokeWidth: 5

            /**#@-*/
        },

        /* special line options */
        line: {
            /**#@+
             * @visprop
             */

            /**
             * Configure the arrow head at the position of its first point or the corresponding
             * intersection with the canvas border
             *
             * In case firstArrow is an object it has the sub-attributes:
             * <pre>
             * {
             *      type: 1, // possible values are 1, 2, ..., 7. Default value is 1.
             *      size: 6, // size of the arrow head. Default value is 6.
             *               // This value is multiplied with the strokeWidth of the line
             *               // Exception: for type=7 size is ignored
             *      highlightSize: 6, // size of the arrow head in case the element is highlighted. Default value
             * }
             * </pre>
             * type=7 is the default for curves if firstArrow: true
             *
             * @name Line#firstArrow
             * @see Line#lastArrow
             * @see Line#touchFirstPoint
             * @type Boolean / Object
             * @default false
             */
            firstArrow: false,

            /**
             * Configute the arrow head at the position of its second point or the corresponding
             * intersection with the canvas border.
             *
             * In case lastArrow is an object it has the sub-attributes:
             * <pre>
             * {
             *      type: 1, // possible values are 1, 2, ..., 7. Default value is 1.
             *      size: 6, // size of the arrow head. Default value is 6.
             *               // This value is multiplied with the strokeWidth of the line.
             *               // Exception: for type=7 size is ignored
             *      highlightSize: 6, // size of the arrow head in case the element is highlighted. Default value is 6.
             * }
             * </pre>
             * type=7 is the default for curves if lastArrow: true
             *
             * @example
             *     var p1 = board.create('point', [-5, 2], {size:1});
             *     var p2 = board.create('point', [5, 2], {size:10});
             *     var li = board.create('segment', ['A','B'],
             *         {name:'seg',
             *          strokeColor:'#000000',
             *          strokeWidth:1,
             *          highlightStrokeWidth: 5,
             *          lastArrow: {type: 2, size: 8, highlightSize: 6},
             *          touchLastPoint: true,
             *          firstArrow: {type: 3, size: 8}
             *         });
             *
             * </pre><div id="JXG184e915c-c2ef-11e8-bece-04d3b0c2aad3" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG184e915c-c2ef-11e8-bece-04d3b0c2aad3',
             *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
             *         var p1 = board.create('point', [-5, 2], {size:1});
             *         var p2 = board.create('point', [5, 2], {size:10});
             *         var li = board.create('segment', ['A','B'],
             *             {name:'seg',
             *              strokeColor:'#000000',
             *              strokeWidth:1,
             *              highlightStrokeWidth: 5,
             *              lastArrow: {type: 2, size: 8, highlightSize: 6},
             *              touchLastPoint: true,
             *              firstArrow: {type: 3, size: 8}
             *             });
             *
             *     })();
             *
             * </script>
             *
             * @name Line#lastArrow
             * @see Line#firstArrow
             * @see Line#touchLastPoint
             * @type Boolean / Object
             * @default false
             */
            lastArrow: false,


            /**
             * This number (pixel value) controls where infinite lines end at the canvas border. If zero, the line
             * ends exactly at the border, if negative there is a margin to the inside, if positive the line
             * ends outside of the canvas (which is invisible).
             *
             * @name: Line#margin
             * @type Number
             * @default 0
             */
            margin: 0,

            /**
             * If true, line stretches infinitely in direction of its first point.
             * Otherwise it ends at point1.
             *
             * @name Line#straightFirst
             * @see Line#straightLast
             * @type Boolean
             * @default true
             */
            straightFirst: true,

            /**
             * If true, line stretches infinitely in direction of its second point.
             * Otherwise it ends at point2.
             *
             * @name Line#straightLast
             * @see Line#straightFirst
             * @type Boolean
             * @default true
             */
            straightLast: true,

            fillColor: 'none',               // Important for VML on IE
            highlightFillColor: 'none',  // Important for VML on IE
            strokeColor: Color.palette.blue,
            highlightStrokeColor: '#c3d9ff',
            withTicks: false,

            /**
             * Attributes for first defining point of the line.
             *
             * @type Point
             * @name Line#point1
             */
            point1: {                  // Default values for point1 if created by line
                visible: false,
                withLabel: false,
                fixed: false,
                name: ''
            },

            /**
             * Attributes for second defining point of the line.
             *
             * @type Point
             * @name Line#point2
             */
            point2: {                  // Default values for point2 if created by line
                visible: false,
                withLabel: false,
                fixed: false,
                name: ''
            },

            /**
             * Attributes for ticks of the line.
             *
             * @type Ticks
             * @name Line#ticks
             */
            ticks: {
                drawLabels: true,
                label: {
                    offset: [4, -12 + 3] // This seems to be a good offset for 12 point fonts
                },
                drawZero: false,
                insertTicks: false,
                minTicksDistance: 50,
                minorHeight: 4,          // if <0: full width and height
                majorHeight: -1,         // if <0: full width and height
                minorTicks: 4,
                defaultDistance: 1,
                strokeOpacity: 0.3,
                visible: 'inherit'
            },

            /**
             * Attributes for the line label.
             *
             * @type Label
             * @name Line#label
             */
            label: {
                position: 'llft'
            },

            /**
             * If set to true, the point will snap to a grid defined by
             * {@link Point#snapSizeX} and {@link Point#snapSizeY}.
             *
             * @see Point#snapSizeX
             * @see Point#snapSizeY
             * @type Boolean
             * @name Line#snapToGrid
             * @default false
             */
            snapToGrid: false,

            /**
             * Defines together with {@link Point#snapSizeY} the grid the point snaps on to.
             * The point will only snap on integer multiples to snapSizeX in x and snapSizeY in y direction.
             * If this value is equal to or less than <tt>0</tt>, it will use the grid displayed by the major ticks
             * of the default ticks of the default x axes of the board.
             *
             * @see Point#snapToGrid
             * @see Point#snapSizeY
             * @see JXG.Board#defaultAxes
             * @type Number
             * @name Line#snapSizeX
             * @default 1
             */
            snapSizeX: 1,

            /**
             * Defines together with {@link Point#snapSizeX} the grid the point snaps on to.
             * The point will only snap on integer multiples to snapSizeX in x and snapSizeY in y direction.
             * If this value is equal to or less than <tt>0</tt>, it will use the grid displayed by the major ticks
             * of the default ticks of the default y axes of the board.
             *
             * @see Point#snapToGrid
             * @see Point#snapSizeX
             * @see JXG.Board#defaultAxes
             * @type Number
             * @name Line#snapSizeY
             * @default 1
             */
            snapSizeY: 1,

            /**
             * If set to true and {@link Line#firstArrow} is set to true, the arrow head will just touch
             * the circle line of the start point of the line.
             *
             * @see Line#firstArrow
             * @type Boolean
             * @name Line#touchFirstPoint
             * @default false
             */
            touchFirstPoint: false,

            /**
             * If set to true and {@link Line#lastArrow} is set to true, the arrow head will just touch
             * the circle line of the start point of the line.
             * @see Line#firstArrow
             * @type Boolean
             * @name Line#touchLastPoint
             * @default false
             */
            touchLastPoint: false,

            /**
             * Line endings (linecap) of a straight line.
             * Possible values are:
             * <ul>
             * <li> 'butt',
             * <li> 'round',
             * <li> 'square'.
             * </ul>
             * Not available for VML renderer.
             * [lineCap description]
             * @name Line#lineCap
             * @type String
             * @default 'butt'
             */
            lineCap: 'butt'


            /**#@-*/
        },

        /* special options for locus curves */
        locus: {
            /**#@+
             * @visprop
             */

            translateToOrigin: false,
            translateTo10: false,
            stretch: false,
            toOrigin: null,
            to10: null
            /**#@-*/
        },

        /* special cardinal spline options */
        metapostspline: {
            /**#@+
             * @visprop
             */

             /**
              * Controls if the data points of the cardinal spline when given as
              * arrays should be converted into {@link JXG.Points}.
              *
              * @name createPoints
              * @memberOf Metapostspline.prototype
              *
              * @see Metapostspline#points
              *
              * @type Boolean
              * @default true
              */
            createPoints: true,

            /**
             * If set to true, the supplied coordinates are interpreted as
             * [[x_0, y_0], [x_1, y_1], p, ...].
             * Otherwise, if the data consists of two arrays of equal length,
             * it is interpreted as
             * [[x_o x_1, ..., x_n], [y_0, y_1, ..., y_n]]
             *
             * @name isArrayOfCoordinates
             * @memberOf Metapostspline.prototype
             * @type Boolean
             * @default false
             */
            isArrayOfCoordinates: false,

            /**
             * Attributes for the points generated by Metapostspline in cases
             * {@link createPoints} is set to true
             *
             * @name points
             * @memberOf Metapostspline.prototype
             *
             * @see Metapostspline#createPoints
             * @type Object
             */
            points: {
                strokeOpacity: 0.05,
                fillOpacity: 0.05,
                highlightStrokeOpacity: 1.0,
                highlightFillOpacity: 1.0,
                withLabel: false,
                name: '',
                fixed: false
            }

            /**#@-*/
        },

        /* special mirrorelement options */
        mirrorelement: {
            /**#@+
             * @visprop
             */

            fixed: true,

            /**
             * Attributes of mirror point, i.e. the point along which the element is mirrored.
             *
             * @type Point
             * @name mirrorelement#point
             */
            point: {},

            /**
             * Attributes of circle center, i.e. the center of the circle,
             * if a circle is the mirror element and the transformation type is 'Euclidean'
             *
             * @type Point
             * @name mirrorelement#center
             */
            center: {},

            /**
             * Type of transformation. Possible values are 'Euclidean', 'projective'.
             *
             * If the value is 'Euclidean', the mirror element of a circle is again a circle,
             * otherwise it is a conic section.
             *
             * @type String
             * @name mirrorelement#type
             * @default 'Euclidean'
             */
            type: 'Euclidean'

            /**#@-*/
        },

        // /* special options for Msector of 3 points */
        // msector: {
        //     strokeColor: '#000000', // Msector line
        //     point: {               // Msector point
        //         visible: false,
        //         fixed: false,
        //         withLabel: false,
        //         name: ''
        //     }
        // },

        /* special options for normal lines */
        normal: {
            /**#@+
             * @visprop
             */

            strokeColor: '#000000', //  normal line

            /**
             * Attributes of helper point of normal.
             *
             * @type Point
             * @name Normal#point
             */
            point: {
                visible: false,
                fixed: false,
                withLabel: false,
                name: ''
            }
            /**#@-*/
        },

        /* special options for orthogonal projection points */
        orthogonalprojection: {
            /**#@+
             * @visprop
             */


            /**#@-*/
        },

        /* special options for parallel lines */
        parallel: {
            /**#@+
             * @visprop
             */

            strokeColor: '#000000', // Parallel line

            /**
             * Attributes of helper point of normal.
             *
             * @type Point
             * @name Parallel#point
             */
            point: {
                visible: false,
                fixed: false,
                withLabel: false,
                name: ''
            },

            label: {
                position: 'llft'
            }
            /**#@-*/
        },

        /* special perpendicular options */
        perpendicular: {
            /**#@+
             * @visprop
             */

            strokeColor: '#000000', // Perpendicular line
            straightFirst: true,
            straightLast: true
            /**#@-*/
        },

        /* special perpendicular options */
        perpendicularsegment: {
            /**#@+
             * @visprop
             */

            strokeColor: '#000000', // Perpendicular segment
            straightFirst: false,
            straightLast: false,
            point: {               // Perpendicular point
                visible: false,
                fixed: true,
                withLabel: false,
                name: ''
            }
            /**#@-*/
        },

        /* special point options */
        point: {
            /**#@+
             * @visprop
             */

            withLabel: true,
            label: {},

            /**
             * This attribute was used to determined the point layout. It was derived from GEONExT and was
             * replaced by {@link Point#face} and {@link Point#size}.
             *
             * @name Point#style
             *
             * @see Point#face
             * @see Point#size
             * @type Number
             * @default 5
             * @deprecated
             */
            style: 5,

            /**
             * There are different point styles which differ in appearance.
             * Posssible values are
             * <table><tr><th>Value</th></tr>
             * <tr><td>cross</td></tr>
             * <tr><td>circle</td></tr>
             * <tr><td>square</td></tr>
             * <tr><td>plus</td></tr>
             * <tr><td>diamond</td></tr>
             * <tr><td>triangleUp</td></tr>
             * <tr><td>triangleDown</td></tr>
             * <tr><td>triangleLeft</td></tr>
             * <tr><td>triangleRight</td></tr>
             * </table>
             *
             * @name Point#face
             *
             * @type String
             * @see JXG.Point#setStyle
             * @default circle
             */
            face: 'o',

            /**
             * Size of a point, either in pixel or user coordinates.
             * Means radius resp. half the width of a point (depending on the face).
             *
             * @name Point#size
             *
             * @see Point#face
             * @see JXG.Point#setStyle
             * @see Point#sizeUnit
             * @type Number
             * @default 3
             */
            size: 3,

            /**
             * Unit for size.
             * Possible values are 'screen' and 'user.
             *
             * @name Point#sizeUnit
             *
             * @see Point#size
             * @type String
             * @default 'screen'
             */
            sizeUnit: 'screen',

            strokeWidth: 2,

            fillColor: Color.palette.red,
            strokeColor: Color.palette.red,
            highlightFillColor:'#c3d9ff',
            highlightStrokeColor: '#c3d9ff',
            // strokeOpacity: 1.0,
            // fillOpacity: 1.0,
            // highlightFillOpacity: 0.5,
            // highlightStrokeOpacity: 0.5,

            // fillColor: '#ff0000',
            // highlightFillColor: '#eeeeee',
            // strokeWidth: 2,
            // strokeColor: '#ff0000',
            // highlightStrokeColor: '#c3d9ff',

            /**
             * If true, the point size changes on zoom events.
             *
             * @type Boolean
             * @name Point#zoom
             * @default false
             *
             */
            zoom: false,             // Change the point size on zoom

            /**
             * If true, the infobox is shown on mouse/pen over, if false not.
             * If the value is 'inherit', the value of
             * {@link JXG.Board#showInfobox} is taken.
             *
             * @name Point#showInfobox
             * @see JXG.Board#showInfobox
             * @type {Boolean|String} true | false | 'inherit'
             * @default true
             */
            showInfobox: 'inherit',

            /**
             * Truncating rule for the digits in the infobox.
             * <ul>
             * <li>'auto': done automatically by JXG.autoDigits()
             * <li>'none': no truncation
             * <li>number: truncate after "number digits" with JXG.toFixed()
             * </ul>
             *
             * @name Point#infoboxDigits
             *
             * @type String, Number
             * @default 'auto'
             * @see JXG#autoDigits
             * @see JXG#toFixed
             */
            infoboxDigits: 'auto',

            draft: false,

            /**
             * List of attractor elements. If the distance of the point is less than
             * attractorDistance the point is made to glider of this element.
             *
             * @name Point#attractors
             *
             * @type Array
             * @default empty
             */
            attractors: [],

            /**
             * Unit for attractorDistance and snatchDistance, used for magnetized points and for snapToPoints.
             * Possible values are 'screen' and 'user'.
             *
             * @name Point#attractorUnit
             *
             * @see Point#attractorDistance
             * @see Point#snatchDistance
             * @see Point#snapToPoints
             * @see Point#attractors
             * @type String
             * @default 'user'
             */
            attractorUnit: 'user',    // 'screen', 'user'

            /**
             * If the distance of the point to one of its attractors is less
             * than this number the point will be a glider on this
             * attracting element.
             * If set to zero nothing happens.
             *
             * @name Point#attractorDistance
             *
             * @type Number
             * @default 0.0
             */
            attractorDistance: 0.0,

            /**
             * If the distance of the point to one of its attractors is at least
             * this number the point will be released from being a glider on the
             * attracting element.
             * If set to zero nothing happens.
             *
             * @name Point#snatchDistance
             *
             * @type Number
             * @default 0.0
             */
            snatchDistance: 0.0,

            /**
             * If set to true, the point will snap to a grid of integer multiples of
             * {@link Point#snapSizeX} and {@link Point#snapSizeY} (in user coordinates).
             * <p>
             * The coordinates of the grid points are either integer multiples of snapSizeX and snapSizeY
             * (given in user coordinates, not pixels) or are the intersection points
             * of the major ticks of the boards default axes in case that snapSizeX, snapSizeY are negative.
             *
             * @name Point#snapToGrid
             *
             * @see Point#snapSizeX
             * @see Point#snapSizeY
             * @type Boolean
             * @default false
             */
            snapToGrid: false,

            /**
             * If set to true, the point will only snap to (possibly invisibly) grid points
             * when within {@link Point#attractorDistance} of such a grid point.
             * <p>
             * The coordinates of the grid points are either integer multiples of snapSizeX and snapSizeY
             * (given in user coordinates, not pixels) or are the intersection points
             * of the major ticks of the boards default axes in case that snapSizeX, snapSizeY are negative.
             *
             * @name Point#attractToGrid
             *
             * @see Point#attractorDistance
             * @see Point#attractorUnit
             * @see Point#snapToGrid
             * @see Point#snapSizeX
             * @see Point#snapSizeY
             * @type Boolean
             * @default false
             *
             * @example
             * board.create('point', [3, 3], { attractToGrid: true, attractorDistance: 10, attractorunit: 'screen' });
             *
             * </pre><div id="JXG397ab787-cd40-449c-a7e7-a3f7bab1d4f6" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG397ab787-cd40-449c-a7e7-a3f7bab1d4f6',
             *             {boundingbox: [-1, 4, 7,-4], axis: true, showcopyright: false, shownavigation: false});
             *     board.create('point', [3, 3], { attractToGrid: true, attractorDistance: 10, attractorunit: 'screen' });
             *
             *     })();
             *
             * </script><pre>
             *
             */
            attractToGrid: false,

            /**
             * Defines together with {@link Point#snapSizeY} the grid the point snaps on to.
             * It is given in user coordinates, not in pixels.
             * The point will only snap on integer multiples to snapSizeX in x and snapSizeY in y direction.
             * If this value is equal to or less than <tt>0</tt>, it will use the grid displayed by the major ticks
             * of the default ticks of the default x axes of the board.
             *
             * @name Point#snapSizeX
             *
             * @see Point#snapToGrid
             * @see Point#snapSizeY
             * @see JXG.Board#defaultAxes
             * @type Number
             * @default 1
             */
            snapSizeX: 1,

            /**
             * Defines together with {@link Point#snapSizeX} the grid the point snaps on to.
             * It is given in user coordinates, not in pixels.
             * The point will only snap on integer multiples to snapSizeX in x and snapSizeY in y direction.
             * If this value is equal to or less than <tt>0</tt>, it will use the grid displayed by the major ticks
             * of the default ticks of the default y axes of the board.
             *
             * @name Point#snapSizeY
             *
             * @see Point#snapToGrid
             * @see Point#snapSizeX
             * @see JXG.Board#defaultAxes
             * @type Number
             * @default 1
             */
            snapSizeY: 1,

            /**
             * If set to true, the point will snap to the nearest point in distance of
             * {@link Point#attractorDistance}.
             *
             * @name Point#snapToPoints
             *
             * @see Point#attractorDistance
             * @type Boolean
             * @default false
             */
            snapToPoints: false,

            /**
             * List of elements which are ignored by snapToPoints.
             * @name Point#ignoredSnapToPoints
             *
             * @type Array
             * @default empty
             */
            ignoredSnapToPoints: []

            /**#@-*/
        },

        /* special polygon options */
        polygon: {
            /**#@+
             * @visprop
             */

            /**
             * If <tt>true</tt>, moving the mouse over inner points triggers hasPoint.
             *
             * @see JXG.GeometryElement#hasPoint
             * @name Polygon#hasInnerPoints
             * @type Boolean
             * @default false
             */
            hasInnerPoints: false,

            fillColor: Color.palette.yellow,
            highlightFillColor: Color.palette.yellow,
            // fillColor: '#00ff00',
            // highlightFillColor: '#00ff00',
            fillOpacity: 0.3,
            highlightFillOpacity: 0.2,

            /**
             * Is the polygon bordered by lines?
             *
             * @type Boolean
             * @name Polygon#withLines
             * @default true
             */
            withLines: true,

            /**
             * Attributes for the polygon border lines.
             *
             * @type Line
             * @name Polygon#borders
             */
            borders: {
                withLabel: false,
                strokeWidth: 1,
                highlightStrokeWidth: 1,
                // Polygon layer + 1
                layer: 5,
                label: {
                    position: 'top'
                },
                visible: 'inherit'
            },

            /**
             * Attributes for the polygon vertices.
             *
             * @type Point
             * @name Polygon#vertices
             */
            vertices: {
                layer: 9,
                withLabel: false,
                name: '',
                strokeColor: Color.palette.red,
                fillColor: Color.palette.red,
                fixed: false,
                visible: 'inherit'
            },

            /**
             * Attributes for the polygon label.
             *
             * @type Label
             * @name Polygon#label
             */
            label: {
                offset: [0, 0]
            }

            /**#@-*/
        },

        /* special polygonal chain options
        */
        polygonalchain: {
            /**#@+
             * @visprop
             */

            fillColor: 'none',
            highlightFillColor: 'none'

            /**#@-*/
        },

        /* special prescribed angle options
        * Not yet implemented. But angle.setAngle(val) is implemented.
        */
        prescribedangle: {
            /**#@+
             * @visprop
             */

            /**
             * Attributes for the helper point of the prescribed angle.
             *
             * @type Point
             * @name PrescribedAngle#anglePoint
             */
            anglePoint: {
                size: 2,
                visible: false,
                withLabel: false
            }

            /**#@-*/
        },

        /* special reflection options */
        reflection: {
            /**#@+
             * @visprop
             */

            fixed: true,

            /**
             * Attributes of circle center, i.e. the center of the circle,
             * if a circle is the mirror element and the transformation type is 'Euclidean'
             *
             * @type Point
             * @name mirrorelement#center
             */
            center: {},

            /**
             * Type of transformation. Possible values are 'Euclidean', 'projective'.
             *
             * If the value is 'Euclidean', the reflected element of a circle is again a circle,
             * otherwise it is a conic section.
             *
             * @type String
             * @name reflection#type
             * @default 'Euclidean'
             */
            type: 'Euclidean'

            /**#@-*/
        },

        /* special regular polygon options */
        regularpolygon: {
            /**#@+
             * @visprop
             */

            /**
             * If <tt>true</tt>, moving the mouse over inner points triggers hasPoint.
             * @see JXG.GeometryElement#hasPoint
             *
             * @name RegularPolygon#hasInnerPoints
             * @type Boolean
             * @default false
             */
            hasInnerPoints: false,
            fillColor: Color.palette.yellow,
            highlightFillColor: Color.palette.yellow,
            fillOpacity: 0.3,
            highlightFillOpacity: 0.2,

            /**
             * Is the polygon bordered by lines?
             *
             * @type Boolean
             * @name RegularPolygon#withLines
             * @default true
             */
            withLines: true,

            /**
             * Attributes for the polygon border lines.
             *
             * @type Line
             * @name RegularPolygon#borders
             */
            borders: {
                withLabel: false,
                strokeWidth: 1,
                highlightStrokeWidth: 1,
                // Polygon layer + 1
                layer: 5,
                label: {
                    position: 'top'
                }
            },

            /**
             * Attributes for the polygon vertices.
             *
             * @type Point
             * @name RegularPolygon#vertices
             */
            vertices: {
                layer: 9,
                withLabel: true,
                strokeColor: Color.palette.red,
                fillColor: Color.palette.red,
                fixed: false
            },

            /**
             * Attributes for the polygon label.
             *
             * @type Label
             * @name Polygon#label
             */
            label: {
                offset: [0, 0]
            }

            /**#@-*/
        },

        /* special options for riemann sums */
        riemannsum: {
            /**#@+
             * @visprop
             */

            withLabel: false,
            fillOpacity: 0.3,
            fillColor: Color.palette.yellow

            /**#@-*/
        },

        /* special sector options */
        sector: {
            /**#@+
             * @visprop
             */

            fillColor: Color.palette.yellow,
            highlightFillColor: Color.palette.yellow,
            // fillColor: '#00ff00',
            // highlightFillColor: '#00ff00',

            fillOpacity: 0.3,
            highlightFillOpacity: 0.3,
            highlightOnSector: false,
            highlightStrokeWidth: 0,

            /**
             * Type of sector. Possible values are 'minor', 'major', and 'auto'.
             *
             * @type String
             * @name Sector#selection
             * @default 'auto'
             */
            selection: 'auto',

            /**
             * Attributes for sub-element arc. It is only available, if the sector is defined by three points.
             *
             * @type Arc
             * @name Sector#arc
             * @default '{visible:false}'
             */
            arc: {
                visible: false,
                fillColor: 'none'
            },

            /**
             * Attributes for helper point radiuspoint in case it is provided by coordinates.
             *
             * @type Point
             * @name Sector#radiusPoint
             */
            radiusPoint: {
                visible: false,
                withLabel: false
            },

            /**
             * Attributes for helper point center in case it is provided by coordinates.
             *
             * @type Point
             * @name Sector#center
             */
            center: {
                visible: false,
                withLabel: false
            },

            /**
             * Attributes for helper point anglepoint in case it is provided by coordinates.
             *
             * @type Point
             * @name Sector#anglePoint
             */
            anglePoint: {
                visible: false,
                withLabel: false
            },

            /**
             * Attributes for the sector label.
             *
             * @type Label
             * @name Sector#label
             */
            label: {
                offset: [0, 0],
                anchorX: 'auto',
                anchorY: 'auto'
            }

            /**#@-*/
        },

        /* special segment options */
        segment: {
            /**#@+
             * @visprop
             */

            label: {
                position: 'top'
            }
            /**#@-*/
        },

        semicircle: {
            /**#@+
             * @visprop
             */

            /**
             * Attributes for center point of the semicircle.
             *
             * @type Point
             * @name Semicircle#center
             */
            center: {
                visible: false,
                withLabel: false,
                fixed: false,
                fillColor: Color.palette.red,
                strokeColor: Color.palette.red,
                highlightFillColor:'#eeeeee',
                highlightStrokeColor: Color.palette.red,
                name: ''
            }

            /**#@-*/
        },

        /* special slider options */
        slider: {
            /**#@+
             * @visprop
             */

            /**
             * The slider only returns integer multiples of this value, e.g. for discrete values set this property to <tt>1</tt>. For
             * continuous results set this to <tt>-1</tt>.
             *
             * @memberOf Slider.prototype
             * @name snapWidth
             * @type Number
             */
            snapWidth: -1,      // -1 = deactivated

            /**
             * The precision of the slider value displayed in the optional text.
             * Replaced by the attribute "digits".
             *
             * @memberOf Slider.prototype
             * @name precision
             * @type Number
             * @deprecated
             * @see Slider#digits
             * @default 2
             */
            precision: 2,

            /**
             * The number of digits of the slider value displayed in the optional text.
             *
             * @memberOf Slider.prototype
             * @name digits
             * @type Number
             * @default 2
             */
            digits: 2,

            firstArrow: false,
            lastArrow: false,

            /**
             * Show slider ticks.
             *
             * @type Boolean
             * @name Slider#withTicks
             * @default true
             */
            withTicks: true,

            /**
             * Show slider label.
             *
             * @type Boolean
             * @name Slider#withLabel
             * @default true
             */
            withLabel: true,

            /**
             * If not null, this replaces the part "name = " in the slider label.
             * Possible types: string, number or function.
             * @type String
             * @name suffixLabel
             * @memberOf Slider.prototype
             * @default null
             * @see JXG.Slider#unitLabel
             * @see JXG.Slider#postLabel
             */
            suffixLabel: null,

            /**
             * If not null, this is appended to the value in the slider label.
             * Possible types: string, number or function.
             * @type String
             * @name unitLabel
             * @memberOf Slider.prototype
             * @default null
             * @see JXG.Slider#suffixLabel
             * @see JXG.Slider#postLabel
             */
            unitLabel: null,

            /**
             * If not null, this is appended to the value and to unitLabel in the slider label.
             * Possible types: string, number or function.
             * @type String
             * @name postLabel
             * @memberOf Slider.prototype
             * @default null
             * @see JXG.Slider#suffixLabel
             * @see JXG.Slider#unitLabel
             */
            postLabel: null,

            layer: 9,
            showInfobox: false,
            name: '',
            visible: true,
            strokeColor: '#000000',
            highlightStrokeColor: '#888888',
            fillColor: '#ffffff',
            highlightFillColor: 'none',

            /**
             * Size of slider point.
             *
             * @type Number
             * @name Slider#size
             * @default 6
             * @see Point#size
             */
            size: 6,

            /**
             * Attributes for first (left) helper point defining the slider position.
             *
             * @type Point
             * @name Slider#point1
             */
            point1: {
                needsRegularUpdate: false,
                showInfobox: false,
                withLabel: false,
                visible: false,
                fixed: true,
                name: ''
            },

            /**
             * Attributes for second (right) helper point defining the slider position.
             *
             * @type Point
             * @name Slider#point2
             */
            point2: {
                needsRegularUpdate: false,
                showInfobox: false,
                withLabel: false,
                visible: false,
                fixed: true,
                name: ''
            },

            /**
             * Attributes for the base line of the slider.
             *
             * @type Line
             * @name Slider#baseline
             */
            baseline: {
                needsRegularUpdate: false,
                visible: 'inherit',
                fixed: true,
                scalable: false,
                tabindex: null,
                name: '',
                strokeWidth: 1,
                strokeColor: '#000000',
                highlightStrokeColor: '#888888'
            },

            /**
             * Attributes for the ticks of the base line of the slider.
             *
             * @type Ticks
             * @name Slider#ticks
             */
            ticks: {
                needsRegularUpdate: false,
                fixed: true,

                // Label drawing
                drawLabels: false,
                digits: 2,
                includeBoundaries: 1,
                drawZero: true,
                label: {
                    offset: [-4, -14],
                    display: 'internal'
                },

                minTicksDistance: 30,
                insertTicks: true,
                minorHeight: 4,         // if <0: full width and height
                majorHeight: 5,        // if <0: full width and height
                minorTicks: 0,
                defaultDistance: 1,
                strokeOpacity: 1,
                strokeWidth: 1,
                tickEndings: [0, 1],
                strokeColor: '#000000',
                visible: 'inherit'
            },

            /**
             * Attributes for the highlighting line of the slider.
             *
             * @type Line
             * @name Slider#highline
             */
            highline: {
                strokeWidth: 3,
                visible: 'inherit',
                fixed: true,
                tabindex: null,
                name: '',
                strokeColor: '#000000',
                highlightStrokeColor: '#888888'
            },

            /**
             * Attributes for the slider label.
             *
             * @type Label
             * @name Slider#label
             */
            label: {
                visible: 'inherit',
                strokeColor: '#000000'
            },

            /**
             * If true, 'up' events on the baseline will trigger slider moves.
             *
             * @type: Boolean
             * @name Slider#moveOnUp
             * @default: true
             */
            moveOnUp: true

            /**#@-*/
        },

        /* special options for comb */
        comb: {
            /**#@+
             * @visprop
             */

            /**
             * Frequency of comb elements.
             *
             * @type Number
             * @name Comb#frequency
             * @default 0.2
             */
            frequency: 0.2,

            /**
             * Width of the comb.
             *
             * @type Number
             * @name Comb#width
             * @default 0.4
             */
            width: 0.4,

            /**
             * Angle under which comb elements are positioned.
             *
             * @type Number
             * @name Comb#angle
             * @default 60 degrees
             */
            angle: Math.PI / 3,

            /**
             * Should the comb go right to left instead of left to right.
             *
             * @type Boolean
             * @name Comb#reverse
             * @default false
             */
            reverse: false,

            /**
             * Attributes for first defining point of the comb.
             *
             * @type Point
             * @name Comb#point1
             */
            point1: {
                visible: false,
                withLabel: false,
                fixed: false,
                name: ''
            },

            /**
             * Attributes for second defining point of the comb.
             *
             * @type Point
             * @name Comb#point2
             */
            point2: {
                visible: false,
                withLabel: false,
                fixed: false,
                name: ''
            },

            /**
             * Attributes for the curve displaying the comb.
             *
             * @type Curve
             * @name Comb#curve
             */
            curve: {
                strokeWidth: 1,
                strokeColor: '#000000',
                fillColor: 'none'
            }
        },

        /* special options for slope triangle */
        slopetriangle: {
            /**#@+
             * @visprop
             */

            fillColor: Color.palette.red,
            fillOpacity: 0.4,
            highlightFillColor: Color.palette.red,
            highlightFillOpacity: 0.3,

            borders: {
                lastArrow: {
                    type: 1,
                    size: 6
                }
            },

            /**
             * Attributes for the gliding helper point.
             *
             * @type Point
             * @name Slopetriangle#glider
             */
            glider: {
                fixed: true,
                visible: false,
                withLabel: false
            },

            /**
             * Attributes for the base line.
             *
             * @type Line
             * @name Slopetriangle#baseline
             */
            baseline: {
                visible: false,
                withLabel: false,
                name: ''
            },

            /**
             * Attributes for the base point.
             *
             * @type Point
             * @name Slopetriangle#basepoint
             */
            basepoint: {
                visible: false,
                withLabel: false,
                name: ''
            },

            /**
             * Attributes for the tangent.
             * The tangent is constructed by slop triangle if the construction
             * is based on a glider, solely.
             *
             * @type Line
             * @name Slopetriangle#tangent
             */
            tangent: {
                visible: false,
                withLabel: false,
                name: ''
            },

            /**
             * Attributes for the top point.
             *
             * @type Point
             * @name Slopetriangle#toppoint
             */
            toppoint: {
                visible: false,
                withLabel: false,
                name: ''
            },

            /**
             * Attributes for the slope triangle label.
             *
             * @type Label
             * @name Slopetriangle#label
             */
            label: {
                visible: true
            }
            /**#@-*/
        },

        /* special options for step functions */
        stepfunction: {
            /**#@+
             * @visprop
             */

            /**#@-*/
        },

        /* special tape measure options */
        tapemeasure: {
            /**#@+
             * @visprop
             */

            strokeColor: '#000000',
            strokeWidth: 2,
            highlightStrokeColor: '#000000',

            /**
             * Show tape measure ticks.
             *
             * @type Boolean
             * @name Tapemeasure#withTicks
             * @default true
             */
            withTicks: true,

            /**
             * Show tape measure label.
             *
             * @type Boolean
             * @name Tapemeasure#withLabel
             * @default true
             */
            withLabel: true,

            /**
             * The precision of the tape measure value displayed in the optional text.
             * Replaced by the attribute digits
             *
             * @memberOf Tapemeasure.prototype
             * @name precision
             * @type Number
             * @deprecated
             * @see Tapemeasure#digits
             * @default 2
             */
            precision: 2,

            /**
             * The precision of the tape measure value displayed in the optional text.
             * @memberOf Tapemeasure.prototype
             * @name precision
             * @type Number
             * @default 2
             */
            digits: 2,

            /**
             * Attributes for first helper point defining the tape measure position.
             *
             * @type Point
             * @name Tapemeasure#point1
             */
            point1: {
                visible: 'inherit',
                strokeColor: '#000000',
                fillColor: '#ffffff',
                fillOpacity: 0.0,
                highlightFillOpacity: 0.1,
                size: 6,
                snapToPoints: true,
                attractorUnit: 'screen',
                attractorDistance: 20,
                showInfobox: false,
                withLabel: false,
                name: ''
            },

            /**
             * Attributes for second helper point defining the tape measure position.
             *
             * @type Point
             * @name Tapemeasure#point2
             */
            point2: {
                visible: 'inherit',
                strokeColor: '#000000',
                fillColor: '#ffffff',
                fillOpacity: 0.0,
                highlightFillOpacity: 0.1,
                size: 6,
                snapToPoints: true,
                attractorUnit: 'screen',
                attractorDistance: 20,
                showInfobox: false,
                withLabel: false,
                name: ''
            },

            /**
             * Attributes for the ticks of the tape measure.
             *
             * @type Ticks
             * @name Tapemeasure#ticks
             */
            ticks: {
                drawLabels: false,
                drawZero: true,
                insertTicks: true,
                minorHeight: 8,
                majorHeight: 16,
                minorTicks: 4,
                tickEndings: [0, 1],
                defaultDistance: 0.1,
                strokeOpacity: 1,
                strokeWidth: 1,
                strokeColor: '#000000',
                visible: 'inherit'
            },

            /**
             * Attributes for the tape measure label.
             *
             * @type Label
             * @name Tapemeasure#label
             */
            label: {
                position: 'top'
            }
            /**#@-*/
        },

        /* special text options */
        text: {
            /**#@+
             * @visprop
             */

            /**
             * The font size in pixels.
             *
             * @name fontSize
             * @memberOf Text.prototype
             * @default 12
             * @type Number
             * @see Text#fontUnit
             */
            fontSize: 12,

            /**
             * CSS unit for the font size of a text element. Usually, this will be the default value 'px' but
             * for responsive application, also 'vw', 'vh', vmax', 'vmin' or 'rem' might be useful.
             *
             * @name fontUnit
             * @memberOf Text.prototype
             * @default 'px'
             * @type String
             * @see Text#fontSize
             *
             * @example
             * var txt = board.create('text', [2, 2, "hello"], {fontSize: 8, fontUnit: 'vmin'});
             *
             * </pre><div id="JXG2da7e972-ac62-416b-a94b-32559c9ec9f9" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG2da7e972-ac62-416b-a94b-32559c9ec9f9',
             *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
             *     var txt = board.create('text', [2, 2, "hello"], {fontSize: 8, fontUnit: 'vmin'});
             *
             *     })();
             *
             * </script><pre>
             *
             */
            fontUnit: 'px',

            /**
             * Used to round texts given by a number.
             *
             * @name digits
             * @memberOf Text.prototype
             * @default 2
             * @type Number
             */
            digits: 2,

            /**
             * If set to true, the text is parsed and evaluated.
             * For labels parse==true results in converting names of the form k_a to subscripts.
             * If the text is given by string and parse==true, the string is parsed as
             * JessieCode expression.
             *
             * @name parse
             * @memberOf Text.prototype
             * @default true
             * @type Boolean
             */
            parse: true,

            /**
             * If set to true and caja's sanitizeHTML function can be found it
             * will be used to sanitize text output.
             *
             * @name useCaja
             * @memberOf Text.prototype
             * @default false
             * @type Boolean
             */
            useCaja: false,

            /**
             * If enabled, the text will be handled as label. Intended for internal use.
             *
             * @name isLabel
             * @memberOf Text.prototype
             * @default false
             * @type Boolean
             */
            isLabel: false,

            strokeColor: '#000000',
            highlightStrokeColor: '#000000',
            highlightStrokeOpacity: 0.666666,

            /**
             * Default CSS properties of the HTML text element.
             * <p>
             * The CSS properties which are set here, are handed over to the style property
             * of the HTML text element. That means, they have higher property than any
             * CSS class.
             * <p>
             * If a property which is set here should be overruled by a CSS class
             * then this property should be removed here.
             * <p>
             * The reason, why this attribute should be kept to its default value at all,
             * is that screen dumps of SVG boards with <tt>board.renderer.dumpToCanvas()</tt>
             * will ignore the font-family if it is set in a CSS class.
             * It has to be set explicitly as style attribute.
             * <p>
             * In summary, the order of priorities from high to low is
             * <ol>
             *  <li> JXG.Options.text.cssStyle
             *  <li> JXG.Options.text.cssDefaultStyle
             *  <li> JXG.Options.text.cssClass
             * </ol>
             * @example
             * If all texts should get its font-family from the default CSS class
             * before initializing the board
             * <pre>
             *   JXG.Options.text.cssDefaultStyle = '';
             *   JXG.Options.text.highlightCssDefaultStyle = '';
             * </pre>
             * should be called.
             *
             * @name cssDefaultStyle
             * @memberOf Text.prototype
             * @default  'font-family: Arial, Helvetica, Geneva, sans-serif;'
             * @type String
             * @see Text#highlightCssDefaultStyle
             * @see Text#cssStyle
             * @see Text#highlightCssStyle
             */
            cssDefaultStyle: 'font-family: Arial, Helvetica, Geneva, sans-serif;',

            /**
             * Default CSS properties of the HTML text element in case of highlighting.
             * <p>
             * The CSS properties which are set here, are handed over to the style property
             * of the HTML text element. That means, they have higher property than any
             * CSS class.
             * @example
             * If all texts should get its font-family from the default CSS class
             * before initializing the board
             * <pre>
             *   JXG.Options.text.cssDefaultStyle = '';
             *   JXG.Options.text.highlightCssDefaultStyle = '';
             * </pre>
             * should be called.
             *
             * @name highlightCssDefaultStyle
             * @memberOf Text.prototype
             * @default  'font-family: Arial, Helvetica, Geneva, sans-serif;'
             * @type String
             * @see Text#cssDefaultStyle
             * @see Text#cssStyle
             * @see Text#highlightCssStyle
            */
            highlightCssDefaultStyle: 'font-family: Arial, Helvetica, Geneva, sans-serif;',

            /**
             * CSS properties of the HTML text element.
             * <p>
             * The CSS properties which are set here, are handed over to the style property
             * of the HTML text element. That means, they have higher property than any
             * CSS class.
             *
             * @name cssStyle
             * @memberOf Text.prototype
             * @default  ''
             * @type String
             * @see Text#cssDefaultStyle
             * @see Text#highlightCssDefaultStyle
             * @see Text#highlightCssStyle
            */
            cssStyle: '',

            /**
             * CSS properties of the HTML text element in case of highlighting.
             * <p>
             * The CSS properties which are set here, are handed over to the style property
             * of the HTML text element. That means, they have higher property than any
             * CSS class.
             *
             * @name highlightCssStyle
             * @memberOf Text.prototype
             * @default  ''
             * @type String
             * @see Text#cssDefaultStyle
             * @see Text#highlightCssDefaultStyle
             * @see Text#cssStyle
            */
            highlightCssStyle: '',

            /**
             * If true, the input will be given to ASCIIMathML before rendering.
             *
             * @name useASCIIMathML
             * @memberOf Text.prototype
             * @default false
             * @type Boolean
             */
            useASCIIMathML: false,

            /**
             * If true, MathJax will be used to render the input string.
             * Supports MathJax 2 as well as Mathjax 3.
             * It is recommended to use this option together with the option
             * "parse: false". Otherwise, 4 backslashes (e.g. \\\\alpha) are needed
             * instead of two (e.g. \\alpha).
             *
             * @name useMathJax
             * @memberOf Text.prototype
             * @default false
             * @type Boolean
             * @see Text#parse
             *
             * @example
             *  // Before loading MathJax, it has to be configured something like this:
             * window.MathJax = {
             *   tex: {
             *     inlineMath: [ ['$','$'], ["\\(","\\)"] ],
             *     displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
             *     packages: ['base', 'ams']
             *   },
             *   options: {
             *     ignoreHtmlClass: 'tex2jax_ignore',
             *     processHtmlClass: 'tex2jax_process'
             *   }
             * };
             *
             * // Display style
             * board.create('text',[ 2,2,  function(){return '$$X=\\frac{2}{x}$$'}], {
             *     fontSize: 15, color:'green', useMathJax: true});
             *
             * // Inline style
             * board.create('text',[-2,2,  function(){return '$X_A=\\frac{2}{x}$'}], {
             *     fontSize: 15, color:'green', useMathJax: true});
             *
             * var A = board.create('point', [-2, 0]);
             * var B = board.create('point', [1, 0]);
             * var C = board.create('point', [0, 1]);
             *
             * var graph = board.create('ellipse', [A, B, C], {
             *         fixed: true,
             *         withLabel: true,
             *         strokeColor: 'black',
             *         strokeWidth: 2,
             *         fillColor: '#cccccc',
             *         fillOpacity: 0.3,
             *         highlightStrokeColor: 'red',
             *         highlightStrokeWidth: 3,
             *         name: '$1=\\frac{(x-h)^2}{a^2}+\\frac{(y-k)^2}{b^2}$',
             *         label: {useMathJax: true}
             *     });
             *
             * var nvect1 = board.create('text', [-4, -3, '\\[\\overrightarrow{V}\\]'],
             * {
             *   fontSize: 24, parse: false
             * });
             * var nvect1 = board.create('text', [-2, -4, function() {return '$\\overrightarrow{G}$';}],
             * {
             *   fontSize: 24, useMathJax: true
             * });
             *
             * </pre>
             * <script>
             * window.MathJax = {
             *   tex: {
             *     inlineMath: [ ['$','$'], ["\\(","\\)"] ],
             *     displayMath: [ ['$$','$$'], ["\\[","\\]"] ],
             *     packages: ['base', 'ams']
             *   },
             *   options: {
             *     ignoreHtmlClass: 'tex2jax_ignore',
             *     processHtmlClass: 'tex2jax_process'
             *   }
             * };
             * </script>
             * <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script"></script>
             * <div id="JXGe2a04876-5813-4db0-b7e8-e48bf4e220b9" class="jxgbox" style="width: 400px; height: 400px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXGe2a04876-5813-4db0-b7e8-e48bf4e220b9',
             *             {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false});
             *     // Display style
             *     board.create('text',[ 2,2,  function(){return '$$X=\\frac{2}{x}$$'}], {
             *         fontSize: 15, color:'green', useMathJax: true});
             *
             *     // Inline style
             *     board.create('text',[-2,2,  function(){return '$X_A=\\frac{2}{x}$'}], {
             *         fontSize: 15, color:'green', useMathJax: true});
             *
             *     var A = board.create('point', [-2, 0]);
             *     var B = board.create('point', [1, 0]);
             *     var C = board.create('point', [0, 1]);
             *
             *     var graph = board.create('ellipse', [A, B, C], {
             *             fixed: true,
             *             withLabel: true,
             *             strokeColor: 'black',
             *             strokeWidth: 2,
             *             fillColor: '#cccccc',
             *             fillOpacity: 0.3,
             *             highlightStrokeColor: 'red',
             *             highlightStrokeWidth: 3,
             *             name: '$1=\\frac{(x-h)^2}{a^2}+\\frac{(y-k)^2}{b^2}$',
             *             label: {useMathJax: true}
             *         });
             *
             *     var nvect1 = board.create('text', [-4, -3, '\\[\\overrightarrow{V}\\]'],
             *     {
             *       fontSize: 24, parse: false
             *     });
             *     var nvect1 = board.create('text', [-2, -4, function() {return '$\\overrightarrow{G}$';}],
             *     {
             *       fontSize: 24, useMathJax: true
             *     });
             *     })();
             *
             * </script><pre>
             *
             *
             * @example
             * // Load MathJax:
             * // &lt;script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"&lt;&lt;/script&gt;
             *
             * // function and its derivative
             * var f1 = function(x) { return x * x * x; },
             * graph1 = board.create('functiongraph', [f1, -0.1, 1.1]),
             *
             * A = board.create('glider', [0.5, f1(0.5), graph1], {
             *             name: 'f(x)',
             *             color: 'black',
             *             face:'x',
             *             fixed: true,
             *             size: 3,
             *             label: {offset: [-30, 10], fontSize: 15}
             *         }),
             * B = board.create('glider', [0.7, f1(0.7), graph1], {
             *             name: 'f(x+&Delta;x)',
             *             size: 3,
             *             label: {offset: [-60, 10], fontSize: 15}
             *         }),
             *
             * secant_line = board.create('line', [A,B],{dash: 1, color: 'green'}),
             * a_h_segment = board.create('segment', [A, [
             *                     function(){ return B.X() > A.X() ? B.X() : A.X()},
             *                     function(){ return B.X() > A.X() ? A.Y() : B.Y()}
             *                 ]],{ name: '&Delta;x', dash: 1, color: 'black'});
             *
             * b_v_segment = board.create('segment', [B, [
             *                     function(){ return B.X() > A.X() ? B.X() : A.X()},
             *                     function(){ return B.X() > A.X() ? A.Y() : B.Y()}
             *                 ]],{ name: '&Delta;y', dash: 1, color: 'black'}),
             *
             * ma = board.create('midpoint', [a_h_segment.point1, a_h_segment.point2
             *     ], {visible: false});
             *
             * board.create('text', [0, 0, function() {return '\\[\\Delta_x='+(B.X()-A.X()).toFixed(4)+'\\]'}], {
             *     anchor: ma, useMathJax: true, fixed: true, color: 'green', anchorY: 'top'
             * });
             *
             * mb = board.create('midpoint', [b_v_segment.point1, b_v_segment.point2], {visible: false});
             * board.create('text', [0, 0, function() {return '\\[\\Delta_y='+(B.Y()-A.Y()).toFixed(4)+'\\]'}], {
             *     anchor: mb, useMathJax: true, fixed: true, color: 'green'
             * });
             *
             * dval = board.create('text',[0.1, 0.8,
             *     function(){
             *         return '\\[\\frac{\\Delta_y}{\\Delta_x}=\\frac{' + ((B.Y()-A.Y()).toFixed(4)) + '}{' + ((B.X()-A.X()).toFixed(4)) +
             *             '}=' + (((B.Y()-A.Y()).toFixed(4))/((B.X()-A.X()).toFixed(4))).toFixed(4) + '\\]';
             *     }],{fontSize: 15, useMathJax: true});
             *
             * </pre>
             * <script src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js" id="MathJax-script"></script>
             * <div id="JXG8c2b65e7-4fc4-43f7-b23c-5076a7fa9621" class="jxgbox" style="width: 400px; height: 400px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG8c2b65e7-4fc4-43f7-b23c-5076a7fa9621',
             *             {boundingbox: [-0.1, 1.1, 1.1, -0.1], axis: true, showcopyright: false, shownavigation: false});
             *     // function and its derivative
             *     var f1 = function(x) { return x * x * x; },
             *     graph1 = board.create('functiongraph', [f1, -0.1, 1.1]),
             *
             *     A = board.create('glider', [0.5, f1(0.5), graph1], {
             *                 name: 'f(x)',
             *                 color: 'black',
             *                 face:'x',
             *                 fixed: true,
             *                 size: 3,
             *                 label: {offset: [-30, 10], fontSize: 15}
             *             }),
             *     B = board.create('glider', [0.7, f1(0.7), graph1], {
             *                 name: 'f(x+&Delta;x)',
             *                 size: 3,
             *                 label: {offset: [-60, 10], fontSize: 15}
             *             }),
             *
             *     secant_line = board.create('line', [A,B],{dash: 1, color: 'green'}),
             *     a_h_segment = board.create('segment', [A, [
             *                         function(){ return B.X() > A.X() ? B.X() : A.X()},
             *                         function(){ return B.X() > A.X() ? A.Y() : B.Y()}
             *                     ]],{ name: '&Delta;x', dash: 1, color: 'black'});
             *
             *     b_v_segment = board.create('segment', [B, [
             *                         function(){ return B.X() > A.X() ? B.X() : A.X()},
             *                         function(){ return B.X() > A.X() ? A.Y() : B.Y()}
             *                     ]],{ name: '&Delta;y', dash: 1, color: 'black'}),
             *
             *     ma = board.create('midpoint', [a_h_segment.point1, a_h_segment.point2
             *         ], {visible: false});
             *
             *     board.create('text', [0, 0, function() {return '\\[\\Delta_x='+(B.X()-A.X()).toFixed(4)+'\\]'}], {
             *         anchor: ma, useMathJax: true, fixed: true, color: 'green', anchorY: 'top'
             *     });
             *
             *     mb = board.create('midpoint', [b_v_segment.point1, b_v_segment.point2], {visible: false});
             *     board.create('text', [0, 0, function() {return '\\[\\Delta_y='+(B.Y()-A.Y()).toFixed(4)+'\\]'}], {
             *         anchor: mb, useMathJax: true, fixed: true, color: 'green'
             *     });
             *
             *     dval = board.create('text',[0.1, 0.8,
             *         function(){
             *             return '\\[\\frac{\\Delta_y}{\\Delta_x}=\\frac{' + ((B.Y()-A.Y()).toFixed(4)) + '}{' + ((B.X()-A.X()).toFixed(4)) +
             *                 '}=' + (((B.Y()-A.Y()).toFixed(4))/((B.X()-A.X()).toFixed(4))).toFixed(4) + '\\]';
             *         }],{fontSize: 15, useMathJax: true});
             *
             *     })();
             *
             * </script><pre>
             *
             * @example
             * var board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-1, 10, 11, -2], axis: true});
             * board.options.text.useMathjax = true;
             *
             * a = board.create('slider',[[-0.7,1.5],[5,1.5],[0,0.5,1]], {
             *     suffixlabel:'\\(t_1=\\)',
             *     unitLabel: ' \\(\\text{ ms}\\)',
             *     snapWidth:0.01}),
             *
             * func = board.create('functiongraph',[function(x){return (a.Value()*x*x)}], {strokeColor: "red"});
             * text1 = board.create('text', [5, 1, function(){
             *             return '\\(a(t)= { 1 \\over ' + a.Value().toFixed(3) + '}\\)';
             *         }], {fontSize: 15, fixed:true, strokeColor:'red', anchorY: 'top', parse: false});
             *
             * </pre><div id="JXGf8bd01db-fb6a-4a5c-9e7f-8823f7aa5ac6" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXGf8bd01db-fb6a-4a5c-9e7f-8823f7aa5ac6',
             *             {boundingbox: [-1, 10, 11, -2], axis: true, showcopyright: false, shownavigation: false});
             *     board.options.text.useMathjax = true;
             *
             *     a = board.create('slider',[[-0.7,1.5],[5,1.5],[0,0.5,1]], {
             *         suffixlabel:'\\(t_1=\\)',
             *         unitLabel: ' \\(\\text{ ms}\\)',
             *         snapWidth:0.01}),
             *
             *     func = board.create('functiongraph',[function(x){return (a.Value()*x*x)}], {strokeColor: "red"});
             *     text1 = board.create('text', [5, 1, function(){
             *                 return '\\(a(t)= { 1 \\over ' + a.Value().toFixed(3) + '}\\)';
             *             }], {fontSize: 15, fixed:true, strokeColor:'red', anchorY: 'top', parse: false});
             *
             *     })();
             *
             * </script><pre>
             *
             */
            useMathJax: false,

            /**
             *
             * If true, KaTeX will be used to render the input string.
             * For this feature, katex.min.js and katex.min.css have to be included.
             * <p>
             * The example below does not work, because there is a conflict with
             * the MathJax library which is used below.
             * </p>
             *
             * @name useKatex
             * @memberOf Text.prototype
             * @default false
             * @type Boolean
             *
             *
             * @example
             * JXG.Options.text.useKatex = true;
             *
             * const board = JXG.JSXGraph.initBoard('jxgbox', {
             *     boundingbox: [-2, 5, 8, -5], axis:true
             * });
             *
             * var a = board.create('slider',[[-0.7,1.5],[5,1.5],[0,0.5,1]], {
             *     suffixlabel:'t_1=',
             *     unitLabel: ' \\text{ ms}',
             *     snapWidth:0.01});
             *
             * func = board.create('functiongraph',[function(x){return (a.Value()*x*x)}], {strokeColor: "red"});
             * text1 = board.create('text', [5, 1, function(){
             *             return 'a(t)= { 1 \\over ' + a.Value().toFixed(3) + '}';
             *         }], {fontSize: 15, fixed:true, strokeColor:'red', anchorY: 'top'});
             *
             * </pre>
             * <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.13.10/dist/katex.min.css" integrity="sha384-0cCFrwW/0bAk1Z/6IMgIyNU3kfTcNirlObr4WjrUU7+hZeD6ravdYJ3kPWSeC31M" crossorigin="anonymous">
             * <script src="https://cdn.jsdelivr.net/npm/katex@0.13.10/dist/katex.min.js" integrity="sha384-dtFDxK2tSkECx/6302Z4VN2ZRqt6Gis+b1IwCjJPrn0kMYFQT9rbtyQWg5NFWAF7" crossorigin="anonymous"></script>
             * <div id="JXG497f065c-cfc1-44c3-ba21-5fa581668869" class="jxgbox" style="width: 300px; height: 300px;"></div>
             * <script type="text/javascript">
             *     (function() {
             *         var board = JXG.JSXGraph.initBoard('JXG497f065c-cfc1-44c3-ba21-5fa581668869',
             *             {boundingbox: [-2, 5, 8, -5], axis: true, showcopyright: false, shownavigation: false});
             *     board.options.useKatex = true;
             *     var a = board.create('slider',[[-0.7,1.5],[5,1.5],[0,0.5,1]], {
             *         suffixlabel:'t_1=',
             *         unitLabel: ' \\text{ ms}',
             *         snapWidth:0.01});
             *
             *     func = board.create('functiongraph',[function(x){return (a.Value()*x*x)}], {strokeColor: "red"});
             *     text1 = board.create('text', [5, 1, function(){
             *                 return 'a(t)= { 1 \\over ' + a.Value().toFixed(3) + '}';
             *             }], {fontSize: 15, fixed:true, strokeColor:'red', anchorY: 'top'});
             *
             *     })();
             *
             * </script><pre>
             */
            useKatex: false,

            /**
             * Determines the rendering method of the text. Possible values
             * include <tt>'html'</tt> and <tt>'internal</tt>.
             *
             * @name display
             * @memberOf Text.prototype
             * @default 'html'
             * @type String
             */
            display: 'html',

            /**
             * Anchor element {@link Point}, {@link Text} or {@link Image} of the text. If it exists, the coordinates of the text are relative
             * to this anchor element.
             *
             * @name anchor
             * @memberOf Text.prototype
             * @default null
             * @type Object
             */
            anchor: null,

            /**
             * The horizontal alignment of the text. Possible values include <tt>'auto</tt>, <tt>'left'</tt>, <tt>'middle'</tt>, and
             * <tt>'right'</tt>.
             *
             * @name anchorX
             * @memberOf Text.prototype
             * @default 'left'
             * @type String
             */
            anchorX: 'left',

            /**
             * The vertical alignment of the text. Possible values include <tt>'auto</tt>, <tt>'top'</tt>, <tt>'middle'</tt>, and
             * <tt>'bottom'</tt>.
             *
             * @name anchorY
             * @memberOf Text.prototype
             * @default 'auto'
             * @type String
             */
            anchorY: 'middle',

            /**
             * CSS class of the text in non-highlighted view.
             *
             * @name cssClass
             * @memberOf Text.prototype
             * @type String
             */
            cssClass: 'JXGtext',

            /**
             * CSS class of the text in highlighted view.
             *
             * @name highlightCssClass
             * @memberOf Text.prototype
             * @type String
             */
            highlightCssClass: 'JXGtext',

            /**
             * Sensitive area for dragging the text.
             * Possible values are 'all', or something else.
             * If set to 'small', a sensitivity margin at the right and left border is taken.
             * This may be extended to left, right, ... in the future.
             *
             * @name Text#dragArea
             * @type String
             * @default 'all'
             */
            dragArea: 'all',

            withLabel: false,

            /**
             * Text rotation in degrees.
             * Works for non-zero values only in combination with display=='internal'.
             *
             * @name Text#rotate
             * @type Number
             * @default 0
             */
            rotate: 0,

            visible: true,

            /**
             * Defines together with {@link Text#snapSizeY} the grid the text snaps on to.
             * The text will only snap on integer multiples to snapSizeX in x and snapSizeY in y direction.
             * If this value is equal to or less than <tt>0</tt>, it will use the grid displayed by the major ticks
             * of the default ticks of the default x axes of the board.
             *
             * @name snapSizeX
             * @memberOf Text.prototype
             *
             * @see Point#snapToGrid
             * @see Text#snapSizeY
             * @see JXG.Board#defaultAxes
             * @type Number
             * @default 1
             */
            snapSizeX: 1,

            /**
             * Defines together with {@link Text#snapSizeX} the grid the text snaps on to.
             * The text will only snap on integer multiples to snapSizeX in x and snapSizeY in y direction.
             * If this value is equal to or less than <tt>0</tt>, it will use the grid displayed by the major ticks
             * of the default ticks of the default y axes of the board.
             *
             * @name snapSizeY
             * @memberOf Text.prototype
             *
             * @see Point#snapToGrid
             * @see Text#snapSizeX
             * @see JXG.Board#defaultAxes
             * @type Number
             * @default 1
             */
            snapSizeY: 1,

            /**
             * List of attractor elements. If the distance of the text is less than
             * attractorDistance the text is made to glider of this element.
             *
             * @name attractors
             * @memberOf Text.prototype
             * @type Array
             * @default empty
             */
            attractors: []

            /**#@-*/
        },

        /* special options for trace curves */
        tracecurve: {
            /**#@+
             * @visprop
             */
            strokeColor: '#000000',
            fillColor: 'none',

            /**
             * The number of evaluated data points.
             * @memberOf Tracecurve.prototype
             * @default 100
             * @name numberPoints
             * @type Number
             */
            numberPoints: 100

            /**#@-*/
        },

        /*special turtle options */
        turtle: {
            /**#@+
             * @visprop
             */

            strokeWidth: 1,
            fillColor: 'none',
            strokeColor: '#000000',

            /**
             * Attributes for the turtle arrow.
             *
             * @type Curve
             * @name Turtle#arrow
             */
            arrow: {
                strokeWidth: 2,
                withLabel: false,
                strokeColor: Color.palette.red,
                lastArrow: true
            }
            /**#@-*/
        },

        /**
         * Abbreviations of attributes. Setting the shortcut means setting abbreviated properties
         * to the same value.
         * It is used in {@link JXG.GeometryElement#setAttribute} and in
         * the constructor {@link JXG.GeometryElement}.
         * Attention: In Options.js abbreviations are not allowed.
         * @type Object
         * @name JXG.Options#shortcuts
         *
         */
        shortcuts: {
            color: ['strokeColor', 'fillColor'],
            opacity: ['strokeOpacity', 'fillOpacity'],
            highlightColor: ['highlightStrokeColor', 'highlightFillColor'],
            highlightOpacity: ['highlightStrokeOpacity', 'highlightFillOpacity'],
            strokeWidth: ['strokeWidth', 'highlightStrokeWidth']
        }
    };

    /**
     * Holds all possible properties and the according validators for geometry elements.
     * A validator is either a function
     * which takes one parameter and returns true, if the value is valid for the property,
     * or it is false if no validator is required.
     */
    JXG.Validator = (function () {
        var i,
            validatePixel = function (v) {
                return (/^[0-9]+px$/).test(v);
            },
            validateDisplay = function (v) {
                return (v  === 'html' || v === 'internal');
            },
            validateColor = function (v) {
                // for now this should do it...
                return Type.isString(v);
            },
            validatePointFace = function (v) {
                return Type.exists(JXG.normalizePointFace(v));
            },
            validateInteger = function (v) {
                return (Math.abs(v - Math.round(v)) < Mat.eps);
            },
            validateNotNegativeInteger = function (v) {
                return validateInteger(v) && v >= 0;
            },
            validatePositiveInteger = function (v) {
                return validateInteger(v) && v > 0;
            },
            validateScreenCoords = function (v) {
                return v.length >= 2 && validateInteger(v[0]) && validateInteger(v[1]);
            },
            validateRenderer = function (v) {
                return (v === 'vml' || v === 'svg' || v === 'canvas' || v === 'no');
            },
            validatePositive = function (v) {
                return v > 0;
            },
            validateNotNegative = function (v) {
                return v >= 0;
            },
            v = {},
            validators = {
                attractorDistance: validateNotNegative,
                color: validateColor,
                defaultDistance: Type.isNumber,
                display: validateDisplay,
                doAdvancedPlot: false,
                draft: false,
                drawLabels: false,
                drawZero: false,
                face: validatePointFace,
                factor: Type.isNumber,
                fillColor: validateColor,
                fillOpacity: Type.isNumber,
                firstArrow: false,
                fontSize: validateInteger,
                dash: validateInteger,
                gridX: Type.isNumber,
                gridY: Type.isNumber,
                hasGrid: false,
                highlightFillColor: validateColor,
                highlightFillOpacity: Type.isNumber,
                highlightStrokeColor: validateColor,
                highlightStrokeOpacity: Type.isNumber,
                insertTicks: false,
                //: validateScreenCoords,
                lastArrow: false,
                layer: validateNotNegativeInteger,
                majorHeight: validateInteger,
                minorHeight: validateInteger,
                minorTicks: validateNotNegative,
                minTicksDistance: validatePositiveInteger,
                numberPointsHigh: validatePositiveInteger,
                numberPointsLow: validatePositiveInteger,
                opacity: Type.isNumber,
                radius: Type.isNumber,
                RDPsmoothing: false,
                renderer: validateRenderer,
                right: validatePixel,
                showCopyright: false,
                showInfobox: false,
                showNavigation: false,
                size: validateNotNegative, //validateInteger,
                snapSizeX: validatePositive,
                snapSizeY: validatePositive,
                snapWidth: Type.isNumber,
                snapToGrid: false,
                snatchDistance: validateNotNegative,
                straightFirst: false,
                straightLast: false,
                stretch: false,
                strokeColor: validateColor,
                strokeOpacity: Type.isNumber,
                strokeWidth: validateNotNegative, //validateInteger,
                takeFirst: false,
                takeSizeFromFile: false,
                to10: false,
                toOrigin: false,
                translateTo10: false,
                translateToOrigin: false,
                useASCIIMathML: false,
                useDirection: false,
                useMathJax: false,
                withLabel: false,
                withTicks: false,
                zoom: false
            };

        // this seems like a redundant step but it makes sure that
        // all properties in the validator object have lower case names
        // and the validator object is easier to read.
        for (i in validators) {
            if (validators.hasOwnProperty(i)) {
                v[i.toLowerCase()] = validators[i];
            }
        }

        return v;
    }());

    /**
     * All point faces can be defined with more than one name, e.g. a cross faced point can be given
     * by face equal to 'cross' or equal to 'x'. This method maps all possible values to fixed ones to
     * simplify if- and switch-clauses regarding point faces. The translation table is as follows:
     * <table>
     * <tr><th>Input</th><th>Output</th></tr>
     * <tr><td>cross, x</td><td>x</td></tr>
     * <tr><td>circle, o</td><td>o</td></tr>
     * <tr><td>square, []</td><td>[]</td></tr>
     * <tr><td>plus, +</td><td>+</td></tr>
     * <tr><td>diamond, &lt;&gt;</td><td>&lt;&gt;</td></tr>
     * <tr><td>triangleup, a, ^</td><td>A</td></tr>
     * <tr><td>triangledown, v</td><td>v</td></tr>
     * <tr><td>triangleleft, &lt;</td><td>&lt;</td></tr>
     * <tr><td>triangleright, &gt;</td><td>&gt;</td></tr>
     * </table>
     * @param {String} s A string which should determine a valid point face.
     * @returns {String} Returns a normalized string or undefined if the given string is not a valid
     * point face.
     */
    JXG.normalizePointFace = function (s) {
        var map = {
            cross: 'x',
            x: 'x',
            circle: 'o',
            o: 'o',
            square: '[]',
            '[]': '[]',
            plus: '+',
            '+': '+',
            diamond: '<>',
            '<>': '<>',
            triangleup: '^',
            a: '^',
            '^': '^',
            triangledown: 'v',
            v: 'v',
            triangleleft: '<',
            '<': '<',
            triangleright: '>',
            '>': '>'
        };

        return map[s];
    };


    /**
     * Apply the options stored in this object to all objects on the given board.
     * @param {JXG.Board} board The board to which objects the options will be applied.
     */
    JXG.useStandardOptions = function (board) {
        var el, t, p, copyProps,
            o = JXG.Options,
            boardHadGrid = board.hasGrid;

        board.options.grid.hasGrid = o.grid.hasGrid;
        board.options.grid.gridX = o.grid.gridX;
        board.options.grid.gridY = o.grid.gridY;
        board.options.grid.gridColor = o.grid.gridColor;
        board.options.grid.gridOpacity = o.grid.gridOpacity;
        board.options.grid.gridDash = o.grid.gridDash;
        board.options.grid.snapToGrid = o.grid.snapToGrid;
        board.options.grid.snapSizeX = o.grid.SnapSizeX;
        board.options.grid.snapSizeY = o.grid.SnapSizeY;
        board.takeSizeFromFile = o.takeSizeFromFile;

        copyProps = function (p, o) {
            p.visProp.fillcolor = o.fillColor;
            p.visProp.highlightfillcolor = o.highlightFillColor;
            p.visProp.strokecolor = o.strokeColor;
            p.visProp.highlightstrokecolor = o.highlightStrokeColor;
        };

        for (el in board.objects) {
            if (board.objects.hasOwnProperty(el)) {
                p = board.objects[el];
                if (p.elementClass === Const.OBJECT_CLASS_POINT) {
                    copyProps(p, o.point);
                } else if (p.elementClass === Const.OBJECT_CLASS_LINE) {
                    copyProps(p, o.line);

                    for (t = 0; t < p.ticks.length; t++) {
                        p.ticks[t].majorTicks = o.line.ticks.majorTicks;
                        p.ticks[t].minTicksDistance = o.line.ticks.minTicksDistance;
                        p.ticks[t].visProp.minorheight = o.line.ticks.minorHeight;
                        p.ticks[t].visProp.majorheight = o.line.ticks.majorHeight;
                    }
                } else if (p.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                    copyProps(p, o.circle);
                } else if (p.type === Const.OBJECT_TYPE_ANGLE) {
                    copyProps(p, o.angle);
                } else if (p.type === Const.OBJECT_TYPE_ARC) {
                    copyProps(p, o.arc);
                } else if (p.type === Const.OBJECT_TYPE_POLYGON) {
                    copyProps(p, o.polygon);
                } else if (p.type === Const.OBJECT_TYPE_CONIC) {
                    copyProps(p, o.conic);
                } else if (p.type === Const.OBJECT_TYPE_CURVE) {
                    copyProps(p, o.curve);
                } else if (p.type === Const.OBJECT_TYPE_SECTOR) {
                    p.arc.visProp.fillcolor = o.sector.fillColor;
                    p.arc.visProp.highlightfillcolor = o.sector.highlightFillColor;
                    p.arc.visProp.fillopacity = o.sector.fillOpacity;
                    p.arc.visProp.highlightfillopacity = o.sector.highlightFillOpacity;
                }
            }
        }

        board.fullUpdate();
        if (boardHadGrid && !board.hasGrid) {
            board.removeGrids(board);
        } else if (!boardHadGrid && board.hasGrid) {
            board.create('grid', []);
        }
    };

    /**
     * Converts all color values to greyscale and calls useStandardOption to put them onto the board.
     * @param {JXG.Board} board The board to which objects the options will be applied.
     * @see #useStandardOptions
     */
    JXG.useBlackWhiteOptions = function (board) {
        var o = JXG.Options;
        o.point.fillColor = Color.rgb2bw(o.point.fillColor);
        o.point.highlightFillColor = Color.rgb2bw(o.point.highlightFillColor);
        o.point.strokeColor = Color.rgb2bw(o.point.strokeColor);
        o.point.highlightStrokeColor = Color.rgb2bw(o.point.highlightStrokeColor);

        o.line.fillColor = Color.rgb2bw(o.line.fillColor);
        o.line.highlightFillColor = Color.rgb2bw(o.line.highlightFillColor);
        o.line.strokeColor = Color.rgb2bw(o.line.strokeColor);
        o.line.highlightStrokeColor = Color.rgb2bw(o.line.highlightStrokeColor);

        o.circle.fillColor = Color.rgb2bw(o.circle.fillColor);
        o.circle.highlightFillColor = Color.rgb2bw(o.circle.highlightFillColor);
        o.circle.strokeColor = Color.rgb2bw(o.circle.strokeColor);
        o.circle.highlightStrokeColor = Color.rgb2bw(o.circle.highlightStrokeColor);

        o.arc.fillColor = Color.rgb2bw(o.arc.fillColor);
        o.arc.highlightFillColor = Color.rgb2bw(o.arc.highlightFillColor);
        o.arc.strokeColor = Color.rgb2bw(o.arc.strokeColor);
        o.arc.highlightStrokeColor = Color.rgb2bw(o.arc.highlightStrokeColor);

        o.polygon.fillColor = Color.rgb2bw(o.polygon.fillColor);
        o.polygon.highlightFillColor  = Color.rgb2bw(o.polygon.highlightFillColor);

        o.sector.fillColor = Color.rgb2bw(o.sector.fillColor);
        o.sector.highlightFillColor  = Color.rgb2bw(o.sector.highlightFillColor);

        o.curve.strokeColor = Color.rgb2bw(o.curve.strokeColor);
        o.grid.gridColor = Color.rgb2bw(o.grid.gridColor);

        JXG.useStandardOptions(board);
    };

    // needs to be exported
    JXG.Options.normalizePointFace = JXG.normalizePointFace;

    return JXG.Options;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true, window: true */

/*
    nomen:    Allow underscores to indicate private class members. Might be replaced by local variables.
    plusplus: Only allowed in for-loops
    newcap:   AsciiMathMl exposes non-constructor functions beginning with upper case letters
*/
/*jslint nomen: true, plusplus: true, newcap: true, unparam: true*/
/*eslint no-unused-vars: "off"*/

/* depends:
 jxg
 options
 base/coords
 base/constants
 math/math
 math/geometry
 utils/type
 utils/env
*/

/**
 * @fileoverview JSXGraph can use various technologies to render the contents of a construction, e.g.
 * SVG, VML, and HTML5 Canvas. To accomplish this, The rendering and the logic and control mechanisms
 * are completely separated from each other. Every rendering technology has it's own class, called
 * Renderer, e.g. SVGRenderer for SVG, the same for VML and Canvas. The common base for all available
 * renderers is the class AbstractRenderer defined in this file.
 */

define('renderer/abstract',[
    'jxg', 'options', 'base/coords', 'base/constants', 'math/math', 'math/geometry', 'utils/type', 'utils/env'
], function (JXG, Options, Coords, Const, Mat, Geometry, Type, Env) {

    "use strict";

    /**
     * <p>This class defines the interface to the graphics part of JSXGraph. This class is an abstract class, it
     * actually does not render anything. This is up to the {@link JXG.SVGRenderer}, {@link JXG.VMLRenderer},
     * and {@link JXG.CanvasRenderer} classes. We strongly discourage you from using the methods in these classes
     * directly. Only the methods which are defined in this class and are not marked as private are guaranteed
     * to exist in any renderer instance you can access via {@link JXG.Board#renderer}. But not all methods may
     * work as expected.</p>
     * <p>The methods of this renderer can be divided into different categories:
     * <dl>
     *     <dt>Draw basic elements</dt>
     *     <dd>In this category we find methods to draw basic elements like {@link JXG.Point}, {@link JXG.Line},
     *     and {@link JXG.Curve} as well as assisting methods tightly bound to these basic painters. You do not
     *     need to implement these methods in a descendant renderer but instead implement the primitive drawing
     *     methods described below. This approach is encouraged when you're using a XML based rendering engine
     *     like VML and SVG. If you want to use a bitmap based rendering technique you are supposed to override
     *     these methods instead of the primitive drawing methods.</dd>
     *     <dt>Draw primitives</dt>
     *     <dd>This category summarizes methods to handle primitive nodes. As creation and management of these nodes
     *     is different among different the rendering techniques most of these methods are purely virtual and need
     *     proper implementation if you choose to not overwrite the basic element drawing methods.</dd>
     *     <dt>Attribute manipulation</dt>
     *     <dd>In XML based renders you have to manipulate XML nodes and their attributes to change the graphics.
     *     For that purpose attribute manipulation methods are defined to set the color, opacity, and other things.
     *     Please note that some of these methods are required in bitmap based renderers, too, because some elements
     *     like {@link JXG.Text} can be HTML nodes floating over the construction.</dd>
     *     <dt>Renderer control</dt>
     *     <dd>Methods to clear the drawing board or to stop and to resume the rendering engine.</dd>
     * </dl></p>
     * @class JXG.AbstractRenderer
     * @constructor
     * @see JXG.SVGRenderer
     * @see JXG.VMLRenderer
     * @see JXG.CanvasRenderer
     */
    JXG.AbstractRenderer = function () {

        // WHY THIS IS A CLASS INSTEAD OF A SINGLETON OBJECT:
        //
        // The renderers need to keep track of some stuff which is not always the same on different boards,
        // like enhancedRendering, reference to the container object, and resolution in VML. Sure, those
        // things could be stored in board. But they are rendering related and JXG.Board is already very
        // very big.
        //
        // And we can't save the rendering related data in {SVG,VML,Canvas}Renderer and make only the
        // JXG.AbstractRenderer a singleton because of that:
        //
        // Given an object o with property a set to true
        //     var o = {a: true};
        // and a class c doing nothing
        //     c = function() {};
        // Set c's prototype to o
        //     c.prototype = o;
        // and create an instance of c we get i.a to be true
        //     i = new c();
        //     i.a;
        //     > true
        // But we can overwrite this property via
        //     c.prototype.a = false;
        //     i.a;
        //     > false

        /**
         * The vertical offset for {@link Text} elements. Every {@link Text} element will
         * be placed this amount of pixels below the user given coordinates.
         * @type Number
         * @default 0
         */
        this.vOffsetText = 0;

        /**
         * If this property is set to <tt>true</tt> the visual properties of the elements are updated
         * on every update. Visual properties means: All the stuff stored in the
         * {@link JXG.GeometryElement#visProp} property won't be set if enhancedRendering is <tt>false</tt>
         * @type Boolean
         * @default true
         */
        this.enhancedRendering = true;

        /**
         * The HTML element that stores the JSXGraph board in it.
         * @type Node
         */
        this.container = null;

        /**
         * This is used to easily determine which renderer we are using
         * @example if (board.renderer.type === 'vml') {
          *     // do something
         * }
         * @type String
         */
        this.type = '';

        /**
         * True if the browsers' SVG engine supports foreignObject.
         * Not supported browsers are IE 9 - 11.
         * All other browsers return ture, since it is tested with
         * document.implementation.hasFeature() which is deprecated.
         *
         * @type Boolean
         * @private
         */
        this.supportsForeignObject = false;

    };

    JXG.extend(JXG.AbstractRenderer.prototype, /** @lends JXG.AbstractRenderer.prototype */ {

        /* ******************************** *
         *    private methods               *
         *    should not be called from     *
         *    outside AbstractRenderer      *
         * ******************************** */

        /**
         * Update visual properties, but only if {@link JXG.AbstractRenderer#enhancedRendering} or <tt>enhanced</tt> is set to true.
         * @param {JXG.GeometryElement} el The element to update
         * @param {Object} [not={}] Select properties you don't want to be updated: <tt>{fill: true, dash: true}</tt> updates
         * everything except for fill and dash. Possible values are <tt>stroke, fill, dash, shadow, gradient</tt>.
         * @param {Boolean} [enhanced=false] If true, {@link JXG.AbstractRenderer#enhancedRendering} is assumed to be true.
         * @private
         */
        _updateVisual: function (el, not, enhanced) {
            if (enhanced || this.enhancedRendering) {
                not = not || {};

                this.setObjectTransition(el);
                if (!Type.evaluate(el.visProp.draft)) {
                    if (!not.stroke) {
                        if (el.highlighted) {
                            this.setObjectStrokeColor(el,
                                el.visProp.highlightstrokecolor,
                                el.visProp.highlightstrokeopacity);
                            this.setObjectStrokeWidth(el, el.visProp.highlightstrokewidth);
                        } else {
                            this.setObjectStrokeColor(el,
                                el.visProp.strokecolor,
                                el.visProp.strokeopacity);
                            this.setObjectStrokeWidth(el, el.visProp.strokewidth);
                        }
                    }

                    if (!not.fill) {
                        if (el.highlighted) {
                            this.setObjectFillColor(el,
                                el.visProp.highlightfillcolor,
                                el.visProp.highlightfillopacity);
                        } else {
                            this.setObjectFillColor(el,
                                el.visProp.fillcolor,
                                el.visProp.fillopacity);
                        }
                    }

                    if (!not.dash) {
                        this.setDashStyle(el, el.visProp);
                    }

                    if (!not.shadow) {
                        this.setShadow(el);
                    }

                    if (!not.gradient) {
                        this.setShadow(el);
                    }

                    if (!not.tabindex) {
                        this.setTabindex(el);
                    }
                } else {
                    this.setDraft(el);
                }
            }
        },

        /**
         * Get information if element is highlighted.
         * @param {JXG.GeometryElement} el The element which is tested for being highlighted.
         * @returns {String} 'highlight' if highlighted, otherwise the ampty string '' is returned.
         * @private
         */
        _getHighlighted: function(el) {
            var isTrace = false,
                hl;

            if (!Type.exists(el.board) || !Type.exists(el.board.highlightedObjects)) {
                // This case handles trace elements.
                // To make them work, we simply neglect highlighting.
                isTrace = true;
            }

            if (!isTrace && Type.exists(el.board.highlightedObjects[el.id])) {
                hl = 'highlight';
            } else {
                hl = '';
            }
            return hl;
        },

        /* ******************************** *
         *    Point drawing and updating    *
         * ******************************** */

        /**
         * Draws a point on the {@link JXG.Board}.
         * @param {JXG.Point} el Reference to a {@link JXG.Point} object that has to be drawn.
         * @see Point
         * @see JXG.Point
         * @see JXG.AbstractRenderer#updatePoint
         * @see JXG.AbstractRenderer#changePointStyle
         */
        drawPoint: function (el) {
            var prim,
                // sometimes el is not a real point and lacks the methods of a JXG.Point instance,
                // in these cases to not use el directly.
                face = Options.normalizePointFace(Type.evaluate(el.visProp.face));

            // determine how the point looks like
            if (face === 'o') {
                prim = 'ellipse';
            } else if (face === '[]') {
                prim = 'rect';
            } else {
                // cross/x, diamond/<>, triangleup/a/^, triangledown/v, triangleleft/<,
                // triangleright/>, plus/+,
                prim = 'path';
            }

            el.rendNode = this.appendChildPrim(this.createPrim(prim, el.id), Type.evaluate(el.visProp.layer));
            this.appendNodesToElement(el, prim);

            // adjust visual propertys
            this._updateVisual(el, {dash: true, shadow: true}, true);

            // By now we only created the xml nodes and set some styles, in updatePoint
            // the attributes are filled with data.
            this.updatePoint(el);
        },

        /**
         * Updates visual appearance of the renderer element assigned to the given {@link JXG.Point}.
         * @param {JXG.Point} el Reference to a {@link JXG.Point} object, that has to be updated.
         * @see Point
         * @see JXG.Point
         * @see JXG.AbstractRenderer#drawPoint
         * @see JXG.AbstractRenderer#changePointStyle
         */
        updatePoint: function (el) {
            var size = Type.evaluate(el.visProp.size),
                // sometimes el is not a real point and lacks the methods of a JXG.Point instance,
                // in these cases to not use el directly.
                face = Options.normalizePointFace(Type.evaluate(el.visProp.face)),
                unit = Type.evaluate(el.visProp.sizeunit),
                zoom = Type.evaluate(el.visProp.zoom),
                s1;

            if (!isNaN(el.coords.scrCoords[2] + el.coords.scrCoords[1])) {
                if (unit === 'user') {
                    size *= Math.sqrt(el.board.unitX * el.board.unitY);
                }
                size *= ((!el.board || !zoom) ?
                    1.0 : Math.sqrt(el.board.zoomX * el.board.zoomY));
                s1 = (size === 0) ? 0 : size + 1;

                if (face === 'o') { // circle
                    this.updateEllipsePrim(el.rendNode, el.coords.scrCoords[1],
                         el.coords.scrCoords[2], s1, s1);
                } else if (face === '[]') { // rectangle
                    this.updateRectPrim(el.rendNode, el.coords.scrCoords[1] - size,
                         el.coords.scrCoords[2] - size, size * 2, size * 2);
                } else { // x, +, <>, ^, v, <, >
                    this.updatePathPrim(el.rendNode,
                        this.updatePathStringPoint(el, size, face), el.board);
                }
                this._updateVisual(el, {dash: false, shadow: false});
                this.setShadow(el);
            }
        },

        /**
         * Changes the style of a {@link JXG.Point}. This is required because the point styles differ in what
         * elements have to be drawn, e.g. if the point is marked by a "x" or a "+" two lines are drawn, if
         * it's marked by spot a circle is drawn. This method removes the old renderer element(s) and creates
         * the new one(s).
         * @param {JXG.Point} el Reference to a {@link JXG.Point} object, that's style is changed.
         * @see Point
         * @see JXG.Point
         * @see JXG.AbstractRenderer#updatePoint
         * @see JXG.AbstractRenderer#drawPoint
         */
        changePointStyle: function (el) {
            var node = this.getElementById(el.id);

            // remove the existing point rendering node
            if (Type.exists(node)) {
                this.remove(node);
            }

            // and make a new one
            this.drawPoint(el);
            Type.clearVisPropOld(el);

            if (!el.visPropCalc.visible) {
                this.display(el, false);
            }

            if (Type.evaluate(el.visProp.draft)) {
                this.setDraft(el);
            }
        },

        /* ******************************** *
         *           Lines                  *
         * ******************************** */

        /**
         * Draws a line on the {@link JXG.Board}.
         * @param {JXG.Line} el Reference to a line object, that has to be drawn.
         * @see Line
         * @see JXG.Line
         * @see JXG.AbstractRenderer#updateLine
         */
        drawLine: function (el) {
            el.rendNode = this.appendChildPrim(this.createPrim('line', el.id),
                                    Type.evaluate(el.visProp.layer));
            this.appendNodesToElement(el, 'lines');
            this.updateLine(el);
        },

        /**
         * Updates visual appearance of the renderer element assigned to the given {@link JXG.Line}.
         * @param {JXG.Line} el Reference to the {@link JXG.Line} object that has to be updated.
         * @see Line
         * @see JXG.Line
         * @see JXG.AbstractRenderer#drawLine
         */
        updateLine: function (el) {
            this._updateVisual(el);
            this.updatePathWithArrowHeads(el);  // Calls the renderer primitive
            this.setLineCap(el);
        },

        /* **************************
         *    Curves
         * **************************/

        /**
         * Draws a {@link JXG.Curve} on the {@link JXG.Board}.
         * @param {JXG.Curve} el Reference to a graph object, that has to be plotted.
         * @see Curve
         * @see JXG.Curve
         * @see JXG.AbstractRenderer#updateCurve
         */
        drawCurve: function (el) {
            el.rendNode = this.appendChildPrim(this.createPrim('path', el.id), Type.evaluate(el.visProp.layer));
            this.appendNodesToElement(el, 'path');
            this.updateCurve(el);
        },

        /**
         * Updates visual appearance of the renderer element assigned to the given {@link JXG.Curve}.
         * @param {JXG.Curve} el Reference to a {@link JXG.Curve} object, that has to be updated.
         * @see Curve
         * @see JXG.Curve
         * @see JXG.AbstractRenderer#drawCurve
         */
        updateCurve: function (el) {
            this._updateVisual(el);
            this.updatePathWithArrowHeads(el); // Calls the renderer primitive
            this.setLineCap(el);
        },

        /* **************************
         *    Arrow heads and related stuff
         * **************************/

        /**
         * Handles arrow heads of a line or curve element and calls the renderer primitive.
         *
         * @param {JXG.GeometryElement} el Reference to a line or curve object that has to be drawn.
         * @param {Boolean} doHighlight
         *
         * @private
         * @see Line
         * @see JXG.Line
         * @see Curve
         * @see JXG.Curve
         * @see JXG.AbstractRenderer#updateLine
         * @see JXG.AbstractRenderer#updateCurve
         * @see JXG.AbstractRenderer#makeArrows
         * @see JXG.AbstractRenderer#getArrowHeadData
         */
        updatePathWithArrowHeads: function(el, doHighlight) {
            var ev = el.visProp,
                hl = doHighlight ? 'highlight' : '',
                w,
                arrowData;

            if (doHighlight && ev.highlightstrokewidth) {
                w = Math.max(Type.evaluate(ev.highlightstrokewidth), Type.evaluate(ev.strokewidth));
            } else {
                w = Type.evaluate(ev.strokewidth);
            }

            // Get information if there are arrow heads and how large they are.
            arrowData = this.getArrowHeadData(el, w, hl);

            // Create the SVG nodes if neccessary
            this.makeArrows(el, arrowData);

            // Draw the paths with arrow heads
            if (el.elementClass === Const.OBJECT_CLASS_LINE) {
                this.updateLineWithEndings(el, arrowData);
            } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
                this.updatePath(el);
            }

            this.setArrowSize(el, arrowData);
        },

        /**
         * This method determines some data about the line endings of this element.
         * If there are arrow heads, the offset is determined so that no parts of the line stroke
         * lap over the arrow head.
         * <p>
         * The returned object also contains the types of the arrow heads.
         *
         * @param {JXG.GeometryElement} el JSXGraph line or curve element
         * @param {Number} strokewidth strokewidth of the element
         * @param {String} hl Ither 'highlight' or empty string
         * @returns {Object} object containing the data
         *
         * @private
         */
        getArrowHeadData: function(el, strokewidth, hl) {
            var minlen = Mat.eps,
                typeFirst, typeLast,
                offFirst = 0,
                offLast = 0,
                sizeFirst = 0,
                sizeLast = 0,
                ev_fa = Type.evaluate(el.visProp.firstarrow),
                ev_la = Type.evaluate(el.visProp.lastarrow),
                off, size;

            /*
               Handle arrow heads.

               The default arrow head is an isosceles triangle with base length 10 units and height 10 units.
               These 10 units are scaled to strokeWidth * arrowSize pixels pixels.
            */
            if (ev_fa || ev_la) {

                if (Type.exists(ev_fa.type)) {
                    typeFirst = Type.evaluate(ev_fa.type);
                } else {
                    if (el.elementClass === Const.OBJECT_CLASS_LINE) {
                        typeFirst = 1;
                    } else {
                        typeFirst = 7;
                    }
                }
                if (Type.exists(ev_la.type)) {
                    typeLast = Type.evaluate(ev_la.type);
                } else {
                    if (el.elementClass === Const.OBJECT_CLASS_LINE) {
                        typeLast = 1;
                    } else {
                        typeLast = 7;
                    }
                }

                if (ev_fa) {
                    size = 6;
                    if (Type.exists(ev_fa.size)) {
                        size = Type.evaluate(ev_fa.size);
                    }
                    if (hl !== '' && Type.exists(ev_fa[hl + 'size'])) {
                        size = Type.evaluate(ev_fa[hl + 'size']);
                    }

                    off = strokewidth * size;
                    if (typeFirst === 2) {
                        off *= 0.5;
                        minlen += strokewidth * size;
                    } else if (typeFirst === 3) {
                        off = strokewidth * size / 3;
                        minlen += strokewidth;
                    } else if (typeFirst === 4 || typeFirst === 5 || typeFirst === 6) {
                        off = strokewidth * size / 1.5;
                        minlen += strokewidth * size;
                    } else if (typeFirst === 7) {
                        off = 0;
                        size = 10;
                        minlen += strokewidth;
                    } else {
                        minlen += strokewidth * size;
                    }
                    offFirst += off;
                    sizeFirst = size;
                }

                if (ev_la) {
                    size = 6;
                    if (Type.exists(ev_la.size)) {
                        size = Type.evaluate(ev_la.size);
                    }
                    if (hl !== '' && Type.exists(ev_la[hl + 'size'])) {
                        size = Type.evaluate(ev_la[hl + 'size']);
                    }
                    off = strokewidth * size;
                    if (typeLast === 2) {
                        off *= 0.5;
                        minlen += strokewidth * size;
                    } else if (typeLast === 3) {
                        off = strokewidth * size / 3;
                        minlen += strokewidth;
                    } else if (typeLast === 4 || typeLast === 5 || typeLast === 6) {
                        off = strokewidth * size / 1.5;
                        minlen += strokewidth * size;
                    } else if (typeLast === 7) {
                        off = 0;
                        size = 10;
                        minlen += strokewidth;
                    } else {
                        minlen += strokewidth * size;
                    }
                    offLast += off;
                    sizeLast = size;
                }
            }
            el.visPropCalc.typeFirst = typeFirst;
            el.visPropCalc.typeLast = typeLast;

            return {
                evFirst: ev_fa,
                evLast: ev_la,
                typeFirst: typeFirst,
                typeLast: typeLast,
                offFirst: offFirst,
                offLast: offLast,
                sizeFirst: sizeFirst,
                sizeLast: sizeLast,
                showFirst: 1, // Show arrow head. 0 if the distance is too small
                showLast: 1,  // Show arrow head. 0 if the distance is too small
                minLen: minlen,
                strokeWidth: strokewidth
            };
        },

        /**
         * Corrects the line length if there are arrow heads, such that
         * the arrow ends exactly at the intended position.
         * Calls the renderer method to draw the line.
         *
         * @param {JXG.Line} el Reference to a line object, that has to be drawn
         * @param {Object} arrowData Data concerning possible arrow heads
         *
         * @returns {JXG.AbstractRenderer} Reference to the renderer
         *
         * @private
         * @see Line
         * @see JXG.Line
         * @see JXG.AbstractRenderer#updateLine
         * @see JXG.AbstractRenderer#getPositionArrowHead
         *
         */
        updateLineWithEndings: function(el, arrowData) {
            var c1, c2,
                // useTotalLength = true,
                margin = null;

            c1 = new Coords(Const.COORDS_BY_USER, el.point1.coords.usrCoords, el.board);
            c2 = new Coords(Const.COORDS_BY_USER, el.point2.coords.usrCoords, el.board);
            margin = Type.evaluate(el.visProp.margin);
            Geometry.calcStraight(el, c1, c2, margin);

            this.handleTouchpoints(el, c1, c2, arrowData);
            this.getPositionArrowHead(el, c1, c2, arrowData);

            this.updateLinePrim(el.rendNode,
                c1.scrCoords[1], c1.scrCoords[2],
                c2.scrCoords[1], c2.scrCoords[2], el.board);

            return this;
        },

        /**
         *
         * Calls the renderer method to draw a curve.
         *
         * @param {JXG.GeometryElement} el Reference to a line object, that has to be drawn.
         * @returns {JXG.AbstractRenderer} Reference to the renderer
         *
         * @private
         * @see Curve
         * @see JXG.Curve
         * @see JXG.AbstractRenderer#updateCurve
         *
         */
        updatePath: function(el) {
            if (Type.evaluate(el.visProp.handdrawing)) {
                this.updatePathPrim(el.rendNode, this.updatePathStringBezierPrim(el), el.board);
            } else {
                this.updatePathPrim(el.rendNode, this.updatePathStringPrim(el), el.board);
            }

            return this;
        },

        /**
         * Shorten the length of a line element such that the arrow head touches
         * the start or end point and such that the arrow head ends exactly
         * at the start / end position of the line.
         *
         * @param  {JXG.Line} el Reference to the line object that gets arrow heads.
         * @param  {JXG.Coords} c1  Coords of the first point of the line (after {@link JXG.Math.Geometry#calcStraight}).
         * @param  {JXG.Coords} c2  Coords of the second point of the line (after {@link JXG.Math.Geometry#calcStraight}).
         * @param  {Object}  a
         * @return {object} Object containing how much the line has to be shortened.
         * Data structure: {c1, c2, d1x, d1y, d2x, d2y, sFirst, sLast}. sFirst and sLast is the length by which
         * firstArrow and lastArrow have to shifted such that there is no gap between arrow head and line.
         * Additionally, if one of these values is zero, the arrow is not displayed. This is the case, if the
         * line length is very short.
         */
         getPositionArrowHead: function(el, c1, c2, a) {
            var d, d1x, d1y, d2x, d2y;

            /*
               Handle arrow heads.

               The default arrow head (type==1) is an isosceles triangle with base length 10 units and height 10 units.
               These 10 units are scaled to strokeWidth * arrowSize pixels pixels.
            */
            if (a.evFirst || a.evLast) {
                // Correct the position of the arrow heads
                d1x = d1y = d2x = d2y = 0.0;
                d = c1.distance(Const.COORDS_BY_SCREEN, c2);

                if (a.evFirst &&
                    el.board.renderer.type !== 'vml') {
                    if (d >= a.minLen) {
                        d1x = (c2.scrCoords[1] - c1.scrCoords[1]) * a.offFirst / d;
                        d1y = (c2.scrCoords[2] - c1.scrCoords[2]) * a.offFirst / d;
                    } else {
                        a.showFirst = 0;
                    }
                }

                if (a.evLast &&
                    el.board.renderer.type !== 'vml') {
                    if (d >= a.minLen) {
                        d2x = (c2.scrCoords[1] - c1.scrCoords[1]) * a.offLast / d;
                        d2y = (c2.scrCoords[2] - c1.scrCoords[2]) * a.offLast / d;
                    } else {
                        a.showLast = 0;
                    }
                }
                c1.setCoordinates(Const.COORDS_BY_SCREEN, [c1.scrCoords[1] + d1x, c1.scrCoords[2] + d1y], false, true);
                c2.setCoordinates(Const.COORDS_BY_SCREEN, [c2.scrCoords[1] - d2x, c2.scrCoords[2] - d2y], false, true);
            }

            return this;
        },

        /**
         * Handle touchlastpoint / touchfirstpoint
         *
         * @param {JXG.GeometryElement} el
         * @param {JXG.Coords} c1 Coordinates of the start of the line. The coordinates are changed in place.
         * @param {JXG.Coords} c2 Coordinates of the end of the line. The coordinates are changed in place.
         * @param {Object} a
         */
        handleTouchpoints: function(el, c1, c2, a) {
            var s1, s2, d,
                d1x, d1y, d2x, d2y;

            if (a.evFirst || a.evLast) {
                d = d1x = d1y = d2x = d2y = 0.0;

                s1 = Type.evaluate(el.point1.visProp.size) + Type.evaluate(el.point1.visProp.strokewidth);
                s2 = Type.evaluate(el.point2.visProp.size) + Type.evaluate(el.point2.visProp.strokewidth);

                // Handle touchlastpoint /touchfirstpoint
                if (a.evFirst && Type.evaluate(el.visProp.touchfirstpoint)) {
                    d = c1.distance(Const.COORDS_BY_SCREEN, c2);
                    //if (d > s) {
                        d1x = (c2.scrCoords[1] - c1.scrCoords[1]) * s1 / d;
                        d1y = (c2.scrCoords[2] - c1.scrCoords[2]) * s1 / d;
                    //}
                }
                if (a.evLast && Type.evaluate(el.visProp.touchlastpoint)) {
                    d = c1.distance(Const.COORDS_BY_SCREEN, c2);
                    //if (d > s) {
                        d2x = (c2.scrCoords[1] - c1.scrCoords[1]) * s2 / d;
                        d2y = (c2.scrCoords[2] - c1.scrCoords[2]) * s2 / d;
                    //}
                }
                c1.setCoordinates(Const.COORDS_BY_SCREEN, [c1.scrCoords[1] + d1x, c1.scrCoords[2] + d1y], false, true);
                c2.setCoordinates(Const.COORDS_BY_SCREEN, [c2.scrCoords[1] - d2x, c2.scrCoords[2] - d2y], false, true);
            }

            return this;
        },

        /**
         * Set the arrow head size.
         *
         * @param {JXG.GeometryElement} el Reference to a line or curve object that has to be drawn.
         * @param {Object} arrowData Data concerning possible arrow heads
         * @returns {JXG.AbstractRenderer} Reference to the renderer
         *
         * @private
         * @see Line
         * @see JXG.Line
         * @see Curve
         * @see JXG.Curve
         * @see JXG.AbstractRenderer#updatePathWithArrowHeads
         * @see JXG.AbstractRenderer#getArrowHeadData
         */
        setArrowSize: function(el, a) {
            if (a.evFirst) {
                this._setArrowWidth(el.rendNodeTriangleStart, a.showFirst * a.strokeWidth, el.rendNode, a.sizeFirst);
            }
            if (a.evLast) {
                this._setArrowWidth(el.rendNodeTriangleEnd, a.showLast * a.strokeWidth, el.rendNode, a.sizeLast);
            }
            return this;
        },

        /**
         * Update the line endings (linecap) of a straight line from its attribute
         * 'linecap'.
         * Possible values for the attribute 'linecap' are: 'butt', 'round', 'square'.
         * The default value is 'butt'. Not available for VML renderer.
         *
         * @param {JXG.Line} element A arbitrary line.
         * @see Line
         * @see JXG.Line
         * @see JXG.AbstractRenderer#updateLine
         */
        setLineCap: function(el) { /* stub */ },

        /* **************************
         *    Ticks related stuff
         * **************************/

        /**
         * Creates a rendering node for ticks added to a line.
         * @param {JXG.Line} el A arbitrary line.
         * @see Line
         * @see Ticks
         * @see JXG.Line
         * @see JXG.Ticks
         * @see JXG.AbstractRenderer#updateTicks
         */
        drawTicks: function (el) {
            el.rendNode = this.appendChildPrim(this.createPrim('path', el.id), Type.evaluate(el.visProp.layer));
            this.appendNodesToElement(el, 'path');
        },

        /**
         * Update {@link Ticks} on a {@link JXG.Line}. This method is only a stub and has to be implemented
         * in any descendant renderer class.
         * @param {JXG.Ticks} element Reference of a ticks object that has to be updated.
         * @see Line
         * @see Ticks
         * @see JXG.Line
         * @see JXG.Ticks
         * @see JXG.AbstractRenderer#drawTicks
         */
        updateTicks: function (element) { /* stub */ },

        /* **************************
         *    Circle related stuff
         * **************************/

        /**
         * Draws a {@link JXG.Circle}
         * @param {JXG.Circle} el Reference to a {@link JXG.Circle} object that has to be drawn.
         * @see Circle
         * @see JXG.Circle
         * @see JXG.AbstractRenderer#updateEllipse
         */
        drawEllipse: function (el) {
            el.rendNode = this.appendChildPrim(this.createPrim('ellipse', el.id),
                                    Type.evaluate(el.visProp.layer));
            this.appendNodesToElement(el, 'ellipse');
            this.updateEllipse(el);
        },

        /**
         * Updates visual appearance of a given {@link JXG.Circle} on the {@link JXG.Board}.
         * @param {JXG.Circle} el Reference to a {@link JXG.Circle} object, that has to be updated.
         * @see Circle
         * @see JXG.Circle
         * @see JXG.AbstractRenderer#drawEllipse
         */
        updateEllipse: function (el) {
            this._updateVisual(el);

            var radius = el.Radius();

            if (radius > 0.0 &&
                    Math.abs(el.center.coords.usrCoords[0]) > Mat.eps &&
                    !isNaN(radius + el.center.coords.scrCoords[1] + el.center.coords.scrCoords[2]) &&
                    radius * el.board.unitX < 2000000) {
                this.updateEllipsePrim(el.rendNode, el.center.coords.scrCoords[1],
                    el.center.coords.scrCoords[2],
                    (radius * el.board.unitX),
                    (radius * el.board.unitY));
            }
        },

        /* **************************
         *   Polygon related stuff
         * **************************/

        /**
         * Draws a {@link JXG.Polygon} on the {@link JXG.Board}.
         * @param {JXG.Polygon} el Reference to a Polygon object, that is to be drawn.
         * @see Polygon
         * @see JXG.Polygon
         * @see JXG.AbstractRenderer#updatePolygon
         */
        drawPolygon: function (el) {
            el.rendNode = this.appendChildPrim(this.createPrim('polygon', el.id),
                                        Type.evaluate(el.visProp.layer));
            this.appendNodesToElement(el, 'polygon');
            this.updatePolygon(el);
        },

        /**
         * Updates properties of a {@link JXG.Polygon}'s rendering node.
         * @param {JXG.Polygon} el Reference to a {@link JXG.Polygon} object, that has to be updated.
         * @see Polygon
         * @see JXG.Polygon
         * @see JXG.AbstractRenderer#drawPolygon
         */
        updatePolygon: function (el) {
            // Here originally strokecolor wasn't updated but strokewidth was.
            // But if there's no strokecolor i don't see why we should update strokewidth.
            this._updateVisual(el, {stroke: true, dash: true});
            this.updatePolygonPrim(el.rendNode, el);
        },

        /* **************************
         *    Text related stuff
         * **************************/

        /**
         * Shows a small copyright notice in the top left corner of the board.
         * @param {String} str The copyright notice itself
         * @param {Number} fontsize Size of the font the copyright notice is written in
         */
        displayCopyright: function (str, fontsize) { /* stub */ },

        /**
         * An internal text is a {@link JXG.Text} element which is drawn using only
         * the given renderer but no HTML. This method is only a stub, the drawing
         * is done in the special renderers.
         * @param {JXG.Text} element Reference to a {@link JXG.Text} object
         * @see Text
         * @see JXG.Text
         * @see JXG.AbstractRenderer#updateInternalText
         * @see JXG.AbstractRenderer#drawText
         * @see JXG.AbstractRenderer#updateText
         * @see JXG.AbstractRenderer#updateTextStyle
         */
        drawInternalText: function (element) { /* stub */ },

        /**
         * Updates visual properties of an already existing {@link JXG.Text} element.
         * @param {JXG.Text} element Reference to an {@link JXG.Text} object, that has to be updated.
         * @see Text
         * @see JXG.Text
         * @see JXG.AbstractRenderer#drawInternalText
         * @see JXG.AbstractRenderer#drawText
         * @see JXG.AbstractRenderer#updateText
         * @see JXG.AbstractRenderer#updateTextStyle
         */
        updateInternalText: function (element) { /* stub */ },

        /**
         * Displays a {@link JXG.Text} on the {@link JXG.Board} by putting a HTML div over it.
         * @param {JXG.Text} el Reference to an {@link JXG.Text} object, that has to be displayed
         * @see Text
         * @see JXG.Text
         * @see JXG.AbstractRenderer#drawInternalText
         * @see JXG.AbstractRenderer#updateText
         * @see JXG.AbstractRenderer#updateInternalText
         * @see JXG.AbstractRenderer#updateTextStyle
         */
        drawText: function (el) {
            var node, z, level,
                ev_visible;

            if (Type.evaluate(el.visProp.display) === 'html' && Env.isBrowser && this.type !== 'no') {
                node = this.container.ownerDocument.createElement('div');
                //node = this.container.ownerDocument.createElementNS('http://www.w3.org/1999/xhtml', 'div'); //
                node.style.position = 'absolute';
                node.className = Type.evaluate(el.visProp.cssclass);

                level = Type.evaluate(el.visProp.layer);
                if (!Type.exists(level)) { // trace nodes have level not set
                    level = 0;
                }

                if (this.container.style.zIndex === '') {
                    z = 0;
                } else {
                    z = parseInt(this.container.style.zIndex, 10);
                }

                node.style.zIndex = z + level;
                this.container.appendChild(node);

                node.setAttribute('id', this.container.id + '_' + el.id);
            } else {
                node = this.drawInternalText(el);
            }

            el.rendNode = node;
            el.htmlStr = '';

            // Set el.visPropCalc.visible
            if (el.visProp.islabel && Type.exists(el.visProp.anchor)) {
                ev_visible = Type.evaluate(el.visProp.anchor.visProp.visible);
                el.prepareUpdate().updateVisibility(ev_visible);
            } else {
                el.prepareUpdate().updateVisibility();
            }
            this.updateText(el);
        },

        /**
         * Updates visual properties of an already existing {@link JXG.Text} element.
         * @param {JXG.Text} el Reference to an {@link JXG.Text} object, that has to be updated.
         * @see Text
         * @see JXG.Text
         * @see JXG.AbstractRenderer#drawText
         * @see JXG.AbstractRenderer#drawInternalText
         * @see JXG.AbstractRenderer#updateInternalText
         * @see JXG.AbstractRenderer#updateTextStyle
         */
        updateText: function (el) {
            var content = el.plaintext,
                v, c,
                parentNode,
                scale, vshift, id, wrap_id,
                ax, ay;

            if (el.visPropCalc.visible) {
                this.updateTextStyle(el, false);

                if (Type.evaluate(el.visProp.display) === 'html' && this.type !== 'no') {
                    // Set the position
                    if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {

                        // Horizontal
                        c = el.coords.scrCoords[1];
                        // webkit seems to fail for extremely large values for c.
                        c = Math.abs(c) < 1000000 ? c : 1000000;
                        ax = el.getAnchorX();

                        if (ax === 'right') {
                            // v = Math.floor(el.board.canvasWidth - c);
                            v = el.board.canvasWidth - c;
                        } else if (ax === 'middle') {
                            // v = Math.floor(c - 0.5 * el.size[0]);
                            v = c - 0.5 * el.size[0];
                        } else { // 'left'
                            // v = Math.floor(c);
                            v = c;
                        }

                        // This may be useful for foreignObj.
                        //if (window.devicePixelRatio !== undefined) {
                        //v *= window.devicePixelRatio;
                        //}

                        if (el.visPropOld.left !== (ax + v)) {
                            if (ax === 'right') {
                                el.rendNode.style.right = v + 'px';
                                el.rendNode.style.left = 'auto';
                            } else {
                                el.rendNode.style.left = v + 'px';
                                el.rendNode.style.right = 'auto';
                            }
                            el.visPropOld.left = ax + v;
                        }

                        // Vertical
                        c = el.coords.scrCoords[2] + this.vOffsetText;
                        c = Math.abs(c) < 1000000 ? c : 1000000;
                        ay = el.getAnchorY();

                        if (ay === 'bottom') {
                            // v = Math.floor(el.board.canvasHeight - c);
                            v = el.board.canvasHeight - c;
                        } else if (ay === 'middle') {
                            // v = Math.floor(c - 0.5 * el.size[1]);
                            v = c - 0.5 * el.size[1];
                        } else { // top
                            // v = Math.floor(c);
                            v = c;
                        }

                        // This may be useful for foreignObj.
                        //if (window.devicePixelRatio !== undefined) {
                        //v *= window.devicePixelRatio;
                        //}

                        if (el.visPropOld.top !== (ay + v)) {
                            if (ay === 'bottom') {
                                el.rendNode.style.top = 'auto';
                                el.rendNode.style.bottom = v + 'px';
                            } else {
                                el.rendNode.style.bottom = 'auto';
                                el.rendNode.style.top = v + 'px';
                            }
                            el.visPropOld.top = ay + v;
                        }
                    }

                    // Set the content
                    if (el.htmlStr !== content) {
                        try {
                            if (el.type === Type.OBJECT_TYPE_BUTTON) {
                                el.rendNodeButton.innerHTML = content;
                            } else if (el.type === Type.OBJECT_TYPE_CHECKBOX ||
                                el.type === Type.OBJECT_TYPE_INPUT) {
                                el.rendNodeLabel.innerHTML = content;
                            } else {
                                el.rendNode.innerHTML = content;
                            }
                        } catch (e) {
                            // Setting innerHTML sometimes fails in IE8.
                            // A workaround is to take the node off the DOM, assign innerHTML,
                            // then append back.
                            // Works for text elements as they are absolutely positioned.
                            parentNode = el.rendNode.parentNode;
                            el.rendNode.parentNode.removeChild(el.rendNode);
                            el.rendNode.innerHTML = content;
                            parentNode.appendChild(el.rendNode);
                        }
                        el.htmlStr = content;

                        if (Type.evaluate(el.visProp.usemathjax)) {
                            // Typesetting directly might not work because mathjax was not loaded completely
                            // see http://www.mathjax.org/docs/1.1/typeset.html
                            try {
                                if (MathJax.typeset) {
                                    // Version 3
                                    MathJax.typeset([el.rendNode]);
                                } else {
                                    // Version 2
                                    MathJax.Hub.Queue(['Typeset', MathJax.Hub, el.rendNode]);
                                }

                                // Restore the transformation necessary for fullscreen mode
                                // MathJax removes it when handling dynamic content
                                id = el.board.container;
                                wrap_id = 'fullscreenwrap_' + id;
                                if (document.getElementById(wrap_id)) {
                                    scale = el.board.containerObj._cssFullscreenStore.scale;
                                    vshift = el.board.containerObj._cssFullscreenStore.vshift;
                                    Env.scaleJSXGraphDiv('#' + wrap_id, '#' + id, scale, vshift);
                                }

                            } catch (e) {
                                JXG.debug('MathJax (not yet) loaded');
                            }
                        } else if (Type.evaluate(el.visProp.usekatex)) {
                            try {
                                /* eslint-disable no-undef */
                                katex.render(content, el.rendNode, {
                                    throwOnError: false
                                });
                                /* eslint-enable no-undef */
                            } catch (e) {
                                JXG.debug('KaTeX (not yet) loaded');
                            }
                    } else if (Type.evaluate(el.visProp.useasciimathml)) {
                            // This is not a constructor.
                            // See http://www1.chapman.edu/~jipsen/mathml/asciimath.html for more information
                            // about AsciiMathML and the project's source code.
                            try {
                                AMprocessNode(el.rendNode, false);
                            } catch (e) {
                                JXG.debug('AsciiMathML (not yet) loaded');
                            }
                        }
                    }
                    this.transformImage(el, el.transformations);
                } else {
                    this.updateInternalText(el);
                }
            }
        },

        /**
         * Converts string containing CSS properties into
         * array with key-value pair objects.
         *
         * @example
         * "color:blue; background-color:yellow" is converted to
         * [{'color': 'blue'}, {'backgroundColor': 'yellow'}]
         *
         * @param  {String} cssString String containing CSS properties
         * @return {Array}           Array of CSS key-value pairs
         */
        _css2js: function(cssString) {
            var pairs = [],
                i, len, key, val, s,
                list = Type.trim(cssString).replace(/;$/, '').split(";");

            len = list.length;
            for (i = 0; i < len; ++i) {
                if (Type.trim(list[i]) !== '') {
                    s = list[i].split(':');
                    key = Type.trim(s[0].replace(/-([a-z])/gi, function(match, char) { return char.toUpperCase(); }));
                    val = Type.trim(s[1]);
                    pairs.push({'key': key, 'val': val});
                }
            }
            return pairs;

        },

        /**
         * Updates font-size, color and opacity propertiey and CSS style properties of a {@link JXG.Text} node.
         * This function is also called by highlight() and nohighlight().
         * @param {JXG.Text} el Reference to the {@link JXG.Text} object, that has to be updated.
         * @param {Boolean} doHighlight
         * @see Text
         * @see JXG.Text
         * @see JXG.AbstractRenderer#drawText
         * @see JXG.AbstractRenderer#drawInternalText
         * @see JXG.AbstractRenderer#updateText
         * @see JXG.AbstractRenderer#updateInternalText
         * @see JXG.AbstractRenderer#updateInternalTextStyle
         */
        updateTextStyle: function (el, doHighlight) {
            var fs, so, sc, css, node,
                ev = el.visProp,
                display = Env.isBrowser ? ev.display : 'internal',
                nodeList = ['rendNode', 'rendNodeTag', 'rendNodeLabel'],
                lenN = nodeList.length,
                fontUnit = Type.evaluate(ev.fontunit),
                cssList, prop, style, cssString,
                styleList = ['cssdefaultstyle', 'cssstyle'],
                lenS = styleList.length;

            if (doHighlight) {
                sc = ev.highlightstrokecolor;
                so = ev.highlightstrokeopacity;
                css = ev.highlightcssclass;
            } else {
                sc = ev.strokecolor;
                so = ev.strokeopacity;
                css = ev.cssclass;
            }

            // This part is executed for all text elements except internal texts in canvas.
            // HTML-texts or internal texts in SVG or VML.
            //            HTML    internal
            //  SVG        +         +
            //  VML        +         +
            //  canvas     +         -
            //  no         -         -
            if ((this.type !== 'no') &&
                (display === 'html' || this.type !== 'canvas')
               ) {
                for (style = 0; style < lenS; style++) {
                    // First set cssString to
                    // ev.cssdefaultstyle of ev.highlightcssdefaultstyle,
                    // then to
                    // ev.cssstyle of ev.highlightcssstyle
                    cssString = Type.evaluate(ev[(doHighlight ? 'highlight' : '') + styleList[style]]);
                    if (cssString !== '' &&
                        el.visPropOld[styleList[style]] !== cssString) {
                        cssList = this._css2js(cssString);
                        for (node = 0; node < lenN; node++) {
                            if (Type.exists(el[nodeList[node]])) {
                                for (prop in cssList) {
                                    if (cssList.hasOwnProperty(prop)) {
                                        el[nodeList[node]].style[cssList[prop].key] = cssList[prop].val;
                                    }
                                }
                            }
                        }
                        el.visPropOld[styleList[style]] = cssString;
                    }
                }

                fs = Type.evaluate(ev.fontsize);
                if (el.visPropOld.fontsize !== fs) {
                    el.needsSizeUpdate = true;
                    try {
                        for (node = 0; node < lenN; node++) {
                            if (Type.exists(el[nodeList[node]])) {
                                el[nodeList[node]].style.fontSize = fs + fontUnit;
                            }
                        }
                    } catch (e) {
                        // IE needs special treatment.
                        for (node = 0; node < lenN; node++) {
                            if (Type.exists(el[nodeList[node]])) {
                                el[nodeList[node]].style.fontSize = fs;
                            }
                        }
                    }
                    el.visPropOld.fontsize = fs;
                }
            }

            this.setObjectTransition(el);
            if (display === 'html' && this.type !== 'no') {
                // Set new CSS class
                if (el.visPropOld.cssclass !== css) {
                    el.rendNode.className = css;
                    el.visPropOld.cssclass = css;
                    el.needsSizeUpdate = true;
                }
                this.setObjectStrokeColor(el, sc, so);
            } else {
                this.updateInternalTextStyle(el, sc, so);
            }

            return this;
        },

        /**
         * Set color and opacity of internal texts.
         * This method is used for Canvas and VML.
         * SVG needs its own version.
         * @private
         * @see JXG.AbstractRenderer#updateTextStyle
         * @see JXG.SVGRenderer#updateInternalTextStyle
         */
        updateInternalTextStyle: function (el, strokeColor, strokeOpacity) {
            this.setObjectStrokeColor(el, strokeColor, strokeOpacity);
        },

        /* **************************
         *    Image related stuff
         * **************************/

        /**
         * Draws an {@link JXG.Image} on a board; This is just a template that has to be implemented by special
         * renderers.
         * @param {JXG.Image} element Reference to the image object that is to be drawn
         * @see Image
         * @see JXG.Image
         * @see JXG.AbstractRenderer#updateImage
         */
        drawImage: function (element) { /* stub */ },

        /**
         * Updates the properties of an {@link JXG.Image} element.
         * @param {JXG.Image} el Reference to an {@link JXG.Image} object, that has to be updated.
         * @see Image
         * @see JXG.Image
         * @see JXG.AbstractRenderer#drawImage
         */
        updateImage: function (el) {
            this.updateRectPrim(el.rendNode, el.coords.scrCoords[1],
                el.coords.scrCoords[2] - el.size[1], el.size[0], el.size[1]);

            this.updateImageURL(el);
            this.transformImage(el, el.transformations);
            this._updateVisual(el, {stroke: true, dash: true}, true);
        },

        /**
         * Multiplication of transformations without updating. That means, at that point it is expected that the
         * matrices contain numbers only. First, the origin in user coords is translated to <tt>(0,0)</tt> in screen
         * coords. Then, the stretch factors are divided out. After the transformations in user coords, the stretch
         * factors are multiplied in again, and the origin in user coords is translated back to its position. This
         * method does not have to be implemented in a new renderer.
         * @param {JXG.GeometryElement} el A JSXGraph element. We only need its board property.
         * @param {Array} transformations An array of JXG.Transformations.
         * @returns {Array} A matrix represented by a two dimensional array of numbers.
         * @see JXG.AbstractRenderer#transformImage
         */
        joinTransforms: function (el, transformations) {
            var i,
                ox = el.board.origin.scrCoords[1],
                oy = el.board.origin.scrCoords[2],
                ux = el.board.unitX,
                uy = el.board.unitY,
                // Translate to 0,0 in screen coords
                /*
                m = [[1, 0, 0], [0, 1, 0], [0, 0, 1]],
                mpre1 =  [[1,   0, 0],
                    [-ox, 1, 0],
                    [-oy, 0, 1]],
                // Scale
                mpre2 =  [[1, 0,     0],
                    [0, 1 / ux,  0],
                    [0, 0, -1 / uy]],
                // Scale back
                mpost2 = [[1, 0,   0],
                    [0, ux,  0],
                    [0, 0, -uy]],
                // Translate back
                mpost1 = [[1,  0, 0],
                    [ox, 1, 0],
                    [oy, 0, 1]],
                */
                len = transformations.length,
                // Translate to 0,0 in screen coords and then scale
                m = [[1,        0,       0],
                     [-ox / ux, 1 / ux,  0],
                     [ oy / uy, 0, -1 / uy]];

            for (i = 0; i < len; i++) {
                //m = Mat.matMatMult(mpre1, m);
                //m = Mat.matMatMult(mpre2, m);
                m = Mat.matMatMult(transformations[i].matrix, m);
                //m = Mat.matMatMult(mpost2, m);
                //m = Mat.matMatMult(mpost1, m);
            }
            // Scale back and then translate back
            m = Mat.matMatMult([[1,   0, 0],
                                [ox, ux, 0],
                                [oy,  0, -uy]], m);
            return m;
        },

        /**
         * Applies transformations on images and text elements. This method is just a stub and has to be implemented in
         * all descendant classes where text and image transformations are to be supported.
         * @param {JXG.Image|JXG.Text} element A {@link JXG.Image} or {@link JXG.Text} object.
         * @param {Array} transformations An array of {@link JXG.Transformation} objects. This is usually the
         * transformations property of the given element <tt>el</tt>.
         */
        transformImage: function (element, transformations) { /* stub */ },

        /**
         * If the URL of the image is provided by a function the URL has to be updated during updateImage()
         * @param {JXG.Image} element Reference to an image object.
         * @see JXG.AbstractRenderer#updateImage
         */
        updateImageURL: function (element) { /* stub */ },

        /**
         * Updates CSS style properties of a {@link JXG.Image} node.
         * In SVGRenderer opacity is the only available style element.
         * This function is called by highlight() and nohighlight().
         * This function works for VML.
         * It does not work for Canvas.
         * SVGRenderer overwrites this method.
         * @param {JXG.Text} el Reference to the {@link JXG.Image} object, that has to be updated.
         * @param {Boolean} doHighlight
         * @see Image
         * @see JXG.Image
         * @see JXG.AbstractRenderer#highlight
         * @see JXG.AbstractRenderer#noHighlight
         */
        updateImageStyle: function (el, doHighlight) {
            el.rendNode.className = Type.evaluate(doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass);
        },

        drawForeignObject: function (el) { /* stub */ },

        updateForeignObject: function(el) { /* stub */ },

        /* **************************
         * Render primitive objects
         * **************************/

        /**
         * Appends a node to a specific layer level. This is just an abstract method and has to be implemented
         * in all renderers that want to use the <tt>createPrim</tt> model to draw.
         * @param {Node} node A DOM tree node.
         * @param {Number} level The layer the node is attached to. This is the index of the layer in
         * {@link JXG.SVGRenderer#layer} or the <tt>z-index</tt> style property of the node in VMLRenderer.
         */
        appendChildPrim: function (node, level) { /* stub */ },

        /**
         * Stores the rendering nodes. This is an abstract method which has to be implemented in all renderers that use
         * the <tt>createPrim</tt> method.
         * @param {JXG.GeometryElement} element A JSXGraph element.
         * @param {String} type The XML node name. Only used in VMLRenderer.
         */
        appendNodesToElement: function (element, type) { /* stub */ },

        /**
         * Creates a node of a given type with a given id.
         * @param {String} type The type of the node to create.
         * @param {String} id Set the id attribute to this.
         * @returns {Node} Reference to the created node.
         */
        createPrim: function (type, id) {
            /* stub */
            return null;
        },

        /**
         * Removes an element node. Just a stub.
         * @param {Node} node The node to remove.
         */
        remove: function (node) { /* stub */ },

        /**
         * Can be used to create the nodes to display arrows. This is an abstract method which has to be implemented
         * in any descendant renderer.
         * @param {JXG.GeometryElement} element The element the arrows are to be attached to.
         * @param {Object} arrowData Data concerning possible arrow heads
         *
         */
        makeArrows: function (element, arrowData) { /* stub */ },

        /**
         * Updates width of an arrow DOM node. Used in
         * @param {Node} node The arrow node.
         * @param {Number} width
         * @param {Node} parentNode Used in IE only
         */
        _setArrowWidth: function(node, width, parentNode) { /* stub */},

        /**
         * Updates an ellipse node primitive. This is an abstract method which has to be implemented in all renderers
         * that use the <tt>createPrim</tt> method.
         * @param {Node} node Reference to the node.
         * @param {Number} x Centre X coordinate
         * @param {Number} y Centre Y coordinate
         * @param {Number} rx The x-axis radius.
         * @param {Number} ry The y-axis radius.
         */
        updateEllipsePrim: function (node, x, y, rx, ry) { /* stub */ },

        /**
         * Refreshes a line node. This is an abstract method which has to be implemented in all renderers that use
         * the <tt>createPrim</tt> method.
         * @param {Node} node The node to be refreshed.
         * @param {Number} p1x The first point's x coordinate.
         * @param {Number} p1y The first point's y coordinate.
         * @param {Number} p2x The second point's x coordinate.
         * @param {Number} p2y The second point's y coordinate.
         * @param {JXG.Board} board
         */
        updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) { /* stub */ },

        /**
         * Updates a path element. This is an abstract method which has to be implemented in all renderers that use
         * the <tt>createPrim</tt> method.
         * @param {Node} node The path node.
         * @param {String} pathString A string formatted like e.g. <em>'M 1,2 L 3,1 L5,5'</em>. The format of the string
         * depends on the rendering engine.
         * @param {JXG.Board} board Reference to the element's board.
         */
        updatePathPrim: function (node, pathString, board) { /* stub */ },

        /**
         * Builds a path data string to draw a point with a face other than <em>rect</em> and <em>circle</em>. Since
         * the format of such a string usually depends on the renderer this method
         * is only an abstract method. Therefore, it has to be implemented in the descendant renderer itself unless
         * the renderer does not use the createPrim interface but the draw* interfaces to paint.
         * @param {JXG.Point} element The point element
         * @param {Number} size A positive number describing the size. Usually the half of the width and height of
         * the drawn point.
         * @param {String} type A string describing the point's face. This method only accepts the shortcut version of
         * each possible face: <tt>x, +, <>, ^, v, >, < </tt>
         */
        updatePathStringPoint: function (element, size, type) { /* stub */ },

        /**
         * Builds a path data string from a {@link JXG.Curve} element. Since the path data strings heavily depend on the
         * underlying rendering technique this method is just a stub. Although such a path string is of no use for the
         * CanvasRenderer, this method is used there to draw a path directly.
         * @param element
         */
        updatePathStringPrim: function (element) { /* stub */ },

        /**
         * Builds a path data string from a {@link JXG.Curve} element such that the curve looks like hand drawn. Since
         * the path data strings heavily depend on the underlying rendering technique this method is just a stub.
         * Although such a path string is of no use for the CanvasRenderer, this method is used there to draw a path
         * directly.
         * @param element
         */
        updatePathStringBezierPrim: function (element) { /* stub */ },


        /**
         * Update a polygon primitive.
         * @param {Node} node
         * @param {JXG.Polygon} element A JSXGraph element of type {@link JXG.Polygon}
         */
        updatePolygonPrim: function (node, element) { /* stub */ },

        /**
         * Update a rectangle primitive. This is used only for points with face of type 'rect'.
         * @param {Node} node The node yearning to be updated.
         * @param {Number} x x coordinate of the top left vertex.
         * @param {Number} y y coordinate of the top left vertex.
         * @param {Number} w Width of the rectangle.
         * @param {Number} h The rectangle's height.
         */
        updateRectPrim: function (node, x, y, w, h) { /* stub */ },

        /* **************************
         *  Set Attributes
         * **************************/

        /**
         * Sets a node's attribute.
         * @param {Node} node The node that is to be updated.
         * @param {String} key Name of the attribute.
         * @param {String} val New value for the attribute.
         */
        setPropertyPrim: function (node, key, val) { /* stub */ },

        setTabindex: function(element) {
            var val;
            if (element.board.attr.keyboard.enabled && Type.exists(element.rendNode)) {
                val = Type.evaluate(element.visProp.tabindex);
                if (!element.visPropCalc.visible || Type.evaluate(element.visProp.fixed)) {
                    val = null;
                }
                if (val !== element.visPropOld.tabindex) {
                    element.rendNode.setAttribute('tabindex', val);
                    element.visPropOld.tabindex = val;
                }
            }
        },

        /**
         * Shows or hides an element on the canvas; Only a stub, requires implementation in the derived renderer.
         * @param {JXG.GeometryElement} element Reference to the object that has to appear.
         * @param {Boolean} value true to show the element, false to hide the element.
         */
        display: function (element, value) {
            if (element) {
                element.visPropOld.visible = value;
            }
        },

        /**
         * Shows a hidden element on the canvas; Only a stub, requires implementation in the derived renderer.
         *
         * Please use JXG.AbstractRenderer#display instead
         * @param {JXG.GeometryElement} element Reference to the object that has to appear.
         * @see JXG.AbstractRenderer#hide
         * @deprecated
         */
        show: function (element) { /* stub */ },

        /**
         * Hides an element on the canvas; Only a stub, requires implementation in the derived renderer.
         *
         * Please use JXG.AbstractRenderer#display instead
         * @param {JXG.GeometryElement} element Reference to the geometry element that has to disappear.
         * @see JXG.AbstractRenderer#show
         * @deprecated
         */
        hide: function (element) { /* stub */ },

        /**
         * Sets the buffering as recommended by SVGWG. Until now only Opera supports this and will be ignored by other
         * browsers. Although this feature is only supported by SVG we have this method in {@link JXG.AbstractRenderer}
         * because it is called from outside the renderer.
         * @param {Node} node The SVG DOM Node which buffering type to update.
         * @param {String} type Either 'auto', 'dynamic', or 'static'. For an explanation see
         *   {@link http://www.w3.org/TR/SVGTiny12/painting.html#BufferedRenderingProperty}.
         */
        setBuffering: function (node, type) { /* stub */ },

        /**
         * Sets an element's dash style.
         * @param {JXG.GeometryElement} element An JSXGraph element.
         */
        setDashStyle: function (element) { /* stub */ },

        /**
         * Puts an object into draft mode, i.e. it's visual appearance will be changed. For GEONE<sub>x</sub>T backwards
         * compatibility.
         * @param {JXG.GeometryElement} el Reference of the object that is in draft mode.
         */
        setDraft: function (el) {
            if (!Type.evaluate(el.visProp.draft)) {
                return;
            }
            var draftColor = el.board.options.elements.draft.color,
                draftOpacity = el.board.options.elements.draft.opacity;

            this.setObjectTransition(el);
            if (el.type === Const.OBJECT_TYPE_POLYGON) {
                this.setObjectFillColor(el, draftColor, draftOpacity);
            } else {
                if (el.elementClass === Const.OBJECT_CLASS_POINT) {
                    this.setObjectFillColor(el, draftColor, draftOpacity);
                } else {
                    this.setObjectFillColor(el, 'none', 0);
                }
                this.setObjectStrokeColor(el, draftColor, draftOpacity);
                this.setObjectStrokeWidth(el, el.board.options.elements.draft.strokeWidth);
            }
        },

        /**
         * Puts an object from draft mode back into normal mode.
         * @param {JXG.GeometryElement} el Reference of the object that no longer is in draft mode.
         */
        removeDraft: function (el) {
            this.setObjectTransition(el);
            if (el.type === Const.OBJECT_TYPE_POLYGON) {
                this.setObjectFillColor(el,
                    el.visProp.fillcolor,
                    el.visProp.fillopacity);
            } else {
                if (el.type === Const.OBJECT_CLASS_POINT) {
                    this.setObjectFillColor(el,
                        el.visProp.fillcolor,
                        el.visProp.fillopacity);
                }
                this.setObjectStrokeColor(el, el.visProp.strokecolor, el.visProp.strokeopacity);
                this.setObjectStrokeWidth(el, el.visProp.strokewidth);
            }
        },

        /**
         * Sets up nodes for rendering a gradient fill.
         * @param element
         */
        setGradient: function (element) { /* stub */ },

        /**
         * Updates the gradient fill.
         * @param {JXG.GeometryElement} element An JSXGraph element with an area that can be filled.
         */
        updateGradient: function (element) { /* stub */ },

        /**
         * Sets the transition duration (in milliseconds) for fill color and stroke
         * color and opacity.
         * @param {JXG.GeometryElement} element Reference of the object that wants a
         *         new transition duration.
         * @param {Number} duration (Optional) duration in milliseconds. If not given,
         *        element.visProp.transitionDuration is taken. This is the default.
         */
        setObjectTransition: function (element, duration) { /* stub */ },

        /**
         * Sets an objects fill color.
         * @param {JXG.GeometryElement} element Reference of the object that wants a new fill color.
         * @param {String} color Color in a HTML/CSS compatible format. If you don't want any fill color at all, choose
         * 'none'.
         * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1.
         */
        setObjectFillColor: function (element, color, opacity) { /* stub */ },

        /**
         * Changes an objects stroke color to the given color.
         * @param {JXG.GeometryElement} element Reference of the {@link JXG.GeometryElement} that gets a new stroke
         * color.
         * @param {String} color Color value in a HTML compatible format, e.g. <strong>#00ff00</strong> or
         * <strong>green</strong> for green.
         * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1.
         */
        setObjectStrokeColor: function (element, color, opacity) { /* stub */ },

        /**
         * Sets an element's stroke width.
         * @param {JXG.GeometryElement} element Reference to the geometry element.
         * @param {Number} width The new stroke width to be assigned to the element.
         */
        setObjectStrokeWidth: function (element, width) { /* stub */ },

        /**
         * Sets the shadow properties to a geometry element. This method is only a stub, it is implemented in the actual
         * renderers.
         * @param {JXG.GeometryElement} element Reference to a geometry object, that should get a shadow
         */
        setShadow: function (element) { /* stub */ },

        /**
         * Highlights an object, i.e. changes the current colors of the object to its highlighting colors
         * and highlighting stroke width.
         * @param {JXG.GeometryElement} el Reference of the object that will be highlighted.
         * @returns {JXG.AbstractRenderer} Reference to the renderer
         * @see JXG.AbstractRenderer#updateTextStyle
         */
        highlight: function (el) {
            var i, ev = el.visProp, sw;

            this.setObjectTransition(el);
            if (!ev.draft) {
                if (el.type === Const.OBJECT_TYPE_POLYGON) {
                    this.setObjectFillColor(el,
                        ev.highlightfillcolor,
                        ev.highlightfillopacity);
                    for (i = 0; i < el.borders.length; i++) {
                        this.setObjectStrokeColor(el.borders[i],
                            el.borders[i].visProp.highlightstrokecolor,
                            el.borders[i].visProp.highlightstrokeopacity);
                    }
                } else {
                    if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
                        this.updateTextStyle(el, true);
                    } else if (el.type === Const.OBJECT_TYPE_IMAGE) {
                        this.updateImageStyle(el, true);
                        this.setObjectFillColor(el,
                            ev.highlightfillcolor,
                            ev.highlightfillopacity);
                    } else {
                        this.setObjectStrokeColor(el, ev.highlightstrokecolor, ev.highlightstrokeopacity);
                        this.setObjectFillColor(el,
                            ev.highlightfillcolor,
                            ev.highlightfillopacity);
                    }
                }
                if (ev.highlightstrokewidth) {
                    sw = Math.max(Type.evaluate(ev.highlightstrokewidth), Type.evaluate(ev.strokewidth));
                    this.setObjectStrokeWidth(el, sw);
                    if (el.elementClass === Const.OBJECT_CLASS_LINE || el.elementClass === Const.OBJECT_CLASS_CURVE) {
                        this.updatePathWithArrowHeads(el, true);
                    }
                }
            }

            return this;
        },

        /**
         * Uses the normal colors of an object, i.e. the opposite of {@link JXG.AbstractRenderer#highlight}.
         * @param {JXG.GeometryElement} el Reference of the object that will get its normal colors.
         * @returns {JXG.AbstractRenderer} Reference to the renderer
         * @see JXG.AbstractRenderer#updateTextStyle
         */
        noHighlight: function (el) {
            var i, ev = el.visProp, sw;

            this.setObjectTransition(el);
            if (!Type.evaluate(el.visProp.draft)) {
                if (el.type === Const.OBJECT_TYPE_POLYGON) {
                    this.setObjectFillColor(el,
                        ev.fillcolor,
                        ev.fillopacity);
                    for (i = 0; i < el.borders.length; i++) {
                        this.setObjectStrokeColor(el.borders[i],
                            el.borders[i].visProp.strokecolor,
                            el.borders[i].visProp.strokeopacity);
                    }
                } else {
                    if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
                        this.updateTextStyle(el, false);
                    } else if (el.type === Const.OBJECT_TYPE_IMAGE) {
                        this.updateImageStyle(el, false);
                        this.setObjectFillColor(el,
                            ev.fillcolor,
                            ev.fillopacity);
                    } else {
                        this.setObjectStrokeColor(el,
                            ev.strokecolor,
                            ev.strokeopacity);
                        this.setObjectFillColor(el,
                            ev.fillcolor,
                            ev.fillopacity);
                    }
                }

                sw = Type.evaluate(ev.strokewidth);
                this.setObjectStrokeWidth(el, sw);
                if (el.elementClass === Const.OBJECT_CLASS_LINE || el.elementClass === Const.OBJECT_CLASS_CURVE) {
                    this.updatePathWithArrowHeads(el, false);
                }

            }

            return this;
        },

        /* **************************
         * renderer control
         * **************************/

        /**
         * Stop redraw. This method is called before every update, so a non-vector-graphics based renderer can use this
         * method to delete the contents of the drawing panel. This is an abstract method every descendant renderer
         * should implement, if appropriate.
         * @see JXG.AbstractRenderer#unsuspendRedraw
         */
        suspendRedraw: function () { /* stub */ },

        /**
         * Restart redraw. This method is called after updating all the rendering node attributes.
         * @see JXG.AbstractRenderer#suspendRedraw
         */
        unsuspendRedraw: function () { /* stub */ },

        /**
         * The tiny zoom bar shown on the bottom of a board (if showNavigation on board creation is true).
         * @param {JXG.Board} board Reference to a JSXGraph board.
         * @param {Object} attr Attributes of the navigation bar
         *
         */
        drawZoomBar: function (board, attr) {
            var doc,
                node,
                cancelbubble = function (e) {
                    if (!e) {
                        e = window.event;
                    }

                    if (e.stopPropagation) {
                        // Non IE<=8
                        e.stopPropagation();
                    } else {
                        e.cancelBubble = true;
                    }
                },
                createButton = function (label, handler) {
                    var button;

                    button = doc.createElement('span');
                    node.appendChild(button);
                    button.appendChild(doc.createTextNode(label));

                    // Style settings are superseded by adding the CSS class below
                    button.style.paddingLeft = '7px';
                    button.style.paddingRight = '7px';

                    if (button.classList !== undefined) { // classList not available in IE 9
                        button.classList.add('JXG_navigation_button');
                    }
                    // button.setAttribute('tabindex', 0);

                    // Highlighting is now done with CSS
                    // Env.addEvent(button, 'mouseover', function () {
                    //     this.style.backgroundColor = attr.highlightfillcolor;
                    // }, button);
                    // Env.addEvent(button, 'mouseover', function () {
                    //     this.style.backgroundColor = attr.highlightfillcolor;
                    // }, button);
                    // Env.addEvent(button, 'mouseout', function () {
                    //     this.style.backgroundColor = attr.fillcolor;
                    // }, button);

                    Env.addEvent(button, 'click', function(e) { (Type.bind(handler, board))(); return false; }, board);
                    // prevent the click from bubbling down to the board
                    Env.addEvent(button, 'mouseup', cancelbubble, board);
                    Env.addEvent(button, 'mousedown', cancelbubble, board);
                    Env.addEvent(button, 'touchend', cancelbubble, board);
                    Env.addEvent(button, 'touchstart', cancelbubble, board);
                };

            if (Env.isBrowser && this.type !== 'no') {
                doc = board.containerObj.ownerDocument;
                node = doc.createElement('div');

                node.setAttribute('id', board.containerObj.id + '_navigationbar');

                // Style settings are superseded by adding the CSS class below
                node.style.color = attr.strokecolor;
                node.style.backgroundColor = attr.fillcolor;
                node.style.padding = attr.padding;
                node.style.position = attr.position;
                node.style.fontSize = attr.fontsize;
                node.style.cursor = attr.cursor;
                node.style.zIndex = attr.zindex;
                board.containerObj.appendChild(node);
                node.style.right = attr.right;
                node.style.bottom = attr.bottom;

                if (node.classList !== undefined) { // classList not available in IE 9
                    node.classList.add('JXG_navigation');
                }
                // For XHTML we need unicode instead of HTML entities

                if (board.attr.showfullscreen) {
                    createButton(board.attr.fullscreen.symbol, function () {
                        board.toFullscreen(board.attr.fullscreen.id);
                    });
                }

                if (board.attr.showscreenshot) {
                    createButton(board.attr.screenshot.symbol, function () {
                        window.setTimeout(function() {
                            board.renderer.screenshot(board, '', false);
                        }, 330);
                    });
                }

                if (board.attr.showreload) {
                    // full reload circle: \u27F2
                    // the board.reload() method does not exist during the creation
                    // of this button. That's why this anonymous function wrapper is required.
                    createButton('\u21BB', function () {
                        board.reload();
                    });
                }

                if (board.attr.showcleartraces) {
                    // clear traces symbol (otimes): \u27F2
                    createButton('\u2297', function () {
                        board.clearTraces();
                    });
                }

                if (board.attr.shownavigation) {
                    if (board.attr.showzoom) {
                        createButton('\u2013', board.zoomOut);
                        createButton('o', board.zoom100);
                        createButton('+', board.zoomIn);
                    }
                    createButton('\u2190', board.clickLeftArrow);
                    createButton('\u2193', board.clickUpArrow);
                    createButton('\u2191', board.clickDownArrow);
                    createButton('\u2192', board.clickRightArrow);
                }
            }
        },

        /**
         * Wrapper for getElementById for maybe other renderers which elements are not directly accessible by DOM
         * methods like document.getElementById().
         * @param {String} id Unique identifier for element.
         * @returns {Object} Reference to a JavaScript object. In case of SVG/VMLRenderer it's a reference to a SVG/VML
         * node.
         */
        getElementById: function (id) {
            if (Type.exists(this.container)) {
                return this.container.ownerDocument.getElementById(this.container.id + '_' + id);
            }
            return '';
        },

        /**
         * Remove an element and provide a function that inserts it into its original position. This method
         * is taken from this article {@link https://developers.google.com/speed/articles/javascript-dom}.
         * @author KeeKim Heng, Google Web Developer
         * @param {Element} el The element to be temporarily removed
         * @returns {Function} A function that inserts the element into its original position
         */
        removeToInsertLater: function (el) {
            var parentNode = el.parentNode,
                nextSibling = el.nextSibling;

            if (parentNode === null) {
                return;
            }
            parentNode.removeChild(el);

            return function () {
                if (nextSibling) {
                    parentNode.insertBefore(el, nextSibling);
                } else {
                    parentNode.appendChild(el);
                }
            };
        },

        /**
         * Resizes the rendering element
         * @param {Number} w New width
         * @param {Number} h New height
         */
        resize: function (w, h) { /* stub */},

        /**
         * Create crosshair elements (Fadenkreuz) for presentations.
         * @param {Number} n Number of crosshairs.
         */
        createTouchpoints: function (n) {},

        /**
         * Show a specific crosshair.
         * @param {Number} i Number of the crosshair to show
         */
        showTouchpoint: function (i) {},

        /**
         * Hide a specific crosshair.
         * @param {Number} i Number of the crosshair to show
         */
        hideTouchpoint: function (i) {},

        /**
         * Move a specific crosshair.
         * @param {Number} i Number of the crosshair to show
         * @param {Array} pos New positon in screen coordinates
         */
        updateTouchpoint: function (i, pos) {},

        /**
         * Convert SVG construction to base64 encoded SVG data URL.
         * Only available on SVGRenderer.
         *
         * @see JXG.SVGRenderer#dumpToDataURI
         */
        dumpToDataURI: function (_ignoreTexts) {},

        /**
         * Convert SVG construction to canvas.
         * Only available on SVGRenderer.
         *
         * @see JXG.SVGRenderer#dumpToCanvas
         */
        dumpToCanvas: function (canvasId, w, h, _ignoreTexts) {},

        /**
         * Display SVG image in html img-tag which enables
         * easy download for the user.
         *
         * See JXG.SVGRenderer#screenshot
         */
        screenshot: function (board) {},

        /**
         * Move element into new layer. This is trivial for canvas, but needs more effort in SVG.
         * Does not work dynamically, i.e. if level is a function.
         *
         * @param {JXG.GeometryElement} el Element which is put into different layer
         * @param {Number} value Layer number
         * @private
         */
        setLayer: function(el, level) {}

    });

    return JXG.AbstractRenderer;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG:true, define: true, ActiveXObject:true, jxgBinFileReader:true, DOMParser:true, XMLHttpRequest:true, document:true, navigator:true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/env
 utils/type
 utils/encoding
 utils/base64
 */

define('reader/file',[
    'jxg', 'utils/env', 'utils/type', 'utils/encoding', 'utils/base64'
], function (JXG, Env, Type, Encoding, Base64) {

    "use strict";

    /**
     * The FileReader object bundles the file input capabilities of JSXGraph.
     */
    JXG.FileReader = {
        /**
         *
         * @param {String} url
         * @param {JXG.Board} board
         * @param {String} format
         * @param {Boolean} async
         * @param {Function} callback
         *
         * @private
         */
        handleRemoteFile: function(url, board, format, async, encoding, callback) {
            var request = false;

            try {
                request = new XMLHttpRequest();
                if (format.toLowerCase() === 'raw') {
                    request.overrideMimeType('text/plain; charset=' + encoding);
                } else {
                    request.overrideMimeType('text/xml; charset=' + encoding);
                }
            } catch (e) {
                try {
                    request = new ActiveXObject("Msxml2.XMLHTTP");
                } catch (ex) {
                    try {
                        request = new ActiveXObject("Microsoft.XMLHTTP");
                    } catch (exc) {
                        request = false;
                    }
                }
            }
            if (!request) {
                JXG.debug("AJAX not activated!");
                return;
            }

            request.open("GET", url, async);
            if (format.toLowerCase() === 'raw') {
                this.cbp = function () {
                    var req = request;
                    if (req.readyState === 4) {
                        board(req.responseText);
                    }
                };
            } else {
                this.cbp = function () {
                    var req = request,
                        text = '';

                    if (req.readyState === 4) {
                        // Hack for ancient IEs:
                        // We use the Visual Basic stuff from below.
                        if (Type.exists(req.responseStream) &&
                                // PK: zip, geogebra
                                // 31: gzip, cinderella
                                (req.responseText.slice(0, 2) === "PK" ||
                                Encoding.asciiCharCodeAt(req.responseText.slice(0, 1), 0) === 31)) {

                            // After this, text contains the binary? zip-compressed string
                            text = Base64.decode(jxgBinFileReader(req));
                        } else {
                            // This is for all browsers except ancient IEs.
                            text = req.responseText;
                            // console.log(text);
                        }
                        this.parseString(text, board, format, callback);
                    }
                };
            }

            this.cb = Type.bind(this.cbp, this);
            // Old style
            request.onreadystatechange = this.cb;

            try {
                request.send(null);
            } catch (ex2) {
                throw new Error("JSXGraph: A problem occurred while trying to read remote file '" + url + "'.");
            }
        },

        /**
         *
         * @param {Blob} url The Blob or File from which to read
         * @param {JXG.Board} board
         * @param {String} format
         * @param {Boolean} async
         * @param {Function} callback
         *
         * @private
         */
        handleLocalFile: function(url, board, format, async, encoding, callback) {
            if (!Type.exists(async)) {
                async = true;
            }

            if (format.toLowerCase() === 'raw') {
                this.cbp = function (e) {
                    board(e.target.result);
                };
            } else {
                this.cbp = function (e) {
                    var text = e.target.result;
                    //console.log(text);
                    this.parseString(text, board, format, callback);
                };
            }

            this.cb = Type.bind(this.cbp, this);

            var reader = new FileReader();
            reader.onload = this.cb;
            if (format.toLowerCase() === 'raw') {
                reader.readAsText(url);
            } else {
                reader.readAsText(url, encoding);
            }
        },

        /**
         * Opens a file using the given URL and passes the contents to {@link JXG.FileReader#parseString}
         * @param {String} url
         * @param {JXG.Board|function} board Either a board or in case <tt>format</tt> equals 'raw' this has to be a callback function.
         * @param {String} format The expected file format. Possible values are <dl>
         * <dt>raw</dt><dd>Raw text file. In this case <tt>board</tt> has to be a callback function.</dd>
         * <dt>geonext</dt><dd>Geonext File <a href="http://www.geonext.de">http://www.geonext.de</a></dd>
         * <dt>intergeo</dt><dd>Intergeo file format <a href="http://www.i2geo.net">http://www.i2geo.net</a></dd>
         * <dt>tracenpoche</dt><dd>Tracenpoche construction <a href="http://www.tracenpoche.net">http://www.tracenpoche.net</a></dd>
         * <dt>graph</dt><dd>Graph file</dd>
         * <dt>digraph</dt><dd>DiGraph file</dd>
         * <dt>geogebra</dt><dd>Geogebra File <a href="http://www.geogebra.org">http://www.geogebra.org</a></dd>
         * <dl><dt>cdy or cinderella</dt><dd>Cinderella (<a href="http://www.cinderella.de/">http://www.cinderella.de</a></dd>
         * </dl>
         * @param {Boolean} async Call ajax asynchonously.
         * @param {function} callback A function that is run when the board is ready.
         */
        parseFileContent: function (url, board, format, async, encoding, callback) {
            if (Type.isString(url) || FileReader === undefined) {
                this.handleRemoteFile(url, board, format, async, encoding, callback);
            } else {
                this.handleLocalFile(url, board, format, async, encoding, callback);
            }
        },

        /**
         * Parses a given string according to the file format given in format.
         * @param {String} str Contents of the file.
         * @param {JXG.Board} board The board the construction in the file should be loaded in.
         * @param {String} format Possible values are <dl>
         * <dt>raw</dt><dd>Raw text file. In this case <tt>board</tt> has to be a callback function.</dd>
         * <dt>geonext</dt><dd>Geonext File <a href="http://www.geonext.de">http://www.geonext.de</a></dd>
         * <dt>intergeo</dt><dd>Intergeo file format <a href="http://www.i2geo.net">http://www.i2geo.net</a></dd>
         * <dt>tracenpoche</dt><dd>Tracenpoche construction <a href="http://www.tracenpoche.net">http://www.tracenpoche.net</a></dd>
         * <dt>graph</dt><dd>Graph file</dd>
         * <dt>digraph</dt><dd>DiGraph file</dd>
         * <dt>geogebra</dt><dd>Geogebra File <a href="http://www.geogebra.org">http://www.geogebra.org</a></dd>
         * <dl><dt>cdy or cinderella</dt><dd>Cinderella (<a href="http://www.cinderella.de/">http://www.cinderella.de</a></dd>
         * </dl>
         * @param {function} callback
         */
        parseString: function (str, board, format, callback) {
            var Reader,
                read;

            format = format.toLowerCase();
            Reader = JXG.readers[format];

            if (Type.exists(Reader)) {
                read = new Reader(board, str);
                read.read();
            } else if (format === 'jessiecode') {

            } else {
                throw new Error('JSXGraph: There is no reader available for \'' + format + '\'.');
            }

            if (Type.isFunction(callback)) {
                callback(board);
            }
        }
    };

    // The following code is vbscript. This is a workaround to enable binary data downloads via AJAX in
    // Microsoft Internet Explorer.

    /*jslint evil:true, es5:true, white:true*/
    /*jshint multistr:true*/
    if (!Env.isMetroApp() && Env.isBrowser && typeof navigator === 'object' && /msie/i.test(navigator.userAgent) && !/opera/i.test(navigator.userAgent) && document && document.write) {
        document.write('<script type="text/vbscript">\n\
Function Base64Encode(inData)\n\
  Const Base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"\n\
  Dim cOut, sOut, I\n\
  For I = 1 To LenB(inData) Step 3\n\
    Dim nGroup, pOut, sGroup\n\
    nGroup = &H10000 * AscB(MidB(inData, I, 1)) + _\n\
      &H100 * MyASC(MidB(inData, I + 1, 1)) + MyASC(MidB(inData, I + 2, 1))\n\
    nGroup = Oct(nGroup)\n\
    nGroup = String(8 - Len(nGroup), "0") & nGroup\n\
    pOut = Mid(Base64, CLng("&o" & Mid(nGroup, 1, 2)) + 1, 1) + _\n\
      Mid(Base64, CLng("&o" & Mid(nGroup, 3, 2)) + 1, 1) + _\n\
      Mid(Base64, CLng("&o" & Mid(nGroup, 5, 2)) + 1, 1) + _\n\
      Mid(Base64, CLng("&o" & Mid(nGroup, 7, 2)) + 1, 1)\n\
    sOut = sOut + pOut\n\
  Next\n\
  Select Case LenB(inData) Mod 3\n\
    Case 1: \'8 bit final\n\
      sOut = Left(sOut, Len(sOut) - 2) + "=="\n\
    Case 2: \'16 bit final\n\
      sOut = Left(sOut, Len(sOut) - 1) + "="\n\
  End Select\n\
  Base64Encode = sOut\n\
End Function\n\
\n\
Function MyASC(OneChar)\n\
  If OneChar = "" Then MyASC = 0 Else MyASC = AscB(OneChar)\n\
End Function\n\
\n\
Function jxgBinFileReader(xhr)\n\
    Dim byteString\n\
    Dim b64String\n\
    Dim i\n\
    byteString = xhr.responseBody\n\
    ReDim byteArray(LenB(byteString))\n\
    For i = 1 To LenB(byteString)\n\
        byteArray(i-1) = AscB(MidB(byteString, i, 1))\n\
    Next\n\
    b64String = Base64Encode(byteString)\n\
    jxgBinFileReader = b64String\n\
End Function\n\
</script>\n');
    }

    return JXG.FileReader;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 utils/type
 math/math
 math/geometry
 */

define('parser/geonext',[
    'jxg', 'base/constants', 'utils/type'
], function (JXG, Const, Type) {

    "use strict";

    /**
     * Parser helper routines. The methods in here are for parsing expressions in Geonext Syntax.
     * @namespace
     */
    JXG.GeonextParser = {
        /**
         * Converts expression of the form <i>leftop^rightop</i> into <i>Math.pow(leftop,rightop)</i>.
         * @param {String} te Expression of the form <i>leftop^rightop</i>
         * @returns {String} Converted expression.
         */
        replacePow: function (te) {
            var count, pos, c, previousIndex,
                leftop, rightop, pre, p, left, i, right, expr;

            // delete all whitespace immediately before and after all ^ operators
            te = te.replace(/(\s*)\^(\s*)/g, '^');

            //  Loop over all ^ operators
            i = te.indexOf('^');
            previousIndex = -1;

            while (i >= 0 && i < te.length - 1) {
                if (previousIndex === i) {
                    throw new Error("JSXGraph: Error while parsing expression '" + te + "'");
                }
                previousIndex = i;

                // left and right are the substrings before, resp. after the ^ character
                left = te.slice(0, i);
                right = te.slice(i + 1);

                // If there is a ")" immediately before the ^ operator, it can be the end of a
                // (i) term in parenthesis
                // (ii) function call
                // (iii) method  call
                // In either case, first the corresponding opening parenthesis is searched.
                // This is the case, when count==0
                if (left.charAt(left.length - 1) === ')') {
                    count = 1;
                    pos = left.length - 2;

                    while (pos >= 0 && count > 0) {
                        c = left.charAt(pos);
                        if (c === ')') {
                            count++;
                        } else if (c === '(') {
                            count -= 1;
                        }
                        pos -= 1;
                    }

                    if (count === 0) {
                        // Now, we have found the opning parenthesis and we have to look
                        // if it is (i), or (ii), (iii).
                        leftop = '';
                        // Search for F or p.M before (...)^
                        pre = left.substring(0, pos + 1);
                        p = pos;
                        while (p >= 0 && pre.substr(p, 1).match(/([\w.]+)/)) {
                            leftop = RegExp.$1 + leftop;
                            p -= 1;
                        }
                        leftop += left.substring(pos + 1, left.length);
                        leftop = leftop.replace(/([()+*%^\-/\][])/g, '\\$1');
                    } else {
                        throw new Error("JSXGraph: Missing '(' in expression");
                    }
                } else {
                    // Otherwise, the operand has to be a constant (or variable).
                    leftop = '[\\w\\.]+'; // former: \\w\\.
                }

                // To the right of the ^ operator there also may be a function or method call
                // or a term in parenthesis. Alos, ere we search for the closing
                // parenthesis.
                if (right.match(/^([\w.]*\()/)) {
                    count = 1;
                    pos = RegExp.$1.length;

                    while (pos < right.length && count > 0) {
                        c = right.charAt(pos);

                        if (c === ')') {
                            count -= 1;
                        } else if (c === '(') {
                            count += 1;
                        }
                        pos += 1;
                    }

                    if (count === 0) {
                        rightop = right.substring(0, pos);
                        rightop = rightop.replace(/([()+*%^\-/[\]])/g, '\\$1');
                    } else {
                        throw new Error("JSXGraph: Missing ')' in expression");
                    }
                } else {
                    // Otherwise, the operand has to be a constant (or variable).
                    rightop = '[\\w\\.]+';
                }
                // Now, we have the two operands and replace ^ by JXG.Math.pow
                expr = new RegExp('(' + leftop + ')\\^(' + rightop + ')');
                //te = te.replace(expr, 'JXG.Math.pow($1,$2)');
                te = te.replace(expr, 'pow($1,$2)');
                i = te.indexOf('^');
            }

            return te;
        },

        /**
         * Converts expression of the form <i>If(a,b,c)</i> into <i>(a)?(b):(c)/i>.
         * @param {String} te Expression of the form <i>If(a,b,c)</i>
         * @returns {String} Converted expression.
         */
        replaceIf: function (te) {
            var left, right,
                i, pos, count, k1, k2, c, meat,
                s = '',
                first = null,
                second = null,
                third = null;

            i = te.indexOf('If(');
            if (i < 0) {
                return te;
            }

            // "" means not defined. Here, we replace it by 0
            te = te.replace(/""/g, '0');
            while (i >= 0) {
                left = te.slice(0, i);
                right = te.slice(i + 3);

                // Search the end of the If() command and take out the meat
                count = 1;
                pos = 0;
                k1 = -1;
                k2 = -1;

                while (pos < right.length && count > 0) {
                    c = right.charAt(pos);

                    if (c === ')') {
                        count -= 1;
                    } else if (c === '(') {
                        count += 1;
                    } else if (c === ',' && count === 1) {
                        if (k1 < 0) {
                            // first komma
                            k1 = pos;
                        } else {
                            // second komma
                            k2 = pos;
                        }
                    }
                    pos += 1;
                }
                meat = right.slice(0, pos - 1);
                right = right.slice(pos);

                // Test the two kommas
                if (k1 < 0) {
                    // , missing
                    return '';
                }

                if (k2 < 0) {
                    // , missing
                    return '';
                }

                first = meat.slice(0, k1);
                second = meat.slice(k1 + 1, k2);
                third = meat.slice(k2 + 1);

                // Recurse
                first = this.replaceIf(first);
                second = this.replaceIf(second);
                third = this.replaceIf(third);

                s += left + '((' + first + ')?' + '(' + second + '):(' + third + '))';
                te = right;
                first = null;
                second = null;
                i = te.indexOf('If(');
            }
            s += right;
            return s;
        },

        /**
         * Replace an element's name in terms by an element's id.
         * @param {String} term Term containing names of elements.
         * @param {JXG.Board} board Reference to the board the elements are on.
         * @param {Boolean} [jc=false] If true, all id's will be surrounded by <tt>$('</tt> and <tt>')</tt>.
         * @returns {String} The same string with names replaced by ids.
         **/
        replaceNameById: function (term, board, jc) {
            var end, elName, el, i,
                pos = 0,
                funcs = ['X', 'Y', 'L', 'V'],

                printId = function (id) {
                    if (jc) {
                        return '$(\'' + id + '\')';
                    }

                    return id;
                };

            // Find X(el), Y(el), ...
            // All functions declared in funcs
            for (i = 0; i < funcs.length; i++) {
                pos = term.indexOf(funcs[i] + '(');

                while (pos >= 0) {
                    if (pos >= 0) {
                        end = term.indexOf(')', pos + 2);
                        if (end >= 0) {
                            elName = term.slice(pos + 2, end);
                            elName = elName.replace(/\\(['"])?/g, '$1');
                            el = board.elementsByName[elName];

                            if (el) {
                                term = term.slice(0, pos + 2) + (jc ? '$(\'' : '') + printId(el.id) +  term.slice(end);
                            }
                        }
                    }
                    end = term.indexOf(')', pos + 2);
                    pos = term.indexOf(funcs[i] + '(', end);
                }
            }

            pos = term.indexOf('Dist(');
            while (pos >= 0) {
                if (pos >= 0) {
                    end = term.indexOf(',', pos + 5);
                    if (end >= 0) {
                        elName = term.slice(pos + 5, end);
                        elName = elName.replace(/\\(['"])?/g, '$1');
                        el = board.elementsByName[elName];

                        if (el) {
                            term = term.slice(0, pos + 5) + printId(el.id) +  term.slice(end);
                        }
                    }
                }
                end = term.indexOf(',', pos + 5);
                pos = term.indexOf(',', end);
                end = term.indexOf(')', pos + 1);

                if (end >= 0) {
                    elName = term.slice(pos + 1, end);
                    elName = elName.replace(/\\(['"])?/g, '$1');
                    el = board.elementsByName[elName];

                    if (el) {
                        term = term.slice(0, pos + 1) + printId(el.id) +  term.slice(end);
                    }
                }
                end = term.indexOf(')', pos + 1);
                pos = term.indexOf('Dist(', end);
            }

            funcs = ['Deg', 'Rad'];
            for (i = 0; i < funcs.length; i++) {
                pos = term.indexOf(funcs[i] + '(');
                while (pos >= 0) {
                    if (pos >= 0) {
                        end = term.indexOf(',', pos + 4);
                        if (end >= 0) {
                            elName = term.slice(pos + 4, end);
                            elName = elName.replace(/\\(['"])?/g, '$1');
                            el = board.elementsByName[elName];

                            if (el) {
                                term = term.slice(0, pos + 4) + printId(el.id) +  term.slice(end);
                            }
                        }
                    }

                    end = term.indexOf(',', pos + 4);
                    pos = term.indexOf(',', end);
                    end = term.indexOf(',', pos + 1);

                    if (end >= 0) {
                        elName = term.slice(pos + 1, end);
                        elName = elName.replace(/\\(['"])?/g, '$1');
                        el = board.elementsByName[elName];

                        if (el) {
                            term = term.slice(0, pos + 1) + printId(el.id) +  term.slice(end);
                        }
                    }

                    end = term.indexOf(',', pos + 1);
                    pos = term.indexOf(',', end);
                    end = term.indexOf(')', pos + 1);

                    if (end >= 0) {
                        elName = term.slice(pos + 1, end);
                        elName = elName.replace(/\\(['"])?/g, '$1');
                        el = board.elementsByName[elName];
                        if (el) {
                            term = term.slice(0, pos + 1) + printId(el.id) +  term.slice(end);
                        }
                    }

                    end = term.indexOf(')', pos + 1);
                    pos = term.indexOf(funcs[i] + '(', end);
                }
            }

            return term;
        },

        /**
         * Replaces element ids in terms by element this.board.objects['id'].
         * @param {String} term A GEONE<sub>x</sub>T function string with JSXGraph ids in it.
         * @returns {String} The input string with element ids replaced by this.board.objects["id"].
         **/
        replaceIdByObj: function (term) {
            // Search for expressions like "X(gi23)" or "Y(gi23A)" and convert them to objects['gi23'].X().
            var expr = /(X|Y|L)\(([\w_]+)\)/g;
            term = term.replace(expr, '$(\'$2\').$1()');

            expr = /(V)\(([\w_]+)\)/g;
            term = term.replace(expr, '$(\'$2\').Value()');

            expr = /(Dist)\(([\w_]+),([\w_]+)\)/g;
            term = term.replace(expr, 'dist($(\'$2\'), $(\'$3\'))');

            expr = /(Deg)\(([\w_]+),([ \w[\w_]+),([\w_]+)\)/g;
            term = term.replace(expr, 'deg($(\'$2\'),$(\'$3\'),$(\'$4\'))');

            // Search for Rad('gi23','gi24','gi25')
            expr = /Rad\(([\w_]+),([\w_]+),([\w_]+)\)/g;
            term = term.replace(expr, 'rad($(\'$1\'),$(\'$2\'),$(\'$3\'))');

            // it's ok, it will run through the jessiecode parser afterwards...
            /*jslint regexp: true*/
            expr = /N\((.+)\)/g;
            term = term.replace(expr, '($1)');

            return term;
        },

        /**
         * Converts the given algebraic expression in GEONE<sub>x</sub>T syntax into an equivalent expression in JavaScript syntax.
         * @param {String} term Expression in GEONExT syntax
         * @param {JXG.Board} board
         * @returns {String} Given expression translated to JavaScript.
         */
        geonext2JS: function (term, board) {
            var expr, newterm, i,
                from = ['Abs', 'ACos', 'ASin', 'ATan', 'Ceil', 'Cos', 'Exp', 'Factorial', 'Floor',
                    'Log', 'Max', 'Min', 'Random', 'Round', 'Sin', 'Sqrt', 'Tan', 'Trunc'],
                to =   ['abs', 'acos', 'asin', 'atan', 'ceil', 'cos',
                    'exp', 'factorial', 'floor', 'log', 'max', 'min',
                    'random', 'round', 'sin', 'sqrt', 'tan', 'ceil'];

            // Hacks, to enable not well formed XML, @see JXG.GeonextReader#replaceLessThan
            term = term.replace(/&lt;/g, '<');
            term = term.replace(/&gt;/g, '>');
            term = term.replace(/&amp;/g, '&');

            // Umwandeln der GEONExT-Syntax in JavaScript-Syntax
            newterm = term;
            newterm = this.replaceNameById(newterm, board);
            newterm = this.replaceIf(newterm);
            // Exponentiations-Problem x^y -> Math(exp(x,y).
            newterm = this.replacePow(newterm);
            newterm = this.replaceIdByObj(newterm);

            for (i = 0; i < from.length; i++) {
                // sin -> Math.sin and asin -> Math.asin
                expr = new RegExp(['(\\W|^)(', from[i], ')'].join(''), 'ig');
                newterm = newterm.replace(expr, ['$1', to[i]].join(''));
            }
            newterm = newterm.replace(/True/g, 'true');
            newterm = newterm.replace(/False/g, 'false');
            newterm = newterm.replace(/fasle/g, 'false');
            newterm = newterm.replace(/Pi/g, 'PI');
            newterm = newterm.replace(/"/g, '\'');

            return newterm;
        },

        /**
         * Finds dependencies in a given term and resolves them by adding the
         * dependent object to the found objects child elements.
         * @param {JXG.GeometryElement} me Object depending on objects in given term.
         * @param {String} term String containing dependencies for the given object.
         * @param {JXG.Board} [board=me.board] Reference to a board
         */
        findDependencies: function (me, term, board) {
            var elements, el, expr, elmask;

            if (!Type.exists(board)) {
                board = me.board;
            }

            elements = board.elementsByName;

            for (el in elements) {
                if (elements.hasOwnProperty(el)) {
                    if (el !== me.name) {
                        if (elements[el].elementClass === Const.OBJECT_CLASS_TEXT) {
                            if (!Type.evaluate(elements[el].visProp.islabel)) {
                                elmask = el.replace(/\[/g, '\\[');
                                elmask = elmask.replace(/\]/g, '\\]');

                                // Searches (A), (A,B),(A,B,C)
                                expr = new RegExp("\\(([\\w\\[\\]'_ ]+,)*(" + elmask + ")(,[\\w\\[\\]'_ ]+)*\\)", 'g');

                                if (term.search(expr) >= 0) {
                                    elements[el].addChild(me);
                                }
                            }
                        } else {
                            elmask = el.replace(/\[/g, '\\[');
                            elmask = elmask.replace(/\]/g, '\\]');

                            // Searches (A), (A,B),(A,B,C)
                            expr = new RegExp("\\(([\\w\\[\\]'_ ]+,)*(" + elmask + ")(,[\\w\\[\\]'_ ]+)*\\)", 'g');

                            if (term.search(expr) >= 0) {
                                elements[el].addChild(me);
                            }
                        }
                    }
                }
            }
        },

        /**
         * Converts the given algebraic expression in GEONE<sub>x</sub>T syntax into an equivalent expression in JessieCode syntax.
         * @param {String} term Expression in GEONExT syntax
         * @param {JXG.Board} board
         * @returns {String} Given expression translated to JavaScript.
         */
        gxt2jc: function (term, board) {
            var newterm,
                from = ['Sqrt'],
                to = ['sqrt'];

            // Hacks, to enable not well formed XML, @see JXG.GeonextReader#replaceLessThan
            term = term.replace(/&lt;/g, '<');
            term = term.replace(/&gt;/g, '>');
            term = term.replace(/&amp;/g, '&');
            newterm = term;
            newterm = this.replaceNameById(newterm, board, true);
            newterm = newterm.replace(/True/g, 'true');
            newterm = newterm.replace(/False/g, 'false');
            newterm = newterm.replace(/fasle/g, 'false');

            return newterm;
        }
    };

    return JXG.GeonextParser;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true, unparam: true*/

/* depends:
 jxg
 base/constants
 base/coords
 math/math
 options
 parser/geonext
 utils/event
 utils/color
 utils/type
 */

define('base/element',[
    'jxg', 'base/constants', 'base/coords', 'math/math', 'math/statistics', 'options', 'utils/event', 'utils/color', 'utils/type'
], function (JXG, Const, Coords, Mat, Statistics, Options, EventEmitter, Color, Type) {

    "use strict";

    /**
     * Constructs a new GeometryElement object.
     * @class This is the basic class for geometry elements like points, circles and lines.
     * @constructor
     * @param {JXG.Board} board Reference to the board the element is constructed on.
     * @param {Object} attributes Hash of attributes and their values.
     * @param {Number} type Element type (a <tt>JXG.OBJECT_TYPE_</tt> value).
     * @param {Number} oclass The element's class (a <tt>JXG.OBJECT_CLASS_</tt> value).
     * @borrows JXG.EventEmitter#on as this.on
     * @borrows JXG.EventEmitter#off as this.off
     * @borrows JXG.EventEmitter#triggerEventHandlers as this.triggerEventHandlers
     * @borrows JXG.EventEmitter#eventHandlers as this.eventHandlers
     */
    JXG.GeometryElement = function (board, attributes, type, oclass) {
        var name, key, attr;

        /**
         * Controls if updates are necessary
         * @type Boolean
         * @default true
         */
        this.needsUpdate = true;

        /**
         * Controls if this element can be dragged. In GEONExT only
         * free points and gliders can be dragged.
         * @type Boolean
         * @default false
         */
        this.isDraggable = false;

        /**
         * If element is in two dimensional real space this is true, else false.
         * @type Boolean
         * @default true
         */
        this.isReal = true;

        /**
         * Stores all dependent objects to be updated when this point is moved.
         * @type Object
         */
        this.childElements = {};

        /**
         * If element has a label subelement then this property will be set to true.
         * @type Boolean
         * @default false
         */
        this.hasLabel = false;

        /**
         * True, if the element is currently highlighted.
         * @type Boolean
         * @default false
         */
        this.highlighted = false;

        /**
         * Stores all Intersection Objects which in this moment are not real and
         * so hide this element.
         * @type Object
         */
        this.notExistingParents = {};

        /**
         * Keeps track of all objects drawn as part of the trace of the element.
         * @see JXG.GeometryElement#clearTrace
         * @see JXG.GeometryElement#numTraces
         * @type Object
         */
        this.traces = {};

        /**
         * Counts the number of objects drawn as part of the trace of the element.
         * @see JXG.GeometryElement#clearTrace
         * @see JXG.GeometryElement#traces
         * @type Number
         */
        this.numTraces = 0;

        /**
         * Stores the  transformations which are applied during update in an array
         * @type Array
         * @see JXG.Transformation
         */
        this.transformations = [];

        /**
         * @type JXG.GeometryElement
         * @default null
         * @private
         */
        this.baseElement = null;

        /**
         * Elements depending on this element are stored here.
         * @type Object
         */
        this.descendants = {};

        /**
         * Elements on which this element depends on are stored here.
         * @type Object
         */
        this.ancestors = {};

        /**
         * Ids of elements on which this element depends directly are stored here.
         * @type Object
         */
        this.parents = [];

        /**
         * Stores variables for symbolic computations
         * @type Object
         */
        this.symbolic = {};

        /**
         * Stores the SVG (or VML) rendering node for the element. This enables low-level
         * access to SVG nodes. The properties of such an SVG node can then be changed
         * by calling setAttribute(). Note that there are a few elements which consist
         * of more than one SVG nodes:
         * <ul>
         * <li> Elements with arrow tail or head: rendNodeTriangleStart, rendNodeTriangleEnd
         * <li> SVG (or VML) texts: rendNodeText
         * <li> Button: rendNodeForm, rendNodeButton, rendNodeTag
         * <li> Checkbox: rendNodeForm, rendNodeCheckbox, rendNodeLabel, rendNodeTag
         * <li> Input: rendNodeForm, rendNodeInput, rendNodeLabel, rendNodeTag
         * </ul>
         *
         * Here is are two examples: The first example shows how to access the SVG node,
         * the second example demonstrates how to change SVG attributes.
         * @example
         *     var p1 = board.create('point', [0, 0]);
         *     console.log(p1.rendNode);
         *     // returns the full SVG node details of the point p1, something like:
         *     // &lt;ellipse id='box_jxgBoard1P6' stroke='#ff0000' stroke-opacity='1' stroke-width='2px'
         *     //   fill='#ff0000' fill-opacity='1' cx='250' cy='250' rx='4' ry='4'
         *     //   style='position: absolute;'&gt;
         *     // &lt;/ellipse&gt;
         *
         * @example
         *     var s = board.create('segment', [p1, p2], {strokeWidth: 60});
         *     s.rendNode.setAttribute('stroke-linecap', 'round');
         *
         * @type Object
         */
        this.rendNode = null;

        /**
         * The string used with {@link JXG.Board#create}
         * @type String
         */
        this.elType = '';

        /**
         * The element is saved with an explicit entry in the file (<tt>true</tt>) or implicitly
         * via a composition.
         * @type Boolean
         * @default true
         */
        this.dump = true;

        /**
         * Subs contains the subelements, created during the create method.
         * @type Object
         */
        this.subs = {};

        /**
         * Inherits contains the subelements, which may have an attribute
         * (in particular the attribute "visible") having value 'inherit'.
         * @type Object
         */
        this.inherits = [];

        /**
         * The position of this element inside the {@link JXG.Board#objectsList}.
         * @type Number
         * @default -1
         * @private
         */
        this._pos = -1;

        /**
         * [c, b0, b1, a, k, r, q0, q1]
         *
         * See
         * A.E. Middleditch, T.W. Stacey, and S.B. Tor:
         * "Intersection Algorithms for Lines and Circles",
         * ACM Transactions on Graphics, Vol. 8, 1, 1989, pp 25-40.
         *
         * The meaning of the parameters is:
         * Circle: points p=[p0, p1] on the circle fulfill
         *  a&lt;p, p&gt; + &lt;b, p&gt; + c = 0
         * For convenience we also store
         *  r: radius
         *  k: discriminant = sqrt(&lt;b,b&gt;-4ac)
         *  q=[q0, q1] center
         *
         * Points have radius = 0.
         * Lines have radius = infinity.
         * b: normalized vector, representing the direction of the line.
         *
         * Should be put into Coords, when all elements possess Coords.
         * @type Array
         * @default [1, 0, 0, 0, 1, 1, 0, 0]
         */
        this.stdform = [1, 0, 0, 0, 1, 1, 0, 0];

        /**
         * The methodMap determines which methods can be called from within JessieCode and under which name it
         * can be used. The map is saved in an object, the name of a property is the name of the method used in JessieCode,
         * the value of a property is the name of the method in JavaScript.
         * @type Object
         */
        this.methodMap = {
            setLabel: 'setLabel',
            label: 'label',
            setName: 'setName',
            getName: 'getName',
            addTransform: 'addTransform',
            setProperty: 'setAttribute',
            setAttribute: 'setAttribute',
            addChild: 'addChild',
            animate: 'animate',
            on: 'on',
            off: 'off',
            trigger: 'trigger',
            addTicks: 'addTicks',
            removeTicks: 'removeTicks',
            removeAllTicks: 'removeAllTicks'
        };

        /**
         * Quadratic form representation of circles (and conics)
         * @type Array
         * @default [[1,0,0],[0,1,0],[0,0,1]]
         */
        this.quadraticform = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];

        /**
         * An associative array containing all visual properties.
         * @type Object
         * @default empty object
         */
        this.visProp = {};

        /**
         * An associative array containing visual properties which are calculated from
         * the attribute values (i.e. visProp) and from other constraints.
         * An example: if an intersection point does not have real coordinates,
         * visPropCalc.visible is set to false.
         * Additionally, the user can control visibility with the attribute "visible",
         * even by supplying a functions as value.
         *
         * @type Object
         * @default empty object
         */
        this.visPropCalc = {
            visible: false
        };

        EventEmitter.eventify(this);

        /**
         * Is the mouse over this element?
         * @type Boolean
         * @default false
         */
        this.mouseover = false;

        /**
         * Time stamp containing the last time this element has been dragged.
         * @type Date
         * @default creation time
         */
        this.lastDragTime = new Date();

        if (arguments.length > 0) {
            /**
             * Reference to the board associated with the element.
             * @type JXG.Board
             */
            this.board = board;

            /**
             * Type of the element.
             * @constant
             * @type Number
             */
            this.type = type;

            /**
             * Original type of the element at construction time. Used for removing glider property.
             * @constant
             * @type Number
             */
            this._org_type = type;

            /**
             * The element's class.
             * @constant
             * @type Number
             */
            this.elementClass = oclass || Const.OBJECT_CLASS_OTHER;

            /**
             * Unique identifier for the element. Equivalent to id-attribute of renderer element.
             * @type String
             */
            this.id = attributes.id;

            name = attributes.name;
            /* If name is not set or null or even undefined, generate an unique name for this object */
            if (!Type.exists(name)) {
                name = this.board.generateName(this);
            }

            if (name !== '') {
                this.board.elementsByName[name] = this;
            }

            /**
             * Not necessarily unique name for the element.
             * @type String
             * @default Name generated by {@link JXG.Board#generateName}.
             * @see JXG.Board#generateName
             */
            this.name = name;

            this.needsRegularUpdate = attributes.needsregularupdate;

            // create this.visPropOld and set default values
            Type.clearVisPropOld(this);

            attr = this.resolveShortcuts(attributes);
            for (key in attr) {
                if (attr.hasOwnProperty(key)) {
                    this._set(key, attr[key]);
                }
            }

            this.visProp.draft = attr.draft && attr.draft.draft;
            //this.visProp.gradientangle = '270';
            // this.visProp.gradientsecondopacity = Type.evaluate(this.visProp.fillopacity);
            //this.visProp.gradientpositionx = 0.5;
            //this.visProp.gradientpositiony = 0.5;
        }
    };

    JXG.extend(JXG.GeometryElement.prototype, /** @lends JXG.GeometryElement.prototype */ {
        /**
         * Add an element as a child to the current element. Can be used to model dependencies between geometry elements.
         * @param {JXG.GeometryElement} obj The dependent object.
         */
        addChild: function (obj) {
            var el, el2;

            this.childElements[obj.id] = obj;
            this.addDescendants(obj);
            obj.ancestors[this.id] = this;

            for (el in this.descendants) {
                if (this.descendants.hasOwnProperty(el)) {
                    this.descendants[el].ancestors[this.id] = this;

                    for (el2 in this.ancestors) {
                        if (this.ancestors.hasOwnProperty(el2)) {
                            this.descendants[el].ancestors[this.ancestors[el2].id] = this.ancestors[el2];
                        }
                    }
                }
            }

            for (el in this.ancestors) {
                if (this.ancestors.hasOwnProperty(el)) {
                    for (el2 in this.descendants) {
                        if (this.descendants.hasOwnProperty(el2)) {
                            this.ancestors[el].descendants[this.descendants[el2].id] = this.descendants[el2];
                        }
                    }
                }
            }
            return this;
        },

        /**
         * Adds the given object to the descendants list of this object and all its child objects.
         * @param {JXG.GeometryElement} obj The element that is to be added to the descendants list.
         * @private
         * @return
         */
        addDescendants: function (obj) {
            var el;

            this.descendants[obj.id] = obj;
            for (el in obj.childElements) {
                if (obj.childElements.hasOwnProperty(el)) {
                    this.addDescendants(obj.childElements[el]);
                }
            }
            return this;
        },

        /**
         * Adds ids of elements to the array this.parents. This method needs to be called if some dependencies
         * can not be detected automatically by JSXGraph. For example if a function graph is given by a function
         * which referes to coordinates of a point, calling addParents() is necessary.
         *
         * @param {Array} parents Array of elements or ids of elements.
         * Alternatively, one can give a list of objects as parameters.
         * @returns {JXG.Object} reference to the object itself.
         *
         * @example
         * // Movable function graph
         * var A = board.create('point', [1, 0], {name:'A'}),
         *     B = board.create('point', [3, 1], {name:'B'}),
         *     f = board.create('functiongraph', function(x) {
         *          var ax = A.X(),
         *              ay = A.Y(),
         *              bx = B.X(),
         *              by = B.Y(),
         *              a = (by - ay) / ( (bx - ax) * (bx - ax) );
         *           return a * (x - ax) * (x - ax) + ay;
         *      }, {fixed: false});
         * f.addParents([A, B]);
         * </pre><div class="jxgbox" id="JXG7c91d4d2-986c-4378-8135-24505027f251" style="width: 400px; height: 400px;"></div>
         * <script type="text/javascript">
         * (function() {
         *   var board = JXG.JSXGraph.initBoard('JXG7c91d4d2-986c-4378-8135-24505027f251', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
         *   var A = board.create('point', [1, 0], {name:'A'}),
         *       B = board.create('point', [3, 1], {name:'B'}),
         *       f = board.create('functiongraph', function(x) {
         *            var ax = A.X(),
         *                ay = A.Y(),
         *                bx = B.X(),
         *                by = B.Y(),
         *                a = (by - ay) / ( (bx - ax) * (bx - ax) );
         *             return a * (x - ax) * (x - ax) + ay;
         *        }, {fixed: false});
         *   f.addParents([A, B]);
         * })();
         * </script><pre>
         *
         **/
        addParents: function (parents) {
            var i, len, par;

            if (Type.isArray(parents)) {
                par = parents;
            } else {
                par = arguments;
            }

            len = par.length;
            for (i = 0; i < len; ++i) {
                if (!Type.exists(par[i])) {
                    continue;
                }
                if (Type.isId(this.board, par[i])) {
                    this.parents.push(par[i]);
                } else if (Type.exists(par[i].id)) {
                    this.parents.push(par[i].id);
                }
            }
            this.parents = Type.uniqueArray(this.parents);
        },

        /**
         * Sets ids of elements to the array this.parents.
         * First, this.parents is cleared. See {@link JXG.GeometryElement#addParents}.
         * @param {Array} parents Array of elements or ids of elements.
         * Alternatively, one can give a list of objects as parameters.
         * @returns {JXG.Object} reference to the object itself.
         **/
        setParents: function(parents) {
            this.parents = [];
            this.addParents(parents);
        },

        /**
         * Remove an element as a child from the current element.
         * @param {JXG.GeometryElement} obj The dependent object.
         */
        removeChild: function (obj) {
            //var el, el2;

            delete this.childElements[obj.id];
            this.removeDescendants(obj);
            delete obj.ancestors[this.id];

            /*
             // I do not know if these addDescendants stuff has to be adapted to removeChild. A.W.
            for (el in this.descendants) {
                if (this.descendants.hasOwnProperty(el)) {
                    delete this.descendants[el].ancestors[this.id];

                    for (el2 in this.ancestors) {
                        if (this.ancestors.hasOwnProperty(el2)) {
                            this.descendants[el].ancestors[this.ancestors[el2].id] = this.ancestors[el2];
                        }
                    }
                }
            }

            for (el in this.ancestors) {
                if (this.ancestors.hasOwnProperty(el)) {
                    for (el2 in this.descendants) {
                        if (this.descendants.hasOwnProperty(el2)) {
                            this.ancestors[el].descendants[this.descendants[el2].id] = this.descendants[el2];
                        }
                    }
                }
            }
            */
            return this;
        },

        /**
         * Removes the given object from the descendants list of this object and all its child objects.
         * @param {JXG.GeometryElement} obj The element that is to be removed from the descendants list.
         * @private
         * @return
         */
        removeDescendants: function (obj) {
            var el;

            delete this.descendants[obj.id];
            for (el in obj.childElements) {
                if (obj.childElements.hasOwnProperty(el)) {
                    this.removeDescendants(obj.childElements[el]);
                }
            }
            return this;
        },

        /**
         * Counts the direct children of an object without counting labels.
         * @private
         * @returns {number} Number of children
         */
        countChildren: function () {
            var prop, d,
                s = 0;

            d = this.childElements;
            for (prop in d) {
                if (d.hasOwnProperty(prop) && prop.indexOf('Label') < 0) {
                    s++;
                }
            }
            return s;
        },

        /**
         * Returns the elements name. Used in JessieCode.
         * @returns {String}
         */
        getName: function () {
            return this.name;
        },

        /**
         * Add transformations to this element.
         * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation}
         * or an array of {@link JXG.Transformation}s.
         * @returns {JXG.GeometryElement} Reference to the element.
         */
        addTransform: function (transform) {
            return this;
        },

        /**
         * Decides whether an element can be dragged. This is used in
         * {@link JXG.GeometryElement#setPositionDirectly} methods
         * where all parent elements are checked if they may be dragged, too.
         * @private
         * @returns {boolean}
         */
        draggable: function () {
            return this.isDraggable && !Type.evaluate(this.visProp.fixed) &&
                /*!this.visProp.frozen &&*/ this.type !== Const.OBJECT_TYPE_GLIDER;
        },

        /**
         * Translates the object by <tt>(x, y)</tt>. In case the element is defined by points, the defining points are
         * translated, e.g. a circle constructed by a center point and a point on the circle line.
         * @param {Number} method The type of coordinates used here.
         * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
         * @param {Array} coords array of translation vector.
         * @returns {JXG.GeometryElement} Reference to the element object.
         */
        setPosition: function (method, coords) {
            var parents = [],
                el, i, len, t;

            if (!Type.exists(this.parents)) {
                return this;
            }

            len = this.parents.length;
            for (i = 0; i < len; ++i) {
                el = this.board.select(this.parents[i]);
                if (Type.isPoint(el)) {
                    if (!el.draggable()) {
                        return this;
                    }
                    parents.push(el);
                }
            }

            if (coords.length === 3) {
                coords = coords.slice(1);
            }

            t = this.board.create('transform', coords, {type: 'translate'});

            // We distinguish two cases:
            // 1) elements which depend on free elements, i.e. arcs and sectors
            // 2) other elements
            //
            // In the first case we simply transform the parents elements
            // In the second case we add a transform to the element.
            //
            len = parents.length;
            if (len > 0) {
                t.applyOnce(parents);
            } else {
                if (this.transformations.length > 0 &&
                        this.transformations[this.transformations.length - 1].isNumericMatrix) {
                    this.transformations[this.transformations.length - 1].melt(t);
                } else {
                    this.addTransform(t);
                }
            }

            /*
             * If - against the default configuration - defining gliders are marked as
             * draggable, then their position has to be updated now.
             */
            for (i = 0; i < len; ++i) {
                if (parents[i].type === Const.OBJECT_TYPE_GLIDER) {
                    parents[i].updateGlider();
                }
            }

            return this;
        },

        /**
         * Moves an element by the difference of two coordinates.
         * @param {Number} method The type of coordinates used here.
         * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
         * @param {Array} coords coordinates in screen/user units
         * @param {Array} oldcoords previous coordinates in screen/user units
         * @returns {JXG.GeometryElement} this element
         */
        setPositionDirectly: function (method, coords, oldcoords) {
            var c = new Coords(method, coords, this.board, false),
                oldc = new Coords(method, oldcoords, this.board, false),
                dc = Statistics.subtract(c.usrCoords, oldc.usrCoords);

            this.setPosition(Const.COORDS_BY_USER, dc);

            return this;
        },

        /**
         * Array of strings containing the polynomials defining the element.
         * Used for determining geometric loci the groebner way.
         * @returns {Array} An array containing polynomials describing the locus of the current object.
         * @public
         */
        generatePolynomial: function () {
            return [];
        },

        /**
         * Animates properties for that object like stroke or fill color, opacity and maybe
         * even more later.
         * @param {Object} hash Object containing properties with target values for the animation.
         * @param {number} time Number of milliseconds to complete the animation.
         * @param {Object} [options] Optional settings for the animation:<ul><li>callback: A function that is called as soon as the animation is finished.</li></ul>
         * @returns {JXG.GeometryElement} A reference to the object
         */
        animate: function (hash, time, options) {
            options = options || {};
            var r, p, i,
                delay = this.board.attr.animationdelay,
                steps = Math.ceil(time / delay),
                self = this,

                animateColor = function (startRGB, endRGB, property) {
                    var hsv1, hsv2, sh, ss, sv;
                    hsv1 = Color.rgb2hsv(startRGB);
                    hsv2 = Color.rgb2hsv(endRGB);

                    sh = (hsv2[0] - hsv1[0]) / steps;
                    ss = (hsv2[1] - hsv1[1]) / steps;
                    sv = (hsv2[2] - hsv1[2]) / steps;
                    self.animationData[property] = [];

                    for (i = 0; i < steps; i++) {
                        self.animationData[property][steps - i - 1] = Color.hsv2rgb(hsv1[0] + (i + 1) * sh, hsv1[1] + (i + 1) * ss, hsv1[2] + (i + 1) * sv);
                    }
                },

                animateFloat = function (start, end, property, round) {
                    var tmp, s;

                    start = parseFloat(start);
                    end = parseFloat(end);

                    // we can't animate without having valid numbers.
                    // And parseFloat returns NaN if the given string doesn't contain
                    // a valid float number.
                    if (isNaN(start) || isNaN(end)) {
                        return;
                    }

                    s = (end - start) / steps;
                    self.animationData[property] = [];

                    for (i = 0; i < steps; i++) {
                        tmp = start + (i + 1) * s;
                        self.animationData[property][steps - i - 1] = round ? Math.floor(tmp) : tmp;
                    }
                };

            this.animationData = {};

            for (r in hash) {
                if (hash.hasOwnProperty(r)) {
                    p = r.toLowerCase();

                    switch (p) {
                    case 'strokecolor':
                    case 'fillcolor':
                        animateColor(this.visProp[p], hash[r], p);
                        break;
                    case 'size':
                        if (!Type.isPoint(this)) {
                            break;
                        }
                        animateFloat(this.visProp[p], hash[r], p, true);
                        break;
                    case 'strokeopacity':
                    case 'strokewidth':
                    case 'fillopacity':
                        animateFloat(this.visProp[p], hash[r], p, false);
                        break;
                    }
                }
            }

            this.animationCallback = options.callback;
            this.board.addAnimation(this);
            return this;
        },

        /**
         * General update method. Should be overwritten by the element itself.
         * Can be used sometimes to commit changes to the object.
         * @return {JXG.GeometryElement} Reference to the element
         */
        update: function () {
            if (Type.evaluate(this.visProp.trace)) {
                this.cloneToBackground();
            }
            return this;
        },

        /**
         * Provide updateRenderer method.
         * @return {JXG.GeometryElement} Reference to the element
         * @private
         */
        updateRenderer: function () {
            return this;
        },

        /**
         * Run through the full update chain of an element.
         * @param  {Boolean} visible Set visibility in case the elements attribute value is 'inherit'. null is allowed.
         * @return {JXG.GeometryElement} Reference to the element
         * @private
         */
        fullUpdate: function(visible) {
            return this.prepareUpdate()
                .update()
                .updateVisibility(visible)
                .updateRenderer();
        },

        /**
         * Show the element or hide it. If hidden, it will still exist but not be
         * visible on the board.
         * @param  {Boolean} val true: show the element, false: hide the element
         * @return {JXG.GeometryElement} Reference to the element
         * @private
         */
        setDisplayRendNode: function(val) {
            var i, len, s, len_s, obj;

            if (val === undefined) {
                val = this.visPropCalc.visible;
            }

            if (val === this.visPropOld.visible) {
                return this;
            }

            // Set display of the element itself
            this.board.renderer.display(this, val);

            // Set the visibility of elements which inherit the attribute 'visible'
            len = this.inherits.length;
            for (s = 0; s < len; s++) {
                obj = this.inherits[s];
                if (Type.isArray(obj)) {
                    len_s = obj.length;
                    for (i = 0; i < len_s; i++) {
                        if (Type.exists(obj[i]) && Type.exists(obj[i].rendNode) &&
                            Type.evaluate(obj[i].visProp.visible) === 'inherit') {
                            obj[i].setDisplayRendNode(val);
                        }
                    }
                } else {
                    if (Type.exists(obj) && Type.exists(obj.rendNode) &&
                        Type.evaluate(obj.visProp.visible) === 'inherit') {
                            obj.setDisplayRendNode(val);
                    }
                }
            }

            // Set the visibility of the label if it inherits the attribute 'visible'
            if (this.hasLabel && Type.exists(this.label) && Type.exists(this.label.rendNode)) {
                if (Type.evaluate(this.label.visProp.visible) === 'inherit') {
                    this.label.setDisplayRendNode(val);
                }
            }

            return this;
        },

        /**
         * Hide the element. It will still exist but not be visible on the board.
         * Alias for "element.setAttribute({visible: false});"
         * @return {JXG.GeometryElement} Reference to the element
         */
        hide: function () {
            this.setAttribute({visible: false});
            return this;
        },

        /**
         * Hide the element. It will still exist but not be visible on the board.
         * Alias for {@link JXG.GeometryElement#hide}
         * @returns {JXG.GeometryElement} Reference to the element
         */
        hideElement: function() {
            this.hide();
            return this;
        },

        /**
         * Make the element visible.
         * Alias for "element.setAttribute({visible: true});"
         * @return {JXG.GeometryElement} Reference to the element
         */
        show: function () {
            this.setAttribute({visible: true});
            return this;
        },

        /**
         * Make the element visible.
         * Alias for {@link JXG.GeometryElement#show}
         * @returns {JXG.GeometryElement} Reference to the element
         */
        showElement: function() {
            this.show();
            return this;
        },

        /**
         * Set the visibility of an element. The visibility is influenced by
         * (listed in ascending priority):
         * <ol>
         * <li> The value of the element's attribute 'visible'
         * <li> The visibility of a parent element. (Example: label)
         * This overrules the value of the element's attribute value only if
         * this attribute value of the element is 'inherit'.
         * <li> being inside of the canvas
         * </ol>
         * <p>
         * This method is called three times for most elements:
         * <ol>
         * <li> between {@link JXG.GeometryElement#update}
         * and {@link JXG.GeometryElement#updateRenderer}. In case the value is 'inherit', nothing is done.
         * <li> Recursively, called by itself for child elements. Here, 'inherit' is overruled by the parent's value.
         * <li> In {@link JXG.GeometryElement#updateRenderer}, if the element is outside of the canvas.
         * </ol>
         *
         * @param  {Boolean} parent_val Visibility of the parent element.
         * @return {JXG.GeometryElement} Reference to the element.
         * @private
         */
        updateVisibility: function(parent_val) {
            var i, len, s, len_s, obj, val;

            if (this.needsUpdate) {
                // Handle the element
                if (parent_val !== undefined) {
                    this.visPropCalc.visible = parent_val;
                } else {
                    val = Type.evaluate(this.visProp.visible);

                    // infobox uses hiddenByParent
                    if (Type.exists(this.hiddenByParent) && this.hiddenByParent) {
                        val = false;
                    }
                    if (val !== 'inherit') {
                        this.visPropCalc.visible = val;
                    }
                }

                // Handle elements which inherit the visibility
                len = this.inherits.length;
                for (s = 0; s < len; s++) {
                    obj = this.inherits[s];
                    if (Type.isArray(obj)) {
                        len_s = obj.length;
                        for (i = 0; i < len_s; i++) {
                            if (Type.exists(obj[i]) /*&& Type.exists(obj[i].rendNode)*/ &&
                                Type.evaluate(obj[i].visProp.visible) === 'inherit') {
                                obj[i].prepareUpdate().updateVisibility(this.visPropCalc.visible);
                            }
                        }
                    } else {
                        if (Type.exists(obj) /*&& Type.exists(obj.rendNode)*/ &&
                            Type.evaluate(obj.visProp.visible) === 'inherit') {
                            obj.prepareUpdate().updateVisibility(this.visPropCalc.visible);
                        }
                    }
                }

                // Handle the label if it inherits the visibility
                if (Type.exists(this.label) && Type.exists(this.label.visProp) &&
                    Type.evaluate(this.label.visProp.visible)) {
                    this.label.prepareUpdate().updateVisibility(this.visPropCalc.visible);
                }
            }
            return this;
        },

        /**
         * Sets the value of property <tt>property</tt> to <tt>value</tt>.
         * @param {String} property The property's name.
         * @param value The new value
         * @private
         */
        _set: function (property, value) {
            var el;

            property = property.toLocaleLowerCase();

            // Search for entries in visProp with "color" as part of the property name
            // and containing a RGBA string
            if (this.visProp.hasOwnProperty(property) &&
                  property.indexOf('color') >= 0 &&
                  Type.isString(value) &&
                  value.length === 9 &&
                  value.charAt(0) === '#') {

                value = Color.rgba2rgbo(value);
                this.visProp[property] = value[0];
                // Previously: *=. But then, we can only decrease opacity.
                this.visProp[property.replace('color', 'opacity')] = value[1];
            } else {
                if (value !== null &&Type.isObject(value) &&
                    !Type.exists(value.id) &&
                    !Type.exists(value.name)) {
                    // value is of type {prop: val, prop: val,...}
                    // Convert these attributes to lowercase, too
                    this.visProp[property] = {};
                    for (el in value) {
                        if (value.hasOwnProperty(el)) {
                            this.visProp[property][el.toLocaleLowerCase()] = value[el];
                        }
                    }
                } else {
                    this.visProp[property] = value;
                }
            }
        },

        /**
         * Resolves property shortcuts like <tt>color</tt> and expands them, e.g. <tt>strokeColor</tt> and <tt>fillColor</tt>.
         * Writes the expanded properties back to the given <tt>properties</tt>.
         * @param {Object} properties
         * @returns {Object} The given parameter with shortcuts expanded.
         */
        resolveShortcuts: function (properties) {
            var key, i;

            for (key in Options.shortcuts) {
                if (Options.shortcuts.hasOwnProperty(key)) {
                    if (Type.exists(properties[key])) {
                        for (i = 0; i < Options.shortcuts[key].length; i++) {
                            if (!Type.exists(properties[Options.shortcuts[key][i]])) {
                                properties[Options.shortcuts[key][i]] = properties[key];
                            }
                        }
                    }
                }
            }
            return properties;
        },

        /**
         * Sets a label and its text
         * If label doesn't exist, it creates one
         * @param {String} str
         */
        setLabel: function (str) {
            if (!this.hasLabel) {
                this.setAttribute({'withlabel': true});
            }
            this.setLabelText(str);
        },

        /**
         * Updates the element's label text, strips all html.
         * @param {String} str
         */
        setLabelText: function (str) {

            if (Type.exists(this.label)) {
                str = str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
                this.label.setText(str);
            }

            return this;
        },

        /**
         * Updates the element's label text and the element's attribute "name", strips all html.
         * @param {String} str
         */
        setName: function (str) {
            str = str.replace(/</g, '&lt;').replace(/>/g, '&gt;');
            if (this.elType !== 'slider') {
                this.setLabelText(str);
            }
            this.setAttribute({name: str});
        },

        /**
         * Deprecated alias for {@link JXG.GeometryElement#setAttribute}.
         * @deprecated Use {@link JXG.GeometryElement#setAttribute}.
         */
        setProperty: function () {
            JXG.deprecated('setProperty()', 'setAttribute()');
            this.setAttribute.apply(this, arguments);
        },

        /**
         * Sets an arbitrary number of attributes. This method has one or more
         * parameters of the following types:
         * <ul>
         * <li> object: {key1:value1,key2:value2,...}
         * <li> string: "key1:value"
         * <li> array: [key, value]
         * </ul>
         * @param {Object} attributes An object with attributes.
         * @returns {JXG.GeometryElement} A reference to the element.
         *
         * @function
         * @example
         * // Set property directly on creation of an element using the attributes object parameter
         * var board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-1, 5, 5, 1]};
         * var p = board.create('point', [2, 2], {visible: false});
         *
         * // Now make this point visible and fixed:
         * p.setAttribute({
         *     fixed: true,
         *     visible: true
         * });
         */
        setAttribute: function (attributes) {
            var i, j, le, key, value, arg, opacity, pair, oldvalue,
                properties = {};

            // Normalize the user input
            for (i = 0; i < arguments.length; i++) {
                arg = arguments[i];
                if (Type.isString(arg)) {
                    // pairRaw is string of the form 'key:value'
                    pair = arg.split(':');
                    properties[Type.trim(pair[0])] = Type.trim(pair[1]);
                } else if (!Type.isArray(arg)) {
                    // pairRaw consists of objects of the form {key1:value1,key2:value2,...}
                    JXG.extend(properties, arg);
                } else {
                    // pairRaw consists of array [key,value]
                    properties[arg[0]] = arg[1];
                }
            }

            // Handle shortcuts
            properties = this.resolveShortcuts(properties);

            for (i in properties) {
                if (properties.hasOwnProperty(i)) {
                    key = i.replace(/\s+/g, '').toLowerCase();
                    value = properties[i];

                    // This handles the subobjects, if the key:value pairs are contained in an object.
                    // Example:
                    // ticks.setAttribute({
                    //      strokeColor: 'blue',
                    //      label: {
                    //          visible: false
                    //      }
                    // })
                    // Now, only the supplied label attributes are overwritten.
                    // Otherwise, the value of label would be {visible:false} only.
                    if (Type.isObject(value) &&
                        Type.exists(this.visProp[key])) {

                        this.visProp[key] = Type.merge(this.visProp[key], value);

                        // First, handle the special case
                        // ticks.setAttribute({label: {anchorX: "right", ..., visible: true});
                        if (this.type === Const.OBJECT_TYPE_TICKS && Type.exists(this.labels)) {
                            le = this.labels.length;
                            for (j = 0; j < le; j++) {
                                this.labels[j].setAttribute(value);
                            }
                        } else if (Type.exists(this[key])) {
                            if (Type.isArray(this[key])) {
                                for (j = 0; j < this[key].length; j++) {
                                    this[key][j].setAttribute(value);
                                }
                            } else {
                                this[key].setAttribute(value);
                            }
                        }
                        continue;
                    }

                    oldvalue = this.visProp[key];
                    switch (key) {
                    case 'name':
                        oldvalue = this.name;
                        delete this.board.elementsByName[this.name];
                        this.name = value;
                        this.board.elementsByName[this.name] = this;
                        break;
                    case 'needsregularupdate':
                        this.needsRegularUpdate = !(value === 'false' || value === false);
                        this.board.renderer.setBuffering(this, this.needsRegularUpdate ? 'auto' : 'static');
                        break;
                    case 'labelcolor':
                        value = Color.rgba2rgbo(value);
                        opacity = value[1];
                        value = value[0];
                        if (opacity === 0) {
                            if (Type.exists(this.label) && this.hasLabel) {
                                this.label.hideElement();
                            }
                        }
                        if (Type.exists(this.label) && this.hasLabel) {
                            this.label.visProp.strokecolor = value;
                            this.board.renderer.setObjectStrokeColor(this.label,
                                value, opacity);
                        }
                        if (this.elementClass === Const.OBJECT_CLASS_TEXT) {
                            this.visProp.strokecolor = value;
                            this.visProp.strokeopacity = opacity;
                            this.board.renderer.setObjectStrokeColor(this,
                                value, opacity);
                        }
                        break;
                    case 'infoboxtext':
                        if (Type.isString(value)) {
                            this.infoboxText = value;
                        } else {
                            this.infoboxText = false;
                        }
                        break;
                    case 'visible':
                        if (value === 'false') {
                            this.visProp.visible = false;
                        } else if (value === 'true') {
                            this.visProp.visible = true;
                        } else {
                            this.visProp.visible = value;
                        }

                        this.setDisplayRendNode(Type.evaluate(this.visProp.visible));
                        if (Type.evaluate(this.visProp.visible) && Type.exists(this.updateSize)) {
                            this.updateSize();
                        }

                        break;
                    case 'face':
                        if (Type.isPoint(this)) {
                            this.visProp.face = value;
                            this.board.renderer.changePointStyle(this);
                        }
                        break;
                    case 'trace':
                        if (value === 'false' || value === false) {
                            this.clearTrace();
                            this.visProp.trace = false;
                        } else if (value === 'pause') {
                                this.visProp.trace = false;
                        } else {
                            this.visProp.trace = true;
                        }
                        break;
                    case 'gradient':
                        this.visProp.gradient = value;
                        this.board.renderer.setGradient(this);
                        break;
                    case 'gradientsecondcolor':
                        value = Color.rgba2rgbo(value);
                        this.visProp.gradientsecondcolor = value[0];
                        this.visProp.gradientsecondopacity = value[1];
                        this.board.renderer.updateGradient(this);
                        break;
                    case 'gradientsecondopacity':
                        this.visProp.gradientsecondopacity = value;
                        this.board.renderer.updateGradient(this);
                        break;
                    case 'withlabel':
                        this.visProp.withlabel = value;
                        if (!Type.evaluate(value)) {
                            if (this.label && this.hasLabel) {
                                //this.label.hideElement();
                                this.label.setAttribute({visible: false});
                            }
                        } else {
                            if (!this.label) {
                                this.createLabel();
                            }
                            //this.label.showElement();
                            this.label.setAttribute({visible: 'inherit'});
                            //this.label.setDisplayRendNode(Type.evaluate(this.visProp.visible));
                        }
                        this.hasLabel = value;
                        break;
                    case 'radius':
                        if (this.type === Const.OBJECT_TYPE_ANGLE || this.type === Const.OBJECT_TYPE_SECTOR) {
                            this.setRadius(value);
                        }
                        break;
                    case 'rotate':
                        if ((this.elementClass === Const.OBJECT_CLASS_TEXT &&
                             Type.evaluate(this.visProp.display) === 'internal') ||
                            this.type === Const.OBJECT_TYPE_IMAGE) {
                            this.addRotation(value);
                        }
                        break;
                    case 'ticksdistance':
                        if (this.type === Const.OBJECT_TYPE_TICKS && Type.isNumber(value)) {
                            this.ticksFunction = this.makeTicksFunction(value);
                        }
                        break;
                    case 'generatelabelvalue':
                        if (this.type === Const.OBJECT_TYPE_TICKS && Type.isFunction(value)) {
                            this.generateLabelValue = value;
                        }
                        break;
                    case 'onpolygon':
                        if (this.type === Const.OBJECT_TYPE_GLIDER) {
                            this.onPolygon = !!value;
                        }
                        break;
                    case 'disabled':
                        // button, checkbox, input. Is not available on initial call.
                        if (Type.exists(this.rendNodeTag)) {
                            this.rendNodeTag.disabled = !!value;
                        }
                        break;
                    case 'checked':
                        // checkbox Is not available on initial call.
                        if (Type.exists(this.rendNodeTag)) {
                            this.rendNodeCheckbox.checked = !!value;
                        }
                        break;
                    case 'maxlength':
                        // input. Is not available on initial call.
                        if (Type.exists(this.rendNodeTag)) {
                            this.rendNodeTag.maxlength = !!value;
                        }
                        break;
                    case 'layer':
                        this.board.renderer.setLayer(this, Type.evaluate(value));
                        this._set(key, value);
                        break;
                    case 'tabindex':
                        if (Type.exists(this.rendNode)) {
                            this.rendNode.setAttribute('tabindex', value);
                            this._set(key, value);
                        }
                        break;
                    default:
                        if (Type.exists(this.visProp[key]) &&
                            (!JXG.Validator[key] ||
                                (JXG.Validator[key] && JXG.Validator[key](value)) ||
                                (JXG.Validator[key] && Type.isFunction(value) && JXG.Validator[key](value()))
                            )
                        ) {
                            value = (value.toLowerCase && value.toLowerCase() === 'false') ? false : value;
                            this._set(key, value);
                        }
                        break;
                    }
                    this.triggerEventHandlers(['attribute:' + key], [oldvalue, value, this]);
                }
            }

            this.triggerEventHandlers(['attribute'], [properties, this]);

            if (!Type.evaluate(this.visProp.needsregularupdate)) {
                this.board.fullUpdate();
            } else {
                this.board.update(this);
            }

            return this;
        },

        /**
         * Deprecated alias for {@link JXG.GeometryElement#getAttribute}.
         * @deprecated Use {@link JXG.GeometryElement#getAttribute}.
         */
        getProperty: function () {
            JXG.deprecated('getProperty()', 'getAttribute()');
            this.getProperty.apply(this, arguments);
        },

        /**
         * Get the value of the property <tt>key</tt>.
         * @param {String} key The name of the property you are looking for
         * @returns The value of the property
         */
        getAttribute: function (key) {
            var result;
            key = key.toLowerCase();

            switch (key) {
            case 'needsregularupdate':
                result = this.needsRegularUpdate;
                break;
            case 'labelcolor':
                result = this.label.visProp.strokecolor;
                break;
            case 'infoboxtext':
                result = this.infoboxText;
                break;
            case 'withlabel':
                result = this.hasLabel;
                break;
            default:
                result = this.visProp[key];
                break;
            }

            return result;
        },

        /**
         * Set the dash style of an object. See {@link JXG.GeometryElement#dash}
         * for a list of available dash styles.
         * You should use {@link JXG.GeometryElement#setAttribute} instead of this method.
         *
         * @param {number} dash Indicates the new dash style
         * @private
         */
        setDash: function (dash) {
            this.setAttribute({dash: dash});
            return this;
        },

        /**
         * Notify all child elements for updates.
         * @private
         */
        prepareUpdate: function () {
            this.needsUpdate = true;
            return this;
        },

        /**
         * Removes the element from the construction.  This only removes the SVG or VML node of the element and its label (if available) from
         * the renderer, to remove the element completely you should use {@link JXG.Board#removeObject}.
         */
        remove: function () {
            this.board.renderer.remove(this.board.renderer.getElementById(this.id));

            if (this.hasLabel) {
                this.board.renderer.remove(this.board.renderer.getElementById(this.label.id));
            }
            return this;
        },

        /**
         * Returns the coords object where a text that is bound to the element shall be drawn.
         * Differs in some cases from the values that getLabelAnchor returns.
         * @returns {JXG.Coords} JXG.Coords Place where the text shall be drawn.
         * @see JXG.GeometryElement#getLabelAnchor
         */
        getTextAnchor: function () {
            return new Coords(Const.COORDS_BY_USER, [0, 0], this.board);
        },

        /**
         * Returns the coords object where the label of the element shall be drawn.
         * Differs in some cases from the values that getTextAnchor returns.
         * @returns {JXG.Coords} JXG.Coords Place where the text shall be drawn.
         * @see JXG.GeometryElement#getTextAnchor
         */
        getLabelAnchor: function () {
            return new Coords(Const.COORDS_BY_USER, [0, 0], this.board);
        },

        /**
         * Determines whether the element has arrows at start or end of the arc.
         * If it is set to be a "typical" vector, ie lastArrow == true,
         * then the element.type is set to VECTOR.
         * @param {Boolean} firstArrow True if there is an arrow at the start of the arc, false otherwise.
         * @param {Boolean} lastArrow True if there is an arrow at the end of the arc, false otherwise.
         */
        setArrow: function (firstArrow, lastArrow) {
            this.visProp.firstarrow = firstArrow;
            this.visProp.lastarrow = lastArrow;
            if (lastArrow) {
                this.type = Const.OBJECT_TYPE_VECTOR;
                this.elType = 'arrow';
            }

            this.prepareUpdate().update().updateVisibility().updateRenderer();
            return this;
        },

        /**
         * Creates a gradient nodes in the renderer.
         * @see JXG.SVGRenderer#setGradient
         * @private
         */
        createGradient: function () {
            var ev_g = Type.evaluate(this.visProp.gradient);
            if (ev_g === 'linear' || ev_g === 'radial') {
                this.board.renderer.setGradient(this);
            }
        },

         /**
         * Creates a label element for this geometry element.
         * @see #addLabelToElement
         */
        createLabel: function () {
            var attr,
                that = this;

            // this is a dirty hack to resolve the text-dependency. If there is no text element available,
            // just don't create a label. This method is usually not called by a user, so we won't throw
            // an exception here and simply output a warning via JXG.debug.
            if (JXG.elements.text) {
                attr =  Type.deepCopy(this.visProp.label, null);
                attr.id = this.id + 'Label';
                attr.isLabel = true;
                attr.anchor = this;
                attr.priv = this.visProp.priv;

                if (this.visProp.withlabel) {
                    this.label = JXG.elements.text(this.board, [0, 0, function () {
                        if (Type.isFunction(that.name)) {
                            return that.name();
                        }
                        return that.name;
                    }], attr);
                    this.label.needsUpdate = true;
                    this.label.dump = false;
                    this.label.fullUpdate();

                    this.hasLabel = true;
                }
            } else {
                JXG.debug('JSXGraph: Can\'t create label: text element is not available. Make sure you include base/text');
            }

            return this;
        },

        /**
         * Highlights the element.
         * @param {Boolean} [force=false] Force the highlighting
         * @returns {JXG.Board}
         */
        highlight: function (force) {
            force = Type.def(force, false);
            // I know, we have the JXG.Board.highlightedObjects AND JXG.GeometryElement.highlighted and YES we need both.
            // Board.highlightedObjects is for the internal highlighting and GeometryElement.highlighted is for user highlighting
            // initiated by the user, e.g. through custom DOM events. We can't just pick one because this would break user
            // defined highlighting in many ways:
            //  * if overriding the highlight() methods the user had to handle the highlightedObjects stuff, otherwise he'd break
            //    everything (e.g. the pie chart example https://jsxgraph.org/wiki/index.php/Pie_chart (not exactly
            //    user defined but for this type of chart the highlight method was overridden and not adjusted to the changes in here)
            //    where it just kept highlighting until the radius of the pie was far beyond infinity...
            //  * user defined highlighting would get pointless, everytime the user highlights something using .highlight(), it would get
            //    dehighlighted immediately, because highlight puts the element into highlightedObjects and from there it gets dehighlighted
            //    through dehighlightAll.

            // highlight only if not highlighted
            if (Type.evaluate(this.visProp.highlight) && (!this.highlighted || force)) {
                this.highlighted = true;
                this.board.highlightedObjects[this.id] = this;
                this.board.renderer.highlight(this);
            }
            return this;
        },

        /**
         * Uses the "normal" properties of the element.
         * @returns {JXG.Board}
         */
        noHighlight: function () {
            // see comment in JXG.GeometryElement.highlight()

            // dehighlight only if not highlighted
            if (this.highlighted) {
                this.highlighted = false;
                delete this.board.highlightedObjects[this.id];
                this.board.renderer.noHighlight(this);
            }
            return this;
        },

        /**
         * Removes all objects generated by the trace function.
         */
        clearTrace: function () {
            var obj;

            for (obj in this.traces) {
                if (this.traces.hasOwnProperty(obj)) {
                    this.board.renderer.remove(this.traces[obj]);
                }
            }

            this.numTraces = 0;
            return this;
        },

        /**
         * Copy the element to background. This is used for tracing elements.
         * @returns {JXG.GeometryElement} A reference to the element
         */
        cloneToBackground: function () {
            return this;
        },

        /**
         * Dimensions of the smallest rectangle enclosing the element.
         * @returns {Array} The coordinates of the enclosing rectangle in a format
         * like the bounding box in {@link JXG.Board#setBoundingBox}.
         * 
         * @returns {Array} similar to {@link JXG.Board#setBoundingBox}.
         */
        bounds: function () {
            return [0, 0, 0, 0];
        },

        /**
         * Normalize the element's standard form.
         * @private
         */
        normalize: function () {
            this.stdform = Mat.normalize(this.stdform);
            return this;
        },

        /**
         * EXPERIMENTAL. Generate JSON object code of visProp and other properties.
         * @type String
         * @private
         * @ignore
         * @returns JSON string containing element's properties.
         */
        toJSON: function () {
            var vis, key,
                json = ['{"name":', this.name];

            json.push(', ' + '"id":' + this.id);

            vis = [];
            for (key in this.visProp) {
                if (this.visProp.hasOwnProperty(key)) {
                    if (Type.exists(this.visProp[key])) {
                        vis.push('"' + key + '":' + this.visProp[key]);
                    }
                }
            }
            json.push(', "visProp":{' + vis.toString() + '}');
            json.push('}');

            return json.join('');
        },

        /**
         * Rotate texts or images by a given degree. Works only for texts where JXG.Text#display equal to "internal".
         * @param {number} angle The degree of the rotation (90 means vertical text).
         * @see JXG.GeometryElement#rotate
         */
        addRotation: function (angle) {
            var tOffInv, tOff, tS, tSInv, tRot,
                that = this;

            if (((this.elementClass === Const.OBJECT_CLASS_TEXT &&
                    Type.evaluate(this.visProp.display) === 'internal') ||
                    this.type === Const.OBJECT_TYPE_IMAGE) && angle !== 0) {

                tOffInv = this.board.create('transform', [
                    function () {
                        return -that.X();
                    }, function () {
                        return -that.Y();
                    }
                ], {type: 'translate'});

                tOff = this.board.create('transform', [
                    function () {
                        return that.X();
                    }, function () {
                        return that.Y();
                    }
                ], {type: 'translate'});

                tS = this.board.create('transform', [
                    function () {
                        return that.board.unitX / that.board.unitY;
                    }, function () {
                        return 1;
                    }
                ], {type: 'scale'});

                tSInv = this.board.create('transform', [
                    function () {
                        return that.board.unitY / that.board.unitX;
                    }, function () {
                        return 1;
                    }
                ], {type: 'scale'});

                tRot = this.board.create('transform', [
                        function() { return Type.evaluate(angle) * Math.PI / 180; }
                    ], {type: 'rotate'});

                tOffInv.bindTo(this);
                tS.bindTo(this);
                tRot.bindTo(this);
                tSInv.bindTo(this);
                tOff.bindTo(this);
            }

            return this;
        },

        /**
         * Set the highlightStrokeColor of an element
         * @param {String} sColor String which determines the stroke color of an object when its highlighted.
         * @see JXG.GeometryElement#highlightStrokeColor
         * @deprecated Use {@link JXG.GeometryElement#setAttribute}
         */
        highlightStrokeColor: function (sColor) {
            JXG.deprecated('highlightStrokeColor()', 'setAttribute()');
            this.setAttribute({highlightStrokeColor: sColor});
            return this;
        },

        /**
         * Set the strokeColor of an element
         * @param {String} sColor String which determines the stroke color of an object.
         * @see JXG.GeometryElement#strokeColor
         * @deprecated Use {@link JXG.GeometryElement#setAttribute}
         */
        strokeColor: function (sColor) {
            JXG.deprecated('strokeColor()', 'setAttribute()');
            this.setAttribute({strokeColor: sColor});
            return this;
        },

        /**
         * Set the strokeWidth of an element
         * @param {Number} width Integer which determines the stroke width of an outline.
         * @see JXG.GeometryElement#strokeWidth
         * @deprecated Use {@link JXG.GeometryElement#setAttribute}
         */
        strokeWidth: function (width) {
            JXG.deprecated('strokeWidth()', 'setAttribute()');
            this.setAttribute({strokeWidth: width});
            return this;
        },

        /**
         * Set the fillColor of an element
         * @param {String} fColor String which determines the fill color of an object.
         * @see JXG.GeometryElement#fillColor
         * @deprecated Use {@link JXG.GeometryElement#setAttribute}
         */
        fillColor: function (fColor) {
            JXG.deprecated('fillColor()', 'setAttribute()');
            this.setAttribute({fillColor: fColor});
            return this;
        },

        /**
         * Set the highlightFillColor of an element
         * @param {String} fColor String which determines the fill color of an object when its highlighted.
         * @see JXG.GeometryElement#highlightFillColor
         * @deprecated Use {@link JXG.GeometryElement#setAttribute}
         */
        highlightFillColor: function (fColor) {
            JXG.deprecated('highlightFillColor()', 'setAttribute()');
            this.setAttribute({highlightFillColor: fColor});
            return this;
        },

        /**
         * Set the labelColor of an element
         * @param {String} lColor String which determines the text color of an object's label.
         * @see JXG.GeometryElement#labelColor
         * @deprecated Use {@link JXG.GeometryElement#setAttribute}
         */
        labelColor: function (lColor) {
            JXG.deprecated('labelColor()', 'setAttribute()');
            this.setAttribute({labelColor: lColor});
            return this;
        },

        /**
         * Set the dash type of an element
         * @param {Number} d Integer which determines the way of dashing an element's outline.
         * @see JXG.GeometryElement#dash
         * @deprecated Use {@link JXG.GeometryElement#setAttribute}
         */
        dash: function (d) {
            JXG.deprecated('dash()', 'setAttribute()');
            this.setAttribute({dash: d});
            return this;
        },

        /**
         * Set the visibility of an element
         * @param {Boolean} v Boolean which determines whether the element is drawn.
         * @see JXG.GeometryElement#visible
         * @deprecated Use {@link JXG.GeometryElement#setAttribute}
         */
        visible: function (v) {
            JXG.deprecated('visible()', 'setAttribute()');
            this.setAttribute({visible: v});
            return this;
        },

        /**
         * Set the shadow of an element
         * @param {Boolean} s Boolean which determines whether the element has a shadow or not.
         * @see JXG.GeometryElement#shadow
         * @deprecated Use {@link JXG.GeometryElement#setAttribute}
         */
        shadow: function (s) {
            JXG.deprecated('shadow()', 'setAttribute()');
            this.setAttribute({shadow: s});
            return this;
        },

        /**
         * The type of the element as used in {@link JXG.Board#create}.
         * @returns {String}
         */
        getType: function () {
            return this.elType;
        },

        /**
         * List of the element ids resp. values used as parents in {@link JXG.Board#create}.
         * @returns {Array}
         */
        getParents: function () {
            return Type.isArray(this.parents) ? this.parents : [];
        },

        /**
         * Snaps the element to the grid. Only works for points, lines and circles. Points will snap to the grid
         * as defined in their properties {@link JXG.Point#snapSizeX} and {@link JXG.Point#snapSizeY}. Lines and circles
         * will snap their parent points to the grid, if they have {@link JXG.Point#snapToGrid} set to true.
         * @returns {JXG.GeometryElement} Reference to the element.
         */
        snapToGrid: function () {
            return this;
        },

        /**
         * Snaps the element to points. Only works for points. Points will snap to the next point
         * as defined in their properties {@link JXG.Point#attractorDistance} and {@link JXG.Point#attractorUnit}.
         * Lines and circles
         * will snap their parent points to points.
         * @returns {JXG.GeometryElement} Reference to the element.
         */
        snapToPoints: function () {
            return this;
        },

        /**
         * Retrieve a copy of the current visProp.
         * @returns {Object}
         */
        getAttributes: function () {
            var attributes = Type.deepCopy(this.visProp),
                /*
                cleanThis = ['attractors', 'snatchdistance', 'traceattributes', 'frozen',
                    'shadow', 'gradientangle', 'gradientsecondopacity', 'gradientpositionx', 'gradientpositiony',
                    'needsregularupdate', 'zoom', 'layer', 'offset'],
                */
                cleanThis = [],
                i, len = cleanThis.length;

            attributes.id = this.id;
            attributes.name = this.name;

            for (i = 0; i < len; i++) {
                delete attributes[cleanThis[i]];
            }

            return attributes;
        },

        /**
         * Checks whether (x,y) is near the element.
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @returns {Boolean} True if (x,y) is near the element, False otherwise.
         */
        hasPoint: function (x, y) {
            return false;
        },

        /**
         * Adds ticks to this line or curve. Ticks can be added to a curve or any kind of line: line, arrow, and axis.
         * @param {JXG.Ticks} ticks Reference to a ticks object which is describing the ticks (color, distance, how many, etc.).
         * @returns {String} Id of the ticks object.
         */
        addTicks: function (ticks) {
            if (ticks.id === '' || !Type.exists(ticks.id)) {
                ticks.id = this.id + '_ticks_' + (this.ticks.length + 1);
            }

            this.board.renderer.drawTicks(ticks);
            this.ticks.push(ticks);

            return ticks.id;
        },

        /**
         * Removes all ticks from a line or curve.
         */
        removeAllTicks: function () {
            var t;
            if (Type.exists(this.ticks)) {
                for (t = this.ticks.length - 1; t >= 0; t--) {
                    this.removeTicks(this.ticks[t]);
                }
                this.ticks = [];
                this.board.update();
            }
        },

        /**
         * Removes ticks identified by parameter named tick from this line or curve.
         * @param {JXG.Ticks} tick Reference to tick object to remove.
         */
        removeTicks: function (tick) {
            var t, j;

            if (Type.exists(this.defaultTicks) && this.defaultTicks === tick) {
                this.defaultTicks = null;
            }

            if (Type.exists(this.ticks)) {
                for (t = this.ticks.length - 1; t >= 0; t--) {
                    if (this.ticks[t] === tick) {
                        this.board.removeObject(this.ticks[t]);

                        if (this.ticks[t].ticks) {
                            for (j = 0; j < this.ticks[t].ticks.length; j++) {
                                if (Type.exists(this.ticks[t].labels[j])) {
                                    this.board.removeObject(this.ticks[t].labels[j]);
                                }
                            }
                        }

                        delete this.ticks[t];
                        break;
                    }
                }
            }
        },

        /**
         * Determine values of snapSizeX and snapSizeY. If the attributes
         * snapSizex and snapSizeY are greater than zero, these values are taken.
         * Otherwise, determine the distance between major ticks of the
         * default axes.
         * @returns {Array} containing the snap sizes for x and y direction.
         * @private
         */
        getSnapSizes: function() {
            var sX, sY, ticks;

            sX = Type.evaluate(this.visProp.snapsizex);
            sY = Type.evaluate(this.visProp.snapsizey);

            if (sX <= 0 && this.board.defaultAxes && this.board.defaultAxes.x.defaultTicks) {
                ticks = this.board.defaultAxes.x.defaultTicks;
                sX = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1);
            }

            if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) {
                ticks = this.board.defaultAxes.y.defaultTicks;
                sY = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1);
            }

            return [sX, sY];
        },

        /**
         * Move an element to its nearest grid point.
         * The function uses the coords object of the element as
         * its actual position. If there is no coords object or if the object is fixed, nothing is done.
         * @param {Boolean} force force snapping independent from what the snaptogrid attribute says
         * @param {Boolean} fromParent True if the drag comes from a child element. This is the case if a line
         *    through two points is dragged. In this case we do not try to force the points to stay inside of
         *    the visible board, but the distance between the two points stays constant.
         * @returns {JXG.GeometryElement} Reference to this element
         */
        handleSnapToGrid: function (force, fromParent) {
            var x, y, rx, ry, rcoords,
                boardBB, res, sX, sY,
                needsSnapToGrid = false,
                attractToGrid = Type.evaluate(this.visProp.attracttogrid),
                ev_au = Type.evaluate(this.visProp.attractorunit),
                ev_ad = Type.evaluate(this.visProp.attractordistance);

            if (!Type.exists(this.coords) || Type.evaluate(this.visProp.fixed)) {
                return this;
            }

            needsSnapToGrid = Type.evaluate(this.visProp.snaptogrid) || attractToGrid || force === true;

            if (needsSnapToGrid) {
                x = this.coords.usrCoords[1];
                y = this.coords.usrCoords[2];
                res = this.getSnapSizes();
                sX = res[0];
                sY = res[1];

                // If no valid snap sizes are available, don't change the coords.
                if (sX > 0 && sY > 0) {
                    boardBB = this.board.getBoundingBox();
                    rx = Math.round(x / sX) * sX;
                    ry = Math.round(y / sY) * sY;
                    rcoords = new JXG.Coords(Const.COORDS_BY_USER, [rx, ry], this.board);
                    if (!attractToGrid ||
                        rcoords.distance(
                            ev_au === 'screen' ? Const.COORDS_BY_SCREEN : Const.COORDS_BY_USER, this.coords
                            ) < ev_ad) {
                        x = rx;
                        y = ry;
                        // Checking whether x and y are still within boundingBox.
                        // If not, adjust them to remain within the board.
                        // Otherwise a point may become invisible.
                        if (!fromParent) {
                            if (x < boardBB[0]) {
                                x += sX;
                            } else if (x > boardBB[2]) {
                                x -= sX;
                            }

                            if (y < boardBB[3]) {
                                y += sY;
                            } else if (y > boardBB[1]) {
                                y -= sY;
                            }
                        }
                        this.coords.setCoordinates(Const.COORDS_BY_USER, [x, y]);
                    }
                }
            }
            return this;
        },

        getBoundingBox: function() {
            var i, le, v, x, y,
                bb = [Infinity, Infinity, -Infinity, -Infinity];

            if (this.type === Const.OBJECT_TYPE_POLYGON) {
                le = this.vertices.length - 1;
                if (le <= 0) {
                    return bb;
                }
                for (i = 0; i < le; i++) {
                    v = this.vertices[i].X();
                    bb[0] = (v < bb[0]) ? v : bb[0];
                    bb[2] = (v > bb[2]) ? v : bb[2];
                    v = this.vertices[i].Y();
                    bb[1] = (v < bb[1]) ? v : bb[1];
                    bb[3] = (v > bb[3]) ? v : bb[3];
                }
            } else if (this.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                x = this.center.X();
                y = this.center.Y();
                bb = [x - this.radius, y + this.radius, x + this.radius, y - this.radius];
            } else if (this.elementClass === Const.OBJECT_CLASS_CURVE) {
                le = this.vertices.length;
                if (le === 0) {
                    return bb;
                }
                for (i = 0; i < le; i++) {
                    v = this.points[i].coords.usrCoords[1];
                    bb[0] = (v < bb[0]) ? v : bb[0];
                    bb[2] = (v > bb[2]) ? v : bb[2];
                    v = this.points[i].coords.usrCoords[1];
                    bb[1] = (v < bb[1]) ? v : bb[1];
                    bb[3] = (v > bb[3]) ? v : bb[3];
                }
            }

            return bb;
        },

        /**
         * Alias of {@link JXG.EventEmitter.on}.
         *
         * @name addEvent
         * @memberof JXG.GeometryElement
         * @function
         */
        addEvent: JXG.shortcut(JXG.GeometryElement.prototype, 'on'),

        /**
         * Alias of {@link JXG.EventEmitter.off}.
         *
         * @name removeEvent
         * @memberof JXG.GeometryElement
         * @function
         */
        removeEvent: JXG.shortcut(JXG.GeometryElement.prototype, 'off'),

        /* **************************
         *     EVENT DEFINITION
         * for documentation purposes
         * ************************** */

        //region Event handler documentation
        /**
         * @event
         * @description This event is fired whenever the user is hovering over an element.
         * @name JXG.GeometryElement#over
         * @param {Event} e The browser's event object.
         */
        __evt__over: function (e) { },

        /**
         * @event
         * @description This event is fired whenever the user puts the mouse over an element.
         * @name JXG.GeometryElement#mouseover
         * @param {Event} e The browser's event object.
         */
        __evt__mouseover: function (e) { },

        /**
         * @event
         * @description This event is fired whenever the user is leaving an element.
         * @name JXG.GeometryElement#out
         * @param {Event} e The browser's event object.
         */
        __evt__out: function (e) { },

        /**
         * @event
         * @description This event is fired whenever the user puts the mouse away from an element.
         * @name JXG.GeometryElement#mouseout
         * @param {Event} e The browser's event object.
         */
        __evt__mouseout: function (e) { },

        /**
         * @event
         * @description This event is fired whenever the user is moving over an element.
         * @name JXG.GeometryElement#move
         * @param {Event} e The browser's event object.
         */
        __evt__move: function (e) { },

        /**
         * @event
         * @description This event is fired whenever the user is moving the mouse over an element.
         * @name JXG.GeometryElement#mousemove
         * @param {Event} e The browser's event object.
         */
        __evt__mousemove: function (e) { },

        /**
         * @event
         * @description This event is fired whenever the user drags an element.
         * @name JXG.GeometryElement#drag
         * @param {Event} e The browser's event object.
         */
        __evt__drag: function (e) { },

        /**
         * @event
         * @description This event is fired whenever the user drags the element with a mouse.
         * @name JXG.GeometryElement#mousedrag
         * @param {Event} e The browser's event object.
         */
        __evt__mousedrag: function (e) { },

        /**
         * @event
         * @description This event is fired whenever the user drags the element with a pen.
         * @name JXG.GeometryElement#pendrag
         * @param {Event} e The browser's event object.
         */
        __evt__pendrag: function (e) { },

        /**
         * @event
         * @description This event is fired whenever the user drags the element on a touch device.
         * @name JXG.GeometryElement#touchdrag
         * @param {Event} e The browser's event object.
         */
        __evt__touchdrag: function (e) { },

        /**
         * @event
         * @description Whenever the user starts to touch or click an element.
         * @name JXG.GeometryElement#down
         * @param {Event} e The browser's event object.
         */
        __evt__down: function (e) { },

        /**
         * @event
         * @description Whenever the user starts to click an element.
         * @name JXG.GeometryElement#mousedown
         * @param {Event} e The browser's event object.
         */
        __evt__mousedown: function (e) { },

        /**
         * @event
         * @description Whenever the user taps an element with the pen.
         * @name JXG.GeometryElement#pendown
         * @param {Event} e The browser's event object.
         */
        __evt__pendown: function (e) { },

        /**
         * @event
         * @description Whenever the user starts to touch an element.
         * @name JXG.GeometryElement#touchdown
         * @param {Event} e The browser's event object.
         */
        __evt__touchdown: function (e) { },

        /**
         * @event
         * @description Whenever the user stops to touch or click an element.
         * @name JXG.GeometryElement#up
         * @param {Event} e The browser's event object.
         */
        __evt__up: function (e) { },

        /**
         * @event
         * @description Whenever the user releases the mousebutton over an element.
         * @name JXG.GeometryElement#mouseup
         * @param {Event} e The browser's event object.
         */
        __evt__mouseup: function (e) { },

        /**
         * @event
         * @description Whenever the user lifts the pen over an element.
         * @name JXG.GeometryElement#penup
         * @param {Event} e The browser's event object.
         */
        __evt__penup: function (e) { },

        /**
         * @event
         * @description Whenever the user stops touching an element.
         * @name JXG.GeometryElement#touchup
         * @param {Event} e The browser's event object.
         */
        __evt__touchup: function (e) {},

        /**
         * @event
         * @description Notify every time an attribute is changed.
         * @name JXG.GeometryElement#attribute
         * @param {Object} o A list of changed attributes and their new value.
         * @param {Object} el Reference to the element
         */
        __evt__attribute: function (o, el) {},

        /**
         * @event
         * @description This is a generic event handler. It exists for every possible attribute that can be set for
         * any element, e.g. if you want to be notified everytime an element's strokecolor is changed, is the event
         * <tt>attribute:strokecolor</tt>.
         * @name JXG.GeometryElement#attribute:key
         * @param val The old value.
         * @param nval The new value
         * @param {Object} el Reference to the element
         */
        __evt__attribute_: function (val, nval, el) {},

        /**
         * @ignore
         */
        __evt: function () {}
        //endregion

    });

    return JXG.GeometryElement;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Alfred Wassermann

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, console: true, window: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 options
 math/math
 math/geometry
 math/numerics
 base/coords
 base/constants
 base/element
 parser/geonext
 utils/type
  elements:
   transform
 */

/**
 * @fileoverview The geometry object CoordsElement is defined in this file.
 * This object provides the coordinate handling of points, images and texts.
 */

define('base/coordselement',[
    'jxg', 'math/math', 'math/geometry', 'math/numerics', 'math/statistics', 'base/coords', 'base/constants', 'utils/type',
], function (JXG, Mat, Geometry, Numerics, Statistics, Coords, Const, Type) {

    "use strict";

    /**
     * An element containing coords is the basic geometric element. Based on points lines and circles can be constructed which can be intersected
     * which in turn are points again which can be used to construct new lines, circles, polygons, etc. This class holds methods for
     * all kind of coordinate elements like points, texts and images.
     * @class Creates a new coords element object. Do not use this constructor to create an element.
     *
     * @private
     * @augments JXG.GeometryElement
     * @param {Array} coordinates An array with the affine user coordinates of the point.
     * {@link JXG.Options#elements}, and - optionally - a name and an id.
     */
    JXG.CoordsElement = function (coordinates, isLabel) {
        var i;

        if (!Type.exists(coordinates)) {
            coordinates = [1, 0, 0];
        }

        for (i = 0; i < coordinates.length; ++i) {
            coordinates[i] = parseFloat(coordinates[i]);
        }

        /**
         * Coordinates of the element.
         * @type JXG.Coords
         * @private
         */
        this.coords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
        this.initialCoords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);

        /**
         * Relative position on a slide element (line, circle, curve) if element is a glider on this element.
         * @type Number
         * @private
         */
        this.position = null;

        /**
         * True if there the method this.updateConstraint() has been set. It is
         * probably different from the prototype function() {return this;}.
         * Used in updateCoords fo glider elements.
         *
         * @see JXG.CoordsElement#updateCoords
         * @type Boolean
         * @private
         */
        this.isConstrained = false;

        /**
         * Determines whether the element slides on a polygon if point is a glider.
         * @type Boolean
         * @default false
         * @private
         */
        this.onPolygon = false;

        /**
         * When used as a glider this member stores the object, where to glide on.
         * To set the object to glide on use the method
         * {@link JXG.Point#makeGlider} and DO NOT set this property directly
         * as it will break the dependency tree.
         * @type JXG.GeometryElement
         */
        this.slideObject = null;

        /**
         * List of elements the element is bound to, i.e. the element glides on.
         * Only the last entry is active.
         * Use {@link JXG.Point#popSlideObject} to remove the currently active slideObject.
         */
        this.slideObjects = [];

        /**
         * A {@link JXG.CoordsElement#updateGlider} call is usually followed
         * by a general {@link JXG.Board#update} which calls
         * {@link JXG.CoordsElement#updateGliderFromParent}.
         * To prevent double updates, {@link JXG.CoordsElement#needsUpdateFromParent}
         * is set to false in updateGlider() and reset to true in the following call to
         * {@link JXG.CoordsElement#updateGliderFromParent}
         * @type Boolean
         */
        this.needsUpdateFromParent = true;

        /**
         * Stores the groups of this element in an array of Group.
         * @type Array
         * @see JXG.Group
         * @private
         */
        this.groups = [];

        /*
         * Do we need this?
         */
        this.Xjc = null;
        this.Yjc = null;

        // documented in GeometryElement
        this.methodMap = Type.deepCopy(this.methodMap, {
            move: 'moveTo',
            moveTo: 'moveTo',
            moveAlong: 'moveAlong',
            visit: 'visit',
            glide: 'makeGlider',
            makeGlider: 'makeGlider',
            intersect: 'makeIntersection',
            makeIntersection: 'makeIntersection',
            X: 'X',
            Y: 'Y',
            free: 'free',
            setPosition: 'setGliderPosition',
            setGliderPosition: 'setGliderPosition',
            addConstraint: 'addConstraint',
            dist: 'Dist',
            onPolygon: 'onPolygon'
        });

        /*
         * this.element may have been set by the object constructor.
         */
        if (Type.exists(this.element)) {
            this.addAnchor(coordinates, isLabel);
        }
        this.isDraggable = true;

    };

    JXG.extend(JXG.CoordsElement.prototype, /** @lends JXG.CoordsElement.prototype */ {
        /**
         * Dummy function for unconstrained points or gliders.
         * @private
         */
        updateConstraint: function () {
            return this;
        },

        /**
         * Updates the coordinates of the element.
         * @private
         */
        updateCoords: function (fromParent) {
            var i;

            if (!this.needsUpdate) {
                return this;
            }

            if (!Type.exists(fromParent)) {
                fromParent = false;
            }

            if (!Type.evaluate(this.visProp.frozen)) {
                this.updateConstraint();
            }

            /*
             * We need to calculate the new coordinates no matter of the elements visibility because
             * a child could be visible and depend on the coordinates of the element/point (e.g. perpendicular).
             *
             * Check if the element is a glider and calculate new coords in dependency of this.slideObject.
             * This function is called with fromParent==true in case it is a glider element for example if
             * the defining elements of the line or circle have been changed.
             */
            if (this.type === Const.OBJECT_TYPE_GLIDER) {
                if (this.isConstrained) {
                    fromParent = false;
                }

                if (fromParent) {
                    this.updateGliderFromParent();
                } else {
                    this.updateGlider();
                }
            }

            this.updateTransform(fromParent);

            return this;
        },

        /**
         * Update of glider in case of dragging the glider or setting the postion of the glider.
         * The relative position of the glider has to be updated.
         *
         * In case of a glider on a line:
         * If the second point is an ideal point, then -1 < this.position < 1,
         * this.position==+/-1 equals point2, this.position==0 equals point1
         *
         * If the first point is an ideal point, then 0 < this.position < 2
         * this.position==0  or 2 equals point1, this.position==1 equals point2
         *
         * @private
         */
        updateGlider: function () {
            var i, p1c, p2c, d, v, poly, cc, pos, sgn,
                alpha, beta,
                delta = 2.0 * Math.PI,
                angle,
                cp, c, invMat, newCoords, newPos,
                doRound = false,
                ev_sw,
                slide = this.slideObject,
                res, cu,
                slides = [],
                isTransformed;

            this.needsUpdateFromParent = false;
            if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                if (Type.evaluate(this.visProp.isgeonext)) {
                    delta = 1.0;
                }
                newCoords = Geometry.projectPointToCircle(this, slide, this.board);
                newPos = Geometry.rad([slide.center.X() + 1.0, slide.center.Y()], slide.center, this) / delta;
            } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) {
                /*
                 * onPolygon==true: the point is a slider on a segment and this segment is one of the
                 * "borders" of a polygon.
                 * This is a GEONExT feature.
                 */
                if (this.onPolygon) {
                    p1c = slide.point1.coords.usrCoords;
                    p2c = slide.point2.coords.usrCoords;
                    i = 1;
                    d = p2c[i] - p1c[i];

                    if (Math.abs(d) < Mat.eps) {
                        i = 2;
                        d = p2c[i] - p1c[i];
                    }

                    cc = Geometry.projectPointToLine(this, slide, this.board);
                    pos = (cc.usrCoords[i] - p1c[i]) / d;
                    poly = slide.parentPolygon;

                    if (pos < 0) {
                        for (i = 0; i < poly.borders.length; i++) {
                            if (slide === poly.borders[i]) {
                                slide = poly.borders[(i - 1 + poly.borders.length) % poly.borders.length];
                                break;
                            }
                        }
                    } else if (pos > 1.0) {
                        for (i = 0; i < poly.borders.length; i++) {
                            if (slide === poly.borders[i]) {
                                slide = poly.borders[(i + 1 + poly.borders.length) % poly.borders.length];
                                break;
                            }
                        }
                    }

                    // If the slide object has changed, save the change to the glider.
                    if (slide.id !== this.slideObject.id) {
                        this.slideObject = slide;
                    }
                }

                p1c = slide.point1.coords;
                p2c = slide.point2.coords;

                // Distance between the two defining points
                d = p1c.distance(Const.COORDS_BY_USER, p2c);

                // The defining points are identical
                if (d < Mat.eps) {
                    //this.coords.setCoordinates(Const.COORDS_BY_USER, p1c);
                    newCoords = p1c;
                    doRound = true;
                    newPos = 0.0;
                } else {
                    //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToLine(this, slide, this.board).usrCoords, false);
                    newCoords = Geometry.projectPointToLine(this, slide, this.board);
                    p1c = p1c.usrCoords.slice(0);
                    p2c = p2c.usrCoords.slice(0);

                    // The second point is an ideal point
                    if (Math.abs(p2c[0]) < Mat.eps) {
                        i = 1;
                        d = p2c[i];

                        if (Math.abs(d) < Mat.eps) {
                            i = 2;
                            d = p2c[i];
                        }

                        d = (newCoords.usrCoords[i] - p1c[i]) / d;
                        sgn = (d >= 0) ? 1 : -1;
                        d = Math.abs(d);
                        newPos = sgn * d / (d + 1);

                    // The first point is an ideal point
                    } else if (Math.abs(p1c[0]) < Mat.eps) {
                        i = 1;
                        d = p1c[i];

                        if (Math.abs(d) < Mat.eps) {
                            i = 2;
                            d = p1c[i];
                        }

                        d = (newCoords.usrCoords[i] - p2c[i]) / d;

                        // 1.0 - d/(1-d);
                        if (d < 0.0) {
                            newPos = (1 - 2.0 * d) / (1.0 - d);
                        } else {
                            newPos = 1 / (d + 1);
                        }
                    } else {
                        i = 1;
                        d = p2c[i] - p1c[i];

                        if (Math.abs(d) < Mat.eps) {
                            i = 2;
                            d = p2c[i] - p1c[i];
                        }
                        newPos = (newCoords.usrCoords[i] - p1c[i]) / d;
                    }
                }

                // Snap the glider point of the slider into its appropiate position
                // First, recalculate the new value of this.position
                // Second, call update(fromParent==true) to make the positioning snappier.
                ev_sw = Type.evaluate(this.visProp.snapwidth);
                if (Type.evaluate(ev_sw) > 0.0 &&
                    Math.abs(this._smax - this._smin) >= Mat.eps) {
                    newPos = Math.max(Math.min(newPos, 1), 0);

                    v = newPos * (this._smax - this._smin) + this._smin;
                    v = Math.round(v / ev_sw) * ev_sw;
                    newPos = (v - this._smin) / (this._smax - this._smin);
                    this.update(true);
                }

                p1c = slide.point1.coords;
                if (!Type.evaluate(slide.visProp.straightfirst) &&
                    Math.abs(p1c.usrCoords[0]) > Mat.eps && newPos < 0) {
                    newCoords = p1c;
                    doRound = true;
                    newPos = 0;
                }

                p2c = slide.point2.coords;
                if (!Type.evaluate(slide.visProp.straightlast) &&
                    Math.abs(p2c.usrCoords[0]) > Mat.eps && newPos > 1) {
                    newCoords = p2c;
                    doRound = true;
                    newPos = 1;
                }
            } else if (slide.type === Const.OBJECT_TYPE_TURTLE) {
                // In case, the point is a constrained glider.
                this.updateConstraint();
                res = Geometry.projectPointToTurtle(this, slide, this.board);
                newCoords = res[0];
                newPos = res[1];     // save position for the overwriting below
            } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) {
                if ((slide.type === Const.OBJECT_TYPE_ARC ||
                     slide.type === Const.OBJECT_TYPE_SECTOR)) {
                    newCoords = Geometry.projectPointToCircle(this, slide, this.board);

                    angle = Geometry.rad(slide.radiuspoint, slide.center, this);
                    alpha = 0.0;
                    beta = Geometry.rad(slide.radiuspoint, slide.center, slide.anglepoint);
                    newPos = angle;

                    ev_sw = Type.evaluate(slide.visProp.selection);
                    if ((ev_sw === 'minor' && beta > Math.PI) ||
                        (ev_sw === 'major' && beta < Math.PI)) {
                        alpha = beta;
                        beta = 2 * Math.PI;
                    }

                    // Correct the position if we are outside of the sector/arc
                    if (angle < alpha || angle > beta) {
                        newPos = beta;

                        if ((angle < alpha && angle > alpha * 0.5) || (angle > beta && angle > beta * 0.5 + Math.PI)) {
                            newPos = alpha;
                        }

                        this.needsUpdateFromParent = true;
                        this.updateGliderFromParent();
                    }

                    delta = beta - alpha;
                    if (this.visProp.isgeonext) {
                        delta = 1.0;
                    }
                    if (Math.abs(delta) > Mat.eps) {
                        newPos /= delta;
                    }
                } else {
                    // In case, the point is a constrained glider.
                    this.updateConstraint();

                    // Handle the case if the curve comes from a transformation of a continous curve.
                    if (slide.transformations.length > 0) {
                        isTransformed = false;
                        res = slide.getTransformationSource();
                        if (res[0]) {
                            isTransformed = res[0];
                            slides.push(slide);
                            slides.push(res[1]);
                        }
                        // Recurse
                        while (res[0] && Type.exists(res[1]._transformationSource)) {
                            res = res[1].getTransformationSource();
                            slides.push(res[1]);
                        }

                        cu = this.coords.usrCoords;
                        if (isTransformed) {
                            for (i = 0; i < slides.length; i++) {
                                slides[i].updateTransformMatrix();
                                invMat = Mat.inverse(slides[i].transformMat);
                                cu = Mat.matVecMult(invMat, cu);
                            }
                            cp = (new Coords(Const.COORDS_BY_USER, cu, this.board)).usrCoords;
                            c = Geometry.projectCoordsToCurve(cp[1], cp[2],
                                        this.position || 0,
                                        slides[slides.length - 1],
                                        this.board);
                            // projectPointCurve() already would apply the transformation.
                            // Since we are projecting on the original curve, we have to do
                            // the transformations "by hand".
                            cu = c[0].usrCoords;
                            for (i = slides.length - 2; i >= 0; i--) {
                                cu = Mat.matVecMult(slides[i].transformMat, cu);
                            }
                            c[0] = new Coords(Const.COORDS_BY_USER, cu, this.board);
                        } else {
                            slide.updateTransformMatrix();
                            invMat = Mat.inverse(slide.transformMat);
                            cu = Mat.matVecMult(invMat, cu);
                            cp = (new Coords(Const.COORDS_BY_USER, cu, this.board)).usrCoords;
                            c = Geometry.projectCoordsToCurve(cp[1], cp[2], this.position || 0, slide, this.board);
                        }

                        newCoords = c[0];
                        newPos = c[1];
                    } else {
                        res = Geometry.projectPointToCurve(this, slide, this.board);
                        newCoords = res[0];
                        newPos = res[1]; // save position for the overwriting below
                    }
                }
            } else if (Type.isPoint(slide)) {
                //this.coords.setCoordinates(Const.COORDS_BY_USER, Geometry.projectPointToPoint(this, slide, this.board).usrCoords, false);
                newCoords = Geometry.projectPointToPoint(this, slide, this.board);
                newPos = this.position; // save position for the overwriting below
            }

            this.coords.setCoordinates(Const.COORDS_BY_USER, newCoords.usrCoords, doRound);
            this.position = newPos;
        },

        /**
         * Update of a glider in case a parent element has been updated. That means the
         * relative position of the glider stays the same.
         * @private
         */
        updateGliderFromParent: function () {
            var p1c, p2c, r, lbda, c,
                slide = this.slideObject,
                slides = [],
                res, i,
                isTransformed,
                baseangle, alpha, angle, beta,
                delta = 2.0 * Math.PI;

            if (!this.needsUpdateFromParent) {
                this.needsUpdateFromParent = true;
                return;
            }

            if (slide.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                r = slide.Radius();
                if (Type.evaluate(this.visProp.isgeonext)) {
                    delta = 1.0;
                }
                c = [
                    slide.center.X() + r * Math.cos(this.position * delta),
                    slide.center.Y() + r * Math.sin(this.position * delta)
                ];
            } else if (slide.elementClass === Const.OBJECT_CLASS_LINE) {
                p1c = slide.point1.coords.usrCoords;
                p2c = slide.point2.coords.usrCoords;

                // If one of the defining points of the line does not exist,
                // the glider should disappear
                if ((p1c[0] === 0 && p1c[1] === 0 && p1c[2] === 0) ||
                    (p2c[0] === 0 && p2c[1] === 0 && p2c[2] === 0)) {
                    c = [0, 0, 0];
                // The second point is an ideal point
                } else if (Math.abs(p2c[0]) < Mat.eps) {
                    lbda = Math.min(Math.abs(this.position), 1 - Mat.eps);
                    lbda /= (1.0 - lbda);

                    if (this.position < 0) {
                        lbda = -lbda;
                    }

                    c = [
                        p1c[0] + lbda * p2c[0],
                        p1c[1] + lbda * p2c[1],
                        p1c[2] + lbda * p2c[2]
                    ];
                // The first point is an ideal point
                } else if (Math.abs(p1c[0]) < Mat.eps) {
                    lbda = Math.max(this.position, Mat.eps);
                    lbda = Math.min(lbda, 2 - Mat.eps);

                    if (lbda > 1) {
                        lbda = (lbda - 1) / (lbda - 2);
                    } else {
                        lbda = (1 - lbda) / lbda;
                    }

                    c = [
                        p2c[0] + lbda * p1c[0],
                        p2c[1] + lbda * p1c[1],
                        p2c[2] + lbda * p1c[2]
                    ];
                } else {
                    lbda = this.position;
                    c = [
                        p1c[0] + lbda * (p2c[0] - p1c[0]),
                        p1c[1] + lbda * (p2c[1] - p1c[1]),
                        p1c[2] + lbda * (p2c[2] - p1c[2])
                    ];
                }
            } else if (slide.type === Const.OBJECT_TYPE_TURTLE) {
                this.coords.setCoordinates(Const.COORDS_BY_USER, [slide.Z(this.position), slide.X(this.position), slide.Y(this.position)]);
                // In case, the point is a constrained glider.
                this.updateConstraint();
                c  = Geometry.projectPointToTurtle(this, slide, this.board)[0].usrCoords;
            } else if (slide.elementClass === Const.OBJECT_CLASS_CURVE) {
                // Handle the case if the curve comes from a transformation of a continuous curve.
                isTransformed = false;
                res = slide.getTransformationSource();
                if (res[0]) {
                    isTransformed = res[0];
                    slides.push(slide);
                    slides.push(res[1]);
                }
                // Recurse
                while (res[0] && Type.exists(res[1]._transformationSource)) {
                    res = res[1].getTransformationSource();
                    slides.push(res[1]);
                }
                if (isTransformed) {
                    this.coords.setCoordinates(Const.COORDS_BY_USER, [
                        slides[slides.length - 1].Z(this.position),
                        slides[slides.length - 1].X(this.position),
                        slides[slides.length - 1].Y(this.position)]);
                } else {
                    this.coords.setCoordinates(Const.COORDS_BY_USER, [
                        slide.Z(this.position),
                        slide.X(this.position),
                        slide.Y(this.position)]);
                }

                if (slide.type === Const.OBJECT_TYPE_ARC || slide.type === Const.OBJECT_TYPE_SECTOR) {
                    baseangle = Geometry.rad([slide.center.X() + 1, slide.center.Y()], slide.center, slide.radiuspoint);

                    alpha = 0.0;
                    beta = Geometry.rad(slide.radiuspoint, slide.center, slide.anglepoint);

                    if ((slide.visProp.selection === 'minor' && beta > Math.PI) ||
                            (slide.visProp.selection === 'major' && beta < Math.PI)) {
                        alpha = beta;
                        beta = 2 * Math.PI;
                    }

                    delta = beta - alpha;
                    if (Type.evaluate(this.visProp.isgeonext)) {
                        delta = 1.0;
                    }
                    angle = this.position * delta;

                    // Correct the position if we are outside of the sector/arc
                    if (angle < alpha || angle > beta) {
                        angle = beta;

                        if ((angle < alpha && angle > alpha * 0.5) ||
                                (angle > beta && angle > beta * 0.5 + Math.PI)) {
                            angle = alpha;
                        }

                        this.position = angle;
                        if (Math.abs(delta) > Mat.eps) {
                            this.position /= delta;
                        }
                    }

                    r = slide.Radius();
                    c = [
                        slide.center.X() + r * Math.cos(this.position * delta + baseangle),
                        slide.center.Y() + r * Math.sin(this.position * delta + baseangle)
                    ];
                } else {
                    // In case, the point is a constrained glider.
                    this.updateConstraint();

                    if (isTransformed) {
                        c = Geometry.projectPointToCurve(this, slides[slides.length - 1], this.board)[0].usrCoords;
                        // projectPointCurve() already would do the transformation.
                        // But since we are projecting on the original curve, we have to do
                        // the transformation "by hand".
                        for (i = slides.length - 2; i >= 0; i--) {
                            c = (new Coords(Const.COORDS_BY_USER,
                                Mat.matVecMult(slides[i].transformMat, c), this.board)).usrCoords;
                        }

                    } else {
                        c = Geometry.projectPointToCurve(this, slide, this.board)[0].usrCoords;
                    }
                }

            } else if (Type.isPoint(slide)) {
                c = Geometry.projectPointToPoint(this, slide, this.board).usrCoords;
            }

            this.coords.setCoordinates(Const.COORDS_BY_USER, c, false);
        },

        updateRendererGeneric: function (rendererMethod) {
            //var wasReal;

            if (!this.needsUpdate) {
                return this;
            }

            if (this.visPropCalc.visible) {
                //wasReal = this.isReal;
                this.isReal = (!isNaN(this.coords.usrCoords[1] + this.coords.usrCoords[2]));
                //Homogeneous coords: ideal point
                this.isReal = (Math.abs(this.coords.usrCoords[0]) > Mat.eps) ? this.isReal : false;

                if (// wasReal &&
                    !this.isReal) {
                    this.updateVisibility(false);
                }
            }

            // Call the renderer only if element is visible.
            // Update the position
            if (this.visPropCalc.visible) {
                this.board.renderer[rendererMethod](this);
            }

            // Update the label if visible.
            if (this.hasLabel && this.visPropCalc.visible && this.label &&
                this.label.visPropCalc.visible && this.isReal) {
                this.label.update();
                this.board.renderer.updateText(this.label);
            }

            // Update rendNode display
            this.setDisplayRendNode();
            // if (this.visPropCalc.visible !== this.visPropOld.visible) {
            //     this.board.renderer.display(this, this.visPropCalc.visible);
            //     this.visPropOld.visible = this.visPropCalc.visible;
            //
            //     if (this.hasLabel) {
            //         this.board.renderer.display(this.label, this.label.visPropCalc.visible);
            //     }
            // }

            this.needsUpdate = false;
            return this;
        },

        /**
         * Getter method for x, this is used by for CAS-points to access point coordinates.
         * @returns {Number} User coordinate of point in x direction.
         */
        X: function () {
            return this.coords.usrCoords[1];
        },

        /**
         * Getter method for y, this is used by CAS-points to access point coordinates.
         * @returns {Number} User coordinate of point in y direction.
         */
        Y: function () {
            return this.coords.usrCoords[2];
        },

        /**
         * Getter method for z, this is used by CAS-points to access point coordinates.
         * @returns {Number} User coordinate of point in z direction.
         */
        Z: function () {
            return this.coords.usrCoords[0];
        },

        /**
         * New evaluation of the function term.
         * This is required for CAS-points: Their XTerm() method is
         * overwritten in {@link JXG.CoordsElement#addConstraint}.
         *
         * @returns {Number} User coordinate of point in x direction.
         * @private
         */
        XEval: function () {
            return this.coords.usrCoords[1];
        },

        /**
         * New evaluation of the function term.
         * This is required for CAS-points: Their YTerm() method is overwritten
         * in {@link JXG.CoordsElement#addConstraint}.
         *
         * @returns {Number} User coordinate of point in y direction.
         * @private
         */
        YEval: function () {
            return this.coords.usrCoords[2];
        },

        /**
         * New evaluation of the function term.
         * This is required for CAS-points: Their ZTerm() method is overwritten in
         * {@link JXG.CoordsElement#addConstraint}.
         *
         * @returns {Number} User coordinate of point in z direction.
         * @private
         */
        ZEval: function () {
            return this.coords.usrCoords[0];
        },

        /**
         * Getter method for the distance to a second point, this is required for CAS-elements.
         * Here, function inlining seems to be worthwile  (for plotting).
         * @param {JXG.Point} point2 The point to which the distance shall be calculated.
         * @returns {Number} Distance in user coordinate to the given point
         */
        Dist: function (point2) {
            if (this.isReal && point2.isReal) {
                return this.coords.distance(Const.COORDS_BY_USER, point2.coords);
            }
            return NaN;
        },

        /**
         * Alias for {@link JXG.Element#handleSnapToGrid}
         * @param {Boolean} force force snapping independent from what the snaptogrid attribute says
         * @returns {JXG.CoordsElement} Reference to this element
         */
        snapToGrid: function (force) {
            return this.handleSnapToGrid(force);
        },

        /**
         * Let a point snap to the nearest point in distance of
         * {@link JXG.Point#attractorDistance}.
         * The function uses the coords object of the point as
         * its actual position.
         * @param {Boolean} force force snapping independent from what the snaptogrid attribute says
         * @returns {JXG.Point} Reference to this element
         */
        handleSnapToPoints: function (force) {
            var i, pEl, pCoords,
                d = 0,
                len,
                dMax = Infinity,
                c = null,
                ev_au, ev_ad,
                ev_is2p = Type.evaluate(this.visProp.ignoredsnaptopoints),
                len2, j, ignore = false;

            len = this.board.objectsList.length;

            if (ev_is2p) {
                len2 = ev_is2p.length;
            }

            if (Type.evaluate(this.visProp.snaptopoints) || force) {
                ev_au = Type.evaluate(this.visProp.attractorunit);
                ev_ad = Type.evaluate(this.visProp.attractordistance);

                for (i = 0; i < len; i++) {
                    pEl = this.board.objectsList[i];

                    if (ev_is2p) {
                        ignore = false;
                        for (j = 0; j < len2; j++) {
                            if (pEl === this.board.select(ev_is2p[j])) {
                                ignore = true;
                                break;
                            }
                        }
                        if (ignore) {
                            continue;
                        }
                    }

                    if (Type.isPoint(pEl) && pEl !== this && pEl.visPropCalc.visible) {
                        pCoords = Geometry.projectPointToPoint(this, pEl, this.board);
                        if (ev_au === 'screen') {
                            d = pCoords.distance(Const.COORDS_BY_SCREEN, this.coords);
                        } else {
                            d = pCoords.distance(Const.COORDS_BY_USER, this.coords);
                        }

                        if (d < ev_ad && d < dMax) {
                            dMax = d;
                            c = pCoords;
                        }
                    }
                }

                if (c !== null) {
                    this.coords.setCoordinates(Const.COORDS_BY_USER, c.usrCoords);
                }
            }

            return this;
        },

        /**
         * Alias for {@link JXG.CoordsElement#handleSnapToPoints}.
         *
         * @param {Boolean} force force snapping independent from what the snaptogrid attribute says
         * @returns {JXG.Point} Reference to this element
         */
        snapToPoints: function (force) {
            return this.handleSnapToPoints(force);
        },

        /**
         * A point can change its type from free point to glider
         * and vice versa. If it is given an array of attractor elements
         * (attribute attractors) and the attribute attractorDistance
         * then the point will be made a glider if it less than attractorDistance
         * apart from one of its attractor elements.
         * If attractorDistance is equal to zero, the point stays in its
         * current form.
         * @returns {JXG.Point} Reference to this element
         */
        handleAttractors: function () {
            var i, el, projCoords,
                d = 0.0,
                projection,
                ev_au = Type.evaluate(this.visProp.attractorunit),
                ev_ad = Type.evaluate(this.visProp.attractordistance),
                ev_sd = Type.evaluate(this.visProp.snatchdistance),
                ev_a = Type.evaluate(this.visProp.attractors),
                len = ev_a.length;

            if (ev_ad === 0.0) {
                return;
            }

            for (i = 0; i < len; i++) {
                el = this.board.select(ev_a[i]);

                if (Type.exists(el) && el !== this) {
                    if (Type.isPoint(el)) {
                        projCoords = Geometry.projectPointToPoint(this, el, this.board);
                    } else if (el.elementClass === Const.OBJECT_CLASS_LINE) {
                        projection = Geometry.projectCoordsToSegment(
                                    this.coords.usrCoords,
                                    el.point1.coords.usrCoords,
                                    el.point2.coords.usrCoords);
                        if (!Type.evaluate(el.visProp.straightfirst) && projection[1] < 0.0) {
                            projCoords = el.point1.coords;
                        } else if (!Type.evaluate(el.visProp.straightlast) && projection[1] > 1.0) {
                            projCoords = el.point2.coords;
                        } else {
                            projCoords = new Coords(Const.COORDS_BY_USER, projection[0], this.board);
                        }
                    } else if (el.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                        projCoords = Geometry.projectPointToCircle(this, el, this.board);
                    } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
                        projCoords = Geometry.projectPointToCurve(this, el, this.board)[0];
                    } else if (el.type === Const.OBJECT_TYPE_TURTLE) {
                        projCoords = Geometry.projectPointToTurtle(this, el, this.board)[0];
                    } else if (el.type === Const.OBJECT_TYPE_POLYGON) {
                        projCoords = new Coords(Const.COORDS_BY_USER,
                            Geometry.projectCoordsToPolygon(this.coords.usrCoords, el),
                            this.board);
                    }

                    if (ev_au === 'screen') {
                        d = projCoords.distance(Const.COORDS_BY_SCREEN, this.coords);
                    } else {
                        d = projCoords.distance(Const.COORDS_BY_USER, this.coords);
                    }

                    if (d < ev_ad) {
                        if (!(this.type === Const.OBJECT_TYPE_GLIDER &&
                              (el === this.slideObject || this.slideObject && this.onPolygon && this.slideObject.parentPolygon === el)
                             )
                           ) {
                            this.makeGlider(el);
                        }
                        break;       // bind the point to the first attractor in its list.
                    }
                    if (d >= ev_sd &&
                        (el === this.slideObject || this.slideObject && this.onPolygon && this.slideObject.parentPolygon === el)
                       ) {
                        this.popSlideObject();
                    }
                }
            }

            return this;
        },

        /**
         * Sets coordinates and calls the point's update() method.
         * @param {Number} method The type of coordinates used here.
         * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
         * @param {Array} coords coordinates <tt>([z], x, y)</tt> in screen/user units
         * @returns {JXG.Point} this element
         */
        setPositionDirectly: function (method, coords) {
            var i, c, dc,
                oldCoords = this.coords,
                newCoords;

            if (this.relativeCoords) {
                c = new Coords(method, coords, this.board);
                if (Type.evaluate(this.visProp.islabel)) {
                    dc = Statistics.subtract(c.scrCoords, oldCoords.scrCoords);
                    this.relativeCoords.scrCoords[1] += dc[1];
                    this.relativeCoords.scrCoords[2] += dc[2];
                } else {
                    dc = Statistics.subtract(c.usrCoords, oldCoords.usrCoords);
                    this.relativeCoords.usrCoords[1] += dc[1];
                    this.relativeCoords.usrCoords[2] += dc[2];
                }

                return this;
            }

            this.coords.setCoordinates(method, coords);
            this.handleSnapToGrid();
            this.handleSnapToPoints();
            this.handleAttractors();

            // Update the initial coordinates. This is needed for free points
            // that have a transformation bound to it.
            for (i = this.transformations.length - 1; i >= 0; i--) {
                if (method === Const.COORDS_BY_SCREEN) {
                    newCoords = (new Coords(method, coords, this.board)).usrCoords;
                } else {
                    if (coords.length === 2) {
                        coords = [1].concat(coords);
                    }
                    newCoords = coords;
                }
                this.initialCoords.setCoordinates(Const.COORDS_BY_USER, Mat.matVecMult(Mat.inverse(this.transformations[i].matrix), newCoords));
            }
            this.prepareUpdate().update();

            // If the user suspends the board updates we need to recalculate the relative position of
            // the point on the slide object. This is done in updateGlider() which is NOT called during the
            // update process triggered by unsuspendUpdate.
            if (this.board.isSuspendedUpdate && this.type === Const.OBJECT_TYPE_GLIDER) {
                this.updateGlider();
            }

            return this;
        },

        /**
         * Translates the point by <tt>tv = (x, y)</tt>.
         * @param {Number} method The type of coordinates used here.
         * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
         * @param {Array} tv (x, y)
         * @returns {JXG.Point}
         */
        setPositionByTransform: function (method, tv) {
            var t;

            tv = new Coords(method, tv, this.board);
            t = this.board.create('transform', tv.usrCoords.slice(1), {type: 'translate'});

            if (this.transformations.length > 0 &&
                    this.transformations[this.transformations.length - 1].isNumericMatrix) {
                this.transformations[this.transformations.length - 1].melt(t);
            } else {
                this.addTransform(this, t);
            }

            this.prepareUpdate().update();

            return this;
        },

        /**
         * Sets coordinates and calls the point's update() method.
         * @param {Number} method The type of coordinates used here.
         * Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
         * @param {Array} coords coordinates in screen/user units
         * @returns {JXG.Point}
         */
        setPosition: function (method, coords) {
            return this.setPositionDirectly(method, coords);
        },

        /**
         * Sets the position of a glider relative to the defining elements
         * of the {@link JXG.Point#slideObject}.
         * @param {Number} x
         * @returns {JXG.Point} Reference to the point element.
         */
        setGliderPosition: function (x) {
            if (this.type === Const.OBJECT_TYPE_GLIDER) {
                this.position = x;
                this.board.update();
            }

            return this;
        },

        /**
         * Convert the point to glider and update the construction.
         * To move the point visual onto the glider, a call of board update is necessary.
         * @param {String|Object} slide The object the point will be bound to.
         */
        makeGlider: function (slide) {
            var slideobj = this.board.select(slide),
                onPolygon = false,
                min,
                i,
                dist;

            if (slideobj.type === Const.OBJECT_TYPE_POLYGON){
                // Search for the closest edge of the polygon.
                min = Number.MAX_VALUE;
                for (i = 0; i < slideobj.borders.length; i++){
                    dist = JXG.Math.Geometry.distPointLine(this.coords.usrCoords, slideobj.borders[i].stdform);
                    if (dist < min){
                        min = dist;
                        slide = slideobj.borders[i];
                    }
                }
                slideobj = this.board.select(slide);
                onPolygon = true;
            }

            /* Gliders on Ticks are forbidden */
            if (!Type.exists(slideobj)) {
                throw new Error("JSXGraph: slide object undefined.");
            } else if (slideobj.type === Const.OBJECT_TYPE_TICKS) {
                throw new Error("JSXGraph: gliders on ticks are not possible.");
            }

            this.slideObject = this.board.select(slide);
            this.slideObjects.push(this.slideObject);
            this.addParents(slide);

            this.type = Const.OBJECT_TYPE_GLIDER;
            this.elType = 'glider';
            this.visProp.snapwidth = -1;          // By default, deactivate snapWidth
            this.slideObject.addChild(this);
            this.isDraggable = true;
            this.onPolygon = onPolygon;

            this.generatePolynomial = function () {
                return this.slideObject.generatePolynomial(this);
            };

            // Determine the initial value of this.position
            this.updateGlider();
            this.needsUpdateFromParent = true;
            this.updateGliderFromParent();

            return this;
        },

        /**
         * Remove the last slideObject. If there are more than one elements the point is bound to,
         * the second last element is the new active slideObject.
         */
        popSlideObject: function () {
            if (this.slideObjects.length > 0) {
                this.slideObjects.pop();

                // It may not be sufficient to remove the point from
                // the list of childElement. For complex dependencies
                // one may have to go to the list of ancestor and descendants.  A.W.
                // Yes indeed, see #51 on github bugtracker
                //  delete this.slideObject.childElements[this.id];
                this.slideObject.removeChild(this);

                if (this.slideObjects.length === 0) {
                    this.type = this._org_type;
                    if (this.type === Const.OBJECT_TYPE_POINT) {
                        this.elType = 'point';
                    } else if (this.elementClass === Const.OBJECT_CLASS_TEXT) {
                        this.elType = 'text';
                    } else if (this.type === Const.OBJECT_TYPE_IMAGE) {
                        this.elType = 'image';
                    } else if (this.type === Const.OBJECT_TYPE_FOREIGNOBJECT) {
                        this.elType = 'foreignobject';
                    }

                    this.slideObject = null;
                } else {
                    this.slideObject = this.slideObjects[this.slideObjects.length - 1];
                }
            }
        },

        /**
         * Converts a calculated element into a free element,
         * i.e. it will delete all ancestors and transformations and,
         * if the element is currently a glider, will remove the slideObject reference.
         */
        free: function () {
            var ancestorId, ancestor;
                // child;

            if (this.type !== Const.OBJECT_TYPE_GLIDER) {
                // remove all transformations
                this.transformations.length = 0;

                delete this.updateConstraint;
                this.isConstrained = false;
                // this.updateConstraint = function () {
                //     return this;
                // };

                if (!this.isDraggable) {
                    this.isDraggable = true;

                    if (this.elementClass === Const.OBJECT_CLASS_POINT) {
                        this.type = Const.OBJECT_TYPE_POINT;
                        this.elType = 'point';
                    }

                    this.XEval = function () {
                        return this.coords.usrCoords[1];
                    };

                    this.YEval = function () {
                        return this.coords.usrCoords[2];
                    };

                    this.ZEval = function () {
                        return this.coords.usrCoords[0];
                    };

                    this.Xjc = null;
                    this.Yjc = null;
                } else {
                    return;
                }
            }

            // a free point does not depend on anything. And instead of running through tons of descendants and ancestor
            // structures, where we eventually are going to visit a lot of objects twice or thrice with hard to read and
            // comprehend code, just run once through all objects and delete all references to this point and its label.
            for (ancestorId in this.board.objects) {
                if (this.board.objects.hasOwnProperty(ancestorId)) {
                    ancestor = this.board.objects[ancestorId];

                    if (ancestor.descendants) {
                        delete ancestor.descendants[this.id];
                        delete ancestor.childElements[this.id];

                        if (this.hasLabel) {
                            delete ancestor.descendants[this.label.id];
                            delete ancestor.childElements[this.label.id];
                        }
                    }
                }
            }

            // A free point does not depend on anything. Remove all ancestors.
            this.ancestors = {}; // only remove the reference

            // Completely remove all slideObjects of the element
            this.slideObject = null;
            this.slideObjects = [];
            if (this.elementClass === Const.OBJECT_CLASS_POINT) {
                this.type = Const.OBJECT_TYPE_POINT;
                this.elType = 'point';
            } else if (this.elementClass === Const.OBJECT_CLASS_TEXT) {
                this.type = this._org_type;
                this.elType = 'text';
            } else if (this.elementClass === Const.OBJECT_CLASS_OTHER) {
                this.type = this._org_type;
                this.elType = 'image';
            }
        },

        /**
         * Convert the point to CAS point and call update().
         * @param {Array} terms [[zterm], xterm, yterm] defining terms for the z, x and y coordinate.
         * The z-coordinate is optional and it is used for homogeneous coordinates.
         * The coordinates may be either <ul>
         *   <li>a JavaScript function,</li>
         *   <li>a string containing GEONExT syntax. This string will be converted into a JavaScript
         *     function here,</li>
         *   <li>a Number</li>
         *   <li>a pointer to a slider object. This will be converted into a call of the Value()-method
         *     of this slider.</li>
         *   </ul>
         * @see JXG.GeonextParser#geonext2JS
         */
        addConstraint: function (terms) {
            var i, v,
                newfuncs = [],
                what = ['X', 'Y'],

                makeConstFunction = function (z) {
                    return function () {
                        return z;
                    };
                },

                makeSliderFunction = function (a) {
                    return function () {
                        return a.Value();
                    };
                };

            if (this.elementClass === Const.OBJECT_CLASS_POINT) {
                this.type = Const.OBJECT_TYPE_CAS;
            }

            this.isDraggable = false;

            for (i = 0; i < terms.length; i++) {
                v = terms[i];

                if (Type.isString(v)) {
                    // Convert GEONExT syntax into JavaScript syntax
                    //t  = JXG.GeonextParser.geonext2JS(v, this.board);
                    //newfuncs[i] = new Function('','return ' + t + ';');
                    //v = GeonextParser.replaceNameById(v, this.board);
                    newfuncs[i] = this.board.jc.snippet(v, true, null, true);

                    if (terms.length === 2) {
                        this[what[i] + 'jc'] = terms[i];
                    }
                } else if (Type.isFunction(v)) {
                    newfuncs[i] = v;
                } else if (Type.isNumber(v)) {
                    newfuncs[i] = makeConstFunction(v);
                // Slider
            } else if (Type.isObject(v) && Type.isFunction(v.Value)) {
                    newfuncs[i] = makeSliderFunction(v);
                }

                newfuncs[i].origin = v;
            }

            // Intersection function
            if (terms.length === 1) {
                this.updateConstraint = function () {
                    var c = newfuncs[0]();

                    // Array
                    if (Type.isArray(c)) {
                        this.coords.setCoordinates(Const.COORDS_BY_USER, c);
                    // Coords object
                    } else {
                        this.coords = c;
                    }
                    return this;
                };
            // Euclidean coordinates
            } else if (terms.length === 2) {
                this.XEval = newfuncs[0];
                this.YEval = newfuncs[1];

                this.setParents([newfuncs[0].origin, newfuncs[1].origin]);

                this.updateConstraint = function () {
                    this.coords.setCoordinates(Const.COORDS_BY_USER, [this.XEval(), this.YEval()]);
                    return this;
                };
            // Homogeneous coordinates
            } else {
                this.ZEval = newfuncs[0];
                this.XEval = newfuncs[1];
                this.YEval = newfuncs[2];

                this.setParents([newfuncs[0].origin, newfuncs[1].origin, newfuncs[2].origin]);

                this.updateConstraint = function () {
                    this.coords.setCoordinates(Const.COORDS_BY_USER, [this.ZEval(), this.XEval(), this.YEval()]);
                    return this;
                };
            }
            this.isConstrained = true;

            /**
            * We have to do an update. Otherwise, elements relying on this point will receive NaN.
            */
            this.prepareUpdate().update();
            if (!this.board.isSuspendedUpdate) {
                this.updateVisibility().updateRenderer();
                if (this.hasLabel) {
                    this.label.fullUpdate();
                }
            }

            return this;
        },

        /**
         * In case there is an attribute "anchor", the element is bound to
         * this anchor element.
         * This is handled with this.relativeCoords. If the element is a label
         * relativeCoords are given in scrCoords, otherwise in usrCoords.
         * @param{Array} coordinates Offset from th anchor element. These are the values for this.relativeCoords.
         * In case of a label, coordinates are screen coordinates. Otherwise, coordinates are user coordinates.
         * @param{Boolean} isLabel Yes/no
         * @private
         */
        addAnchor: function (coordinates, isLabel) {
            if (isLabel) {
                this.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, coordinates.slice(0, 2), this.board);
            } else {
                this.relativeCoords = new Coords(Const.COORDS_BY_USER, coordinates, this.board);
            }
            this.element.addChild(this);
            if (isLabel) {
                this.addParents(this.element);
            }

            this.XEval = function () {
                var sx, coords, anchor, ev_o;

                if (Type.evaluate(this.visProp.islabel)) {
                    ev_o = Type.evaluate(this.visProp.offset);
                    sx =  parseFloat(ev_o[0]);
                    anchor = this.element.getLabelAnchor();
                    coords = new Coords(Const.COORDS_BY_SCREEN,
                        [sx + this.relativeCoords.scrCoords[1] + anchor.scrCoords[1], 0], this.board);

                    return coords.usrCoords[1];
                }

                anchor = this.element.getTextAnchor();
                return this.relativeCoords.usrCoords[1] + anchor.usrCoords[1];
            };

            this.YEval = function () {
                var sy, coords, anchor, ev_o;

                if (Type.evaluate(this.visProp.islabel)) {
                    ev_o = Type.evaluate(this.visProp.offset);
                    sy = -parseFloat(ev_o[1]);
                    anchor = this.element.getLabelAnchor();
                    coords = new Coords(Const.COORDS_BY_SCREEN,
                        [0, sy + this.relativeCoords.scrCoords[2] + anchor.scrCoords[2]], this.board);

                    return coords.usrCoords[2];
                }

                anchor = this.element.getTextAnchor();
                return this.relativeCoords.usrCoords[2] + anchor.usrCoords[2];
            };

            this.ZEval = Type.createFunction(1, this.board, '');

            this.updateConstraint = function () {
                this.coords.setCoordinates(Const.COORDS_BY_USER, [this.ZEval(), this.XEval(), this.YEval()]);
            };
            this.isConstrained = true;

            this.updateConstraint();
            //this.coords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
        },

        /**
         * Applies the transformations of the element.
         * This method applies to text and images. Point transformations are handled differently.
         * @param {Boolean} fromParent True if the drag comes from a child element. Unused.
         * @returns {JXG.CoordsElement} Reference to itself.
         */
        updateTransform: function (fromParent) {
            var i;

            if (this.transformations.length === 0) {
                return this;
            }

            for (i = 0; i < this.transformations.length; i++) {
                this.transformations[i].update();
            }

            return this;
        },

        /**
         * Add transformations to this element.
         * @param {JXG.GeometryElement} el
         * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation}
         * or an array of {@link JXG.Transformation}s.
         * @returns {JXG.CoordsElement} Reference to itself.
         */
        addTransform: function (el, transform) {
            var i,
                list = Type.isArray(transform) ? transform : [transform],
                len = list.length;

            // There is only one baseElement possible
            if (this.transformations.length === 0) {
                this.baseElement = el;
            }

            for (i = 0; i < len; i++) {
                this.transformations.push(list[i]);
            }

            return this;
        },

        /**
         * Animate the point.
         * @param {Number} direction The direction the glider is animated. Can be +1 or -1.
         * @param {Number} stepCount The number of steps in which the parent element is divided.
         * Must be at least 1.
         * @param {Number} delay Time in msec between two animation steps. Default is 250.
         * @returns {JXG.CoordsElement} Reference to iself.
         *
         * @name Glider#startAnimation
         * @see Glider#stopAnimation
         * @function
         * @example
         * // Divide the circle line into 6 steps and
         * // visit every step 330 msec counterclockwise.
         * var ci = board.create('circle', [[-1,2], [2,1]]);
         * var gl = board.create('glider', [0,2, ci]);
         * gl.startAnimation(-1, 6, 330);
         *
         * </pre><div id="JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad3" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG0f35a50e-e99d-11e8-a1ca-04d3b0c2aad3',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     // Divide the circle line into 6 steps and
         *     // visit every step 330 msec counterclockwise.
         *     var ci = board.create('circle', [[-1,2], [2,1]]);
         *     var gl = board.create('glider', [0,2, ci]);
         *     gl.startAnimation(-1, 6, 330);
         *
         *     })();
         *
         * </script><pre>
         *
         * @example
         * // Divide the slider area into 20 steps and
         * // visit every step 30 msec.
         * var n = board.create('slider',[[-2,4],[2,4],[1,5,100]],{name:'n'});
         * n.startAnimation(1, 20, 30);
         *
         * </pre><div id="JXG40ce04b8-e99c-11e8-a1ca-04d3b0c2aad3" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG40ce04b8-e99c-11e8-a1ca-04d3b0c2aad3',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     // Divide the slider area into 20 steps and
         *     // visit every step 30 msec.
         *     var n = board.create('slider',[[-2,4],[2,4],[1,5,100]],{name:'n'});
         *     n.startAnimation(1, 20, 30);
         *
         *     })();
         * </script><pre>
         *
         */
        startAnimation: function (direction, stepCount, delay) {
            var that = this;

            delay = delay || 250;

            if ((this.type === Const.OBJECT_TYPE_GLIDER) && !Type.exists(this.intervalCode)) {
                this.intervalCode = window.setInterval(function () {
                    that._anim(direction, stepCount);
                }, delay);

                if (!Type.exists(this.intervalCount)) {
                    this.intervalCount = 0;
                }
            }
            return this;
        },

        /**
         * Stop animation.
         * @name Glider#stopAnimation
         * @see Glider#startAnimation
         * @function
         * @returns {JXG.CoordsElement} Reference to itself.
         */
        stopAnimation: function () {
            if (Type.exists(this.intervalCode)) {
                window.clearInterval(this.intervalCode);
                delete this.intervalCode;
            }

            return this;
        },

        /**
         * Starts an animation which moves the point along a given path in given time.
         * @param {Array|function} path The path the point is moved on.
         * This can be either an array of arrays or containing x and y values of the points of
         * the path, or an array of points, or a function taking the amount of elapsed time since the animation
         * has started and returns an array containing a x and a y value or NaN.
         * In case of NaN the animation stops.
         * @param {Number} time The time in milliseconds in which to finish the animation
         * @param {Object} [options] Optional settings for the animation.
         * @param {function} [options.callback] A function that is called as soon as the animation is finished.
         * @param {Boolean} [options.interpolate=true] If <tt>path</tt> is an array moveAlong()
         * will interpolate the path
         * using {@link JXG.Math.Numerics.Neville}. Set this flag to false if you don't want to use interpolation.
         * @returns {JXG.CoordsElement} Reference to itself.
         * @see JXG.CoordsElement#moveAlong
         * @see JXG.CoordsElement#moveTo
         * @see JXG.GeometryElement#animate
         */
        moveAlong: function (path, time, options) {
            options = options || {};

            var i, neville,
                interpath = [],
                p = [],
                delay = this.board.attr.animationdelay,
                steps = time / delay,
                len, pos, part,

                makeFakeFunction = function (i, j) {
                    return function () {
                        return path[i][j];
                    };
                };

            if (Type.isArray(path)) {
                len = path.length;
                for (i = 0; i < len; i++) {
                    if (Type.isPoint(path[i])) {
                        p[i] = path[i];
                    } else {
                        p[i] = {
                            elementClass: Const.OBJECT_CLASS_POINT,
                            X: makeFakeFunction(i, 0),
                            Y: makeFakeFunction(i, 1)
                        };
                    }
                }

                time = time || 0;
                if (time === 0) {
                    this.setPosition(Const.COORDS_BY_USER, [p[p.length - 1].X(), p[p.length - 1].Y()]);
                    return this.board.update(this);
                }

                if (!Type.exists(options.interpolate) || options.interpolate) {
                    neville = Numerics.Neville(p);
                    for (i = 0; i < steps; i++) {
                        interpath[i] = [];
                        interpath[i][0] = neville[0]((steps - i) / steps * neville[3]());
                        interpath[i][1] = neville[1]((steps - i) / steps * neville[3]());
                    }
                } else {
                    len = path.length - 1;
                    for (i = 0; i < steps; ++i) {
                        pos = Math.floor(i / steps * len);
                        part = i / steps * len - pos;

                        interpath[i] = [];
                        interpath[i][0] = (1.0 - part) * p[pos].X() + part * p[pos + 1].X();
                        interpath[i][1] = (1.0 - part) * p[pos].Y() + part * p[pos + 1].Y();
                    }
                    interpath.push([p[len].X(), p[len].Y()]);
                    interpath.reverse();
                    /*
                    for (i = 0; i < steps; i++) {
                        interpath[i] = [];
                        interpath[i][0] = path[Math.floor((steps - i) / steps * (path.length - 1))][0];
                        interpath[i][1] = path[Math.floor((steps - i) / steps * (path.length - 1))][1];
                    }
                    */
                }

                this.animationPath = interpath;
            } else if (Type.isFunction(path)) {
                this.animationPath = path;
                this.animationStart = new Date().getTime();
            }

            this.animationCallback = options.callback;
            this.board.addAnimation(this);

            return this;
        },

        /**
         * Starts an animated point movement towards the given coordinates <tt>where</tt>.
         * The animation is done after <tt>time</tt> milliseconds.
         * If the second parameter is not given or is equal to 0, setPosition() is called, see #setPosition,
         * i.e. the coordinates are changed without animation.
         * @param {Array} where Array containing the x and y coordinate of the target location.
         * @param {Number} [time] Number of milliseconds the animation should last.
         * @param {Object} [options] Optional settings for the animation
         * @param {function} [options.callback] A function that is called as soon as the animation is finished.
         * @param {String} [options.effect='<>'] animation effects like speed fade in and out. possible values are
         * '<>' for speed increase on start and slow down at the end (default) and '--' for constant speed during
         * the whole animation.
         * @returns {JXG.CoordsElement} Reference to itself.
         * @see JXG.CoordsElement#moveAlong
         * @see JXG.CoordsElement#visit
         * @see JXG.GeometryElement#animate
         */
        moveTo: function (where, time, options) {
            options = options || {};
            where = new Coords(Const.COORDS_BY_USER, where, this.board);

            var i,
                delay = this.board.attr.animationdelay,
                steps = Math.ceil(time / delay),
                coords = [],
                X = this.coords.usrCoords[1],
                Y = this.coords.usrCoords[2],
                dX = (where.usrCoords[1] - X),
                dY = (where.usrCoords[2] - Y),

                /** @ignore */
                stepFun = function (i) {
                    if (options.effect && options.effect === '<>') {
                        return Math.pow(Math.sin((i / steps) * Math.PI / 2), 2);
                    }
                    return i / steps;
                };

            if (!Type.exists(time) || time === 0 ||
                (Math.abs(where.usrCoords[0] - this.coords.usrCoords[0]) > Mat.eps)) {
                this.setPosition(Const.COORDS_BY_USER, where.usrCoords);
                return this.board.update(this);
            }

            // In case there is no callback and we are already at the endpoint we can stop here
            if (!Type.exists(options.callback) && Math.abs(dX) < Mat.eps && Math.abs(dY) < Mat.eps) {
                return this;
            }

            for (i = steps; i >= 0; i--) {
                coords[steps - i] = [where.usrCoords[0], X + dX * stepFun(i), Y + dY * stepFun(i)];
            }

            this.animationPath = coords;
            this.animationCallback = options.callback;
            this.board.addAnimation(this);

            return this;
        },

        /**
         * Starts an animated point movement towards the given coordinates <tt>where</tt>. After arriving at
         * <tt>where</tt> the point moves back to where it started. The animation is done after <tt>time</tt>
         * milliseconds.
         * @param {Array} where Array containing the x and y coordinate of the target location.
         * @param {Number} time Number of milliseconds the animation should last.
         * @param {Object} [options] Optional settings for the animation
         * @param {function} [options.callback] A function that is called as soon as the animation is finished.
         * @param {String} [options.effect='<>'] animation effects like speed fade in and out. possible values are
         * '<>' for speed increase on start and slow down at the end (default) and '--' for constant speed during
         * the whole animation.
         * @param {Number} [options.repeat=1] How often this animation should be repeated.
         * @returns {JXG.CoordsElement} Reference to itself.
         * @see JXG.CoordsElement#moveAlong
         * @see JXG.CoordsElement#moveTo
         * @see JXG.GeometryElement#animate
         */
        visit: function (where, time, options) {
            where = new Coords(Const.COORDS_BY_USER, where, this.board);

            var i, j, steps,
                delay = this.board.attr.animationdelay,
                coords = [],
                X = this.coords.usrCoords[1],
                Y = this.coords.usrCoords[2],
                dX = (where.usrCoords[1] - X),
                dY = (where.usrCoords[2] - Y),

                /** @ignore */
                stepFun = function (i) {
                    var x = (i < steps / 2 ? 2 * i / steps : 2 * (steps - i) / steps);

                    if (options.effect && options.effect === '<>') {
                        return Math.pow(Math.sin(x * Math.PI / 2), 2);
                    }

                    return x;
                };

            // support legacy interface where the third parameter was the number of repeats
            if (Type.isNumber(options)) {
                options = {repeat: options};
            } else {
                options = options || {};
                if (!Type.exists(options.repeat)) {
                    options.repeat = 1;
                }
            }

            steps = Math.ceil(time / (delay * options.repeat));

            for (j = 0; j < options.repeat; j++) {
                for (i = steps; i >= 0; i--) {
                    coords[j * (steps + 1) + steps - i] = [where.usrCoords[0], X + dX * stepFun(i), Y + dY * stepFun(i)];
                }
            }
            this.animationPath = coords;
            this.animationCallback = options.callback;
            this.board.addAnimation(this);

            return this;
        },

        /**
         * Animates a glider. Is called by the browser after startAnimation is called.
         * @param {Number} direction The direction the glider is animated.
         * @param {Number} stepCount The number of steps in which the parent element is divided.
         * Must be at least 1.
         * @see #startAnimation
         * @see #stopAnimation
         * @private
         * @returns {JXG.CoordsElement} Reference to itself.
         */
        _anim: function (direction, stepCount) {
            var dX, dY, alpha, startPoint, newX, radius,
                sp1c, sp2c,
                res,
                d;

            this.intervalCount += 1;
            if (this.intervalCount > stepCount) {
                this.intervalCount = 0;
            }

            if (this.slideObject.elementClass === Const.OBJECT_CLASS_LINE) {
                sp1c = this.slideObject.point1.coords.scrCoords;
                sp2c = this.slideObject.point2.coords.scrCoords;

                dX = Math.round((sp2c[1] - sp1c[1]) * this.intervalCount / stepCount);
                dY = Math.round((sp2c[2] - sp1c[2]) * this.intervalCount / stepCount);
                if (direction > 0) {
                    startPoint = this.slideObject.point1;
                } else {
                    startPoint = this.slideObject.point2;
                    dX *= -1;
                    dY *= -1;
                }

                this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [
                    startPoint.coords.scrCoords[1] + dX,
                    startPoint.coords.scrCoords[2] + dY
                ]);
            } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CURVE) {
                if (direction > 0) {
                    newX = Math.round(this.intervalCount / stepCount * this.board.canvasWidth);
                } else {
                    newX = Math.round((stepCount - this.intervalCount) / stepCount * this.board.canvasWidth);
                }

                this.coords.setCoordinates(Const.COORDS_BY_SCREEN, [newX, 0]);
                res = Geometry.projectPointToCurve(this, this.slideObject, this.board);
                this.coords = res[0];
                this.position = res[1];
            } else if (this.slideObject.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                alpha = 2 * Math.PI;
                if (direction < 0) {
                    alpha *= this.intervalCount / stepCount;
                } else {
                    alpha *= (stepCount - this.intervalCount);
                }
                radius = this.slideObject.Radius();

                this.coords.setCoordinates(Const.COORDS_BY_USER, [
                    this.slideObject.center.coords.usrCoords[1] + radius * Math.cos(alpha),
                    this.slideObject.center.coords.usrCoords[2] + radius * Math.sin(alpha)
                ]);
            }

            this.board.update(this);
            return this;
        },

        // documented in GeometryElement
        getTextAnchor: function () {
            return this.coords;
        },

        // documented in GeometryElement
        getLabelAnchor: function () {
            return this.coords;
        },

        // documented in element.js
        getParents: function () {
            var p = [this.Z(), this.X(), this.Y()];

            if (this.parents.length !== 0) {
                p = this.parents;
            }

            if (this.type === Const.OBJECT_TYPE_GLIDER) {
                p = [this.X(), this.Y(), this.slideObject.id];
            }

            return p;
        }

    });

    /**
     * Generic method to create point, text or image.
     * Determines the type of the construction, i.e. free, or constrained by function,
     * transformation or of glider type.
     * @param{Object} Callback Object type, e.g. JXG.Point, JXG.Text or JXG.Image
     * @param{Object} board Link to the board object
     * @param{Array} coords Array with coordinates. This may be: array of numbers, function
     * returning an array of numbers, array of functions returning a number, object and transformation.
     * If the attribute "slideObject" exists, a glider element is constructed.
     * @param{Object} attr Attributes object
     * @param{Object} arg1 Optional argument 1: in case of text this is the text content,
     * in case of an image this is the url.
     * @param{Array} arg2 Optional argument 2: in case of image this is an array containing the size of
     * the image.
     * @returns{Object} returns the created object or false.
     */
    JXG.CoordsElement.create = function (Callback, board, coords, attr, arg1, arg2) {
        var el, isConstrained = false, i;

        for (i = 0; i < coords.length; i++) {
            if (Type.isFunction(coords[i]) || Type.isString(coords[i])) {
                isConstrained = true;
            }
        }

        if (!isConstrained) {
            if (Type.isNumber(coords[0]) && Type.isNumber(coords[1])) {
                el = new Callback(board, coords, attr, arg1, arg2);

                if (Type.exists(attr.slideobject)) {
                    el.makeGlider(attr.slideobject);
                } else {
                    // Free element
                    el.baseElement = el;
                }
                el.isDraggable = true;
            } else if (Type.isObject(coords[0]) && Type.isTransformationOrArray(coords[1])) {
                // Transformation
                // TODO less general specification of isObject
                el = new Callback(board, [0, 0], attr, arg1, arg2);
                el.addTransform(coords[0], coords[1]);
                el.isDraggable = false;
            } else {
                return false;
            }
        } else {
            el = new Callback(board, [0, 0], attr, arg1, arg2);
            el.addConstraint(coords);
        }

        el.handleSnapToGrid();
        el.handleSnapToPoints();
        el.handleAttractors();

        el.addParents(coords);
        return el;
    };

    return JXG.CoordsElement;

});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */

/*global JXG: true, define: true, window: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 base/coords
 base/element
 parser/geonext
 math/statistics
 utils/env
 utils/type
 */

/**
 * @fileoverview In this file the Text element is defined.
 */

define('base/text',[
    'jxg', 'base/constants', 'base/element', 'parser/geonext',
    'utils/env', 'utils/type', 'math/math', 'base/coordselement'
], function (JXG, Const, GeometryElement, GeonextParser, Env, Type, Mat, CoordsElement) {

    "use strict";

    var priv = {
        HTMLSliderInputEventHandler: function () {
            this._val = parseFloat(this.rendNodeRange.value);
            this.rendNodeOut.value = this.rendNodeRange.value;
            this.board.update();
        }
    };

    /**
     * Construct and handle texts.
     *
     * The coordinates can be relative to the coordinates of an element
     * given in {@link JXG.Options#text.anchor}.
     *
     * MathJax, HTML and GEONExT syntax can be handled.
     * @class Creates a new text object. Do not use this constructor to create a text. Use {@link JXG.Board#create} with
     * type {@link Text} instead.
     * @augments JXG.GeometryElement
     * @augments JXG.CoordsElement
     * @param {string|JXG.Board} board The board the new text is drawn on.
     * @param {Array} coordinates An array with the user coordinates of the text.
     * @param {Object} attributes An object containing visual properties and optional a name and a id.
     * @param {string|function} content A string or a function returning a string.
     *
     */
    JXG.Text = function (board, coords, attributes, content) {
        this.constructor(board, attributes, Const.OBJECT_TYPE_TEXT, Const.OBJECT_CLASS_TEXT);

        this.element = this.board.select(attributes.anchor);
        this.coordsConstructor(coords, Type.evaluate(this.visProp.islabel));

        this.content = '';
        this.plaintext = '';
        this.plaintextOld = null;
        this.orgText = '';

        this.needsSizeUpdate = false;
        // Only used by infobox anymore
        this.hiddenByParent = false;

        /**
         * Width and height of the the text element in pixel.
         *
         * @private
         * @type Array
         */
        this.size = [1.0, 1.0];
        this.id = this.board.setId(this, 'T');

        this.board.renderer.drawText(this);
        this.board.finalizeAdding(this);

        // Set text before drawing
        // this._createFctUpdateText(content);
        // this.updateText();

        this.setText(content);

        if (Type.isString(this.content)) {
            this.notifyParents(this.content);
        }
        this.elType = 'text';

        this.methodMap = Type.deepCopy(this.methodMap, {
            setText: 'setTextJessieCode',
            // free: 'free',
            move: 'setCoords'
        });
    };

    JXG.Text.prototype = new GeometryElement();
    Type.copyPrototypeMethods(JXG.Text, CoordsElement, 'coordsConstructor');

    JXG.extend(JXG.Text.prototype, /** @lends JXG.Text.prototype */ {
        /**
         * @private
         * Test if the the screen coordinates (x,y) are in a small stripe
         * at the left side or at the right side of the text.
         * Sensitivity is set in this.board.options.precision.hasPoint.
         * If dragarea is set to 'all' (default), tests if the the screen
         * coordinates (x,y) are in within the text boundary.
         * @param {Number} x
         * @param {Number} y
         * @returns {Boolean}
         */
        hasPoint: function (x, y) {
            var lft, rt, top, bot, ax, ay, type, r;

            if (Type.isObject(Type.evaluate(this.visProp.precision))) {
                type = this.board._inputDevice;
                r = Type.evaluate(this.visProp.precision[type]);
            } else {
                // 'inherit'
                r = this.board.options.precision.hasPoint;
            }
            if (this.transformations.length > 0) {
                //Transform the mouse/touch coordinates
                // back to the original position of the text.
                lft = Mat.matVecMult(Mat.inverse(this.board.renderer.joinTransforms(this, this.transformations)), [1, x, y]);
                x = lft[1];
                y = lft[2];
            }

            ax = this.getAnchorX();
            if (ax === 'right') {
                lft = this.coords.scrCoords[1] - this.size[0];
            } else if (ax === 'middle') {
                lft = this.coords.scrCoords[1] - 0.5 * this.size[0];
            } else {
                lft = this.coords.scrCoords[1];
            }
            rt = lft + this.size[0];

            ay = this.getAnchorY();
            if (ay === 'top') {
                bot = this.coords.scrCoords[2] + this.size[1];
            } else if (ay === 'middle') {
                bot = this.coords.scrCoords[2] + 0.5 * this.size[1];
            } else {
                bot = this.coords.scrCoords[2];
            }
            top = bot - this.size[1];

            if (Type.evaluate(this.visProp.dragarea) === 'all') {
                return x >= lft - r && x < rt + r && y >= top - r && y <= bot + r;
            }
            // e.g. 'small'
            return (y >= top - r && y <= bot + r) &&
                ((x >= lft - r && x <= lft + 2 * r) ||
                    (x >= rt - 2 * r && x <= rt + r));
        },

        /**
         * This sets the updateText function of this element depending on the type of text content passed.
         * Used by {@link JXG.Text#_setText} and {@link JXG.Text} constructor.
         * @param {String|Function|Number} text
         * @private
         */
        _createFctUpdateText: function (text) {
            var updateText, resolvedText,
                ev_p = Type.evaluate(this.visProp.parse),
                ev_um = Type.evaluate(this.visProp.usemathjax),
                ev_uk = Type.evaluate(this.visProp.usekatex);

            this.orgText = text;
            if (Type.isFunction(text)) {
                this.updateText = function () {
                    resolvedText = text().toString();
                    if (ev_p && !ev_um && !ev_uk) {
                        this.plaintext = this.replaceSub(this.replaceSup(this.convertGeonextAndSketchometry2CSS(resolvedText)));
                    } else {
                        this.plaintext = resolvedText;
                    }
                };
            } else if (Type.isString(text) && !ev_p) {   // Do not parse
                this.updateText = function () {
                    this.plaintext = text;
                };
            } else {                                     // Parse
                if (Type.isNumber(text)) {
                    this.content = Type.toFixed(text, Type.evaluate(this.visProp.digits));
                } else {
                    if (Type.evaluate(this.visProp.useasciimathml)) {
                        // Convert via ASCIIMathML
                        this.content = "'`" + text + "`'";
                    } else if (ev_um || ev_uk) {
                        this.content = "'" + text + "'";
                    } else {
                        // Converts GEONExT syntax into JavaScript string
                        // Short math is allowed
                        // Avoid geonext2JS calls
                        this.content = this.generateTerm(text, true, true);
                    }
                }
                updateText = this.board.jc.snippet(this.content, true, '', false);
                this.updateText = function () {
                    this.plaintext = updateText();
                };
            }
        },

        /**
         * Defines new content. This is used by {@link JXG.Text#setTextJessieCode} and {@link JXG.Text#setText}. This is required because
         * JessieCode needs to filter all Texts inserted into the DOM and thus has to replace setText by setTextJessieCode.
         * @param {String|Function|Number} text
         * @returns {JXG.Text}
         * @private
         */
        _setText: function (text) {
            this._createFctUpdateText(text);

            // First evaluation of the string.
            // We need this for display='internal' and Canvas
            this.updateText();
            this.fullUpdate();

            // We do not call updateSize for the infobox to speed up rendering
            if (!this.board.infobox || this.id !== this.board.infobox.id) {
                this.updateSize();    // updateSize() is called at least once.
            }

            // This may slow down canvas renderer
            // if (this.board.renderer.type === 'canvas') {
            //     this.board.fullUpdate();
            // }

            return this;
        },

        /**
         * Defines new content but converts &lt; and &gt; to HTML entities before updating the DOM.
         * @param {String|function} text
         */
        setTextJessieCode: function (text) {
            var s;

            this.visProp.castext = text;
            if (Type.isFunction(text)) {
                s = function () {
                    return Type.sanitizeHTML(text());
                };
            } else {
                if (Type.isNumber(text)) {
                    s = text;
                } else {
                    s = Type.sanitizeHTML(text);
                }
            }

            return this._setText(s);
        },

        /**
         * Defines new content.
         * @param {String|function} text
         * @returns {JXG.Text} Reference to the text object.
         */
        setText: function (text) {
            return this._setText(text);
        },

        /**
         * Recompute the width and the height of the text box.
         * Updates the array {@link JXG.Text#size} with pixel values.
         * The result may differ from browser to browser
         * by some pixels.
         * In canvas an old IEs we use a very crude estimation of the dimensions of
         * the textbox.
         * JSXGraph needs {@link JXG.Text#size} for applying rotations in IE and
         * for aligning text.
         *
         * @return {[type]} [description]
         */
        updateSize: function () {
            var tmp, that, node,
                ev_d = Type.evaluate(this.visProp.display);

            if (!Env.isBrowser || this.board.renderer.type === 'no') {
                return this;
            }
            node = this.rendNode;

            /**
             * offsetWidth and offsetHeight seem to be supported for internal vml elements by IE10+ in IE8 mode.
             */
            if (ev_d === 'html' || this.board.renderer.type === 'vml') {
                if (Type.exists(node.offsetWidth)) {
                    that = this;
                    window.setTimeout(function () {
                        that.size = [node.offsetWidth, node.offsetHeight];
                        that.needsUpdate = true;
                        that.updateRenderer();
                    }, 0);
                    // In case, there is non-zero padding or borders
                    // the following approach does not longer work.
                    // s = [node.offsetWidth, node.offsetHeight];
                    // if (s[0] === 0 && s[1] === 0) { // Some browsers need some time to set offsetWidth and offsetHeight
                    //     that = this;
                    //     window.setTimeout(function () {
                    //         that.size = [node.offsetWidth, node.offsetHeight];
                    //         that.needsUpdate = true;
                    //         that.updateRenderer();
                    //     }, 0);
                    // } else {
                    //     this.size = s;
                    // }
                } else {
                    this.size = this.crudeSizeEstimate();
                }
            } else if (ev_d === 'internal') {
                if (this.board.renderer.type === 'svg') {
                    that = this;
                    window.setTimeout(function () {
                        try {
                            tmp = node.getBBox();
                            that.size = [tmp.width, tmp.height];
                            that.needsUpdate = true;
                            that.updateRenderer();
                        } catch (e) {
                        }
                    }, 0);
                } else if (this.board.renderer.type === 'canvas') {
                    this.size = this.crudeSizeEstimate();
                }
            }

            return this;
        },

        /**
         * A very crude estimation of the dimensions of the textbox in case nothing else is available.
         * @returns {Array}
         */
        crudeSizeEstimate: function () {
            var ev_fs = parseFloat(Type.evaluate(this.visProp.fontsize));
            return [ev_fs * this.plaintext.length * 0.45, ev_fs * 0.9];
        },

        /**
         * Decode unicode entities into characters.
         * @param {String} string
         * @returns {String}
         */
        utf8_decode: function (string) {
            return string.replace(/&#x(\w+);/g, function (m, p1) {
                return String.fromCharCode(parseInt(p1, 16));
            });
        },

        /**
         * Replace _{} by &lt;sub&gt;
         * @param {String} te String containing _{}.
         * @returns {String} Given string with _{} replaced by &lt;sub&gt;.
         */
        replaceSub: function (te) {
            if (!te.indexOf) {
                return te;
            }

            var j,
                i = te.indexOf('_{');

            // the regexp in here are not used for filtering but to provide some kind of sugar for label creation,
            // i.e. replacing _{...} with <sub>...</sub>. What is passed would get out anyway.
            /*jslint regexp: true*/

            while (i >= 0) {
                te = te.substr(0, i) + te.substr(i).replace(/_\{/, '<sub>');
                j = te.substr(i).indexOf('}');
                if (j >= 0) {
                    te = te.substr(0, j) + te.substr(j).replace(/\}/, '</sub>');
                }
                i = te.indexOf('_{');
            }

            i = te.indexOf('_');
            while (i >= 0) {
                te = te.substr(0, i) + te.substr(i).replace(/_(.?)/, '<sub>$1</sub>');
                i = te.indexOf('_');
            }

            return te;
        },

        /**
         * Replace ^{} by &lt;sup&gt;
         * @param {String} te String containing ^{}.
         * @returns {String} Given string with ^{} replaced by &lt;sup&gt;.
         */
        replaceSup: function (te) {
            if (!te.indexOf) {
                return te;
            }

            var j,
                i = te.indexOf('^{');

            // the regexp in here are not used for filtering but to provide some kind of sugar for label creation,
            // i.e. replacing ^{...} with <sup>...</sup>. What is passed would get out anyway.
            /*jslint regexp: true*/

            while (i >= 0) {
                te = te.substr(0, i) + te.substr(i).replace(/\^\{/, '<sup>');
                j = te.substr(i).indexOf('}');
                if (j >= 0) {
                    te = te.substr(0, j) + te.substr(j).replace(/\}/, '</sup>');
                }
                i = te.indexOf('^{');
            }

            i = te.indexOf('^');
            while (i >= 0) {
                te = te.substr(0, i) + te.substr(i).replace(/\^(.?)/, '<sup>$1</sup>');
                i = te.indexOf('^');
            }

            return te;
        },

        /**
         * Return the width of the text element.
         * @returns {Array} [width, height] in pixel
         */
        getSize: function () {
            return this.size;
        },

        /**
         * Move the text to new coordinates.
         * @param {number} x
         * @param {number} y
         * @returns {object} reference to the text object.
         */
        setCoords: function (x, y) {
            var coordsAnchor, dx, dy;
            if (Type.isArray(x) && x.length > 1) {
                y = x[1];
                x = x[0];
            }

            if (Type.evaluate(this.visProp.islabel) && Type.exists(this.element)) {
                coordsAnchor = this.element.getLabelAnchor();
                dx = (x - coordsAnchor.usrCoords[1]) * this.board.unitX;
                dy = -(y - coordsAnchor.usrCoords[2]) * this.board.unitY;

                this.relativeCoords.setCoordinates(Const.COORDS_BY_SCREEN, [dx, dy]);
            } else {
                /*
                this.X = function () {
                    return x;
                };

                this.Y = function () {
                    return y;
                };
                */
                this.coords.setCoordinates(Const.COORDS_BY_USER, [x, y]);
            }

            // this should be a local update, otherwise there might be problems
            // with the tick update routine resulting in orphaned tick labels
            this.fullUpdate();

            return this;
        },

        /**
         * Evaluates the text.
         * Then, the update function of the renderer
         * is called.
         */
        update: function (fromParent) {
            if (!this.needsUpdate) {
                return this;
            }

            this.updateCoords(fromParent);
            this.updateText();

            if (Type.evaluate(this.visProp.display) === 'internal') {
                if (Type.isString(this.plaintext)) {
                    this.plaintext = this.utf8_decode(this.plaintext);
                }
            }

            this.checkForSizeUpdate();
            if (this.needsSizeUpdate) {
                this.updateSize();
            }

            return this;
        },

        /**
         * Used to save updateSize() calls.
         * Called in JXG.Text.update
         * That means this.update() has been called.
         * More tests are in JXG.Renderer.updateTextStyle. The latter tests
         * are one update off. But this should pose not too many problems, since
         * it affects fontSize and cssClass changes.
         *
         * @private
         */
        checkForSizeUpdate: function () {
            if (this.board.infobox && this.id === this.board.infobox.id) {
                this.needsSizeUpdate = false;
            } else {
                // For some magic reason it is more efficient on the iPad to
                // call updateSize() for EVERY text element EVERY time.
                this.needsSizeUpdate = (this.plaintextOld !== this.plaintext);

                if (this.needsSizeUpdate) {
                    this.plaintextOld = this.plaintext;
                }
            }

        },

        /**
         * The update function of the renderert
         * is called.
         * @private
         */
        updateRenderer: function () {
            if (//this.board.updateQuality === this.board.BOARD_QUALITY_HIGH &&
                Type.evaluate(this.visProp.autoposition)) {

                this.setAutoPosition()
                    .updateConstraint();
            }
            return this.updateRendererGeneric('updateText');
        },

        /**
         * Converts shortened math syntax into correct syntax:  3x instead of 3*x or
         * (a+b)(3+1) instead of (a+b)*(3+1).
         *
         * @private
         * @param{String} expr Math term
         * @returns {string} expanded String
         */
        expandShortMath: function (expr) {
            var re = /([)0-9.])\s*([(a-zA-Z_])/g;
            return expr.replace(re, '$1*$2');
        },

        /**
         * Converts the GEONExT syntax of the <value> terms into JavaScript.
         * Also, all Objects whose name appears in the term are searched and
         * the text is added as child to these objects.
         *
         * @param{String} contentStr String to be parsed
         * @param{Boolean} [expand] Optional flag if shortened math syntax is allowed (e.g. 3x instead of 3*x).
         * @param{Boolean} [avoidGeonext2JS] Optional flag if geonext2JS should be called. For backwards compatibility
         * this has to be set explicitely to true.
         * @private
         * @see JXG.GeonextParser.geonext2JS
         */
        generateTerm: function (contentStr, expand, avoidGeonext2JS) {
            var res, term, i, j,
                plaintext = '""';

            // revert possible jc replacement
            contentStr = contentStr || '';
            contentStr = contentStr.replace(/\r/g, '');
            contentStr = contentStr.replace(/\n/g, '');
            contentStr = contentStr.replace(/"/g, '\'');
            contentStr = contentStr.replace(/'/g, "\\'");

            contentStr = contentStr.replace(/&amp;arc;/g, '&ang;');
            contentStr = contentStr.replace(/<arc\s*\/>/g, '&ang;');
            contentStr = contentStr.replace(/&lt;arc\s*\/&gt;/g, '&ang;');
            contentStr = contentStr.replace(/&lt;sqrt\s*\/&gt;/g, '&radic;');

            contentStr = contentStr.replace(/&lt;value&gt;/g, '<value>');
            contentStr = contentStr.replace(/&lt;\/value&gt;/g, '</value>');

            // Convert GEONExT syntax into  JavaScript syntax
            i = contentStr.indexOf('<value>');
            j = contentStr.indexOf('</value>');
            if (i >= 0) {
                while (i >= 0) {
                    plaintext += ' + "' + this.replaceSub(this.replaceSup(contentStr.slice(0, i))) + '"';
                    term = contentStr.slice(i + 7, j);
                    term = term.replace(/\s+/g, ''); // Remove all whitespace
                    if (expand === true) {
                        term = this.expandShortMath(term);
                    }
                    if (avoidGeonext2JS) {
                        res = term;
                    } else {
                        res = GeonextParser.geonext2JS(term, this.board);
                    }
                    res = res.replace(/\\"/g, "'");
                    res = res.replace(/\\'/g, "'");

                    // GEONExT-Hack: apply rounding once only.
                    if (res.indexOf('toFixed') < 0) {
                        // output of a value tag
                        if (Type.isNumber((Type.bind(this.board.jc.snippet(res, true, '', false), this))())) {
                            // may also be a string
                            plaintext += '+(' + res + ').toFixed(' + (Type.evaluate(this.visProp.digits)) + ')';
                        } else {
                            plaintext += '+(' + res + ')';
                        }
                    } else {
                        plaintext += '+(' + res + ')';
                    }

                    contentStr = contentStr.slice(j + 8);
                    i = contentStr.indexOf('<value>');
                    j = contentStr.indexOf('</value>');
                }
            }

            plaintext += ' + "' + this.replaceSub(this.replaceSup(contentStr)) + '"';
            plaintext = this.convertGeonextAndSketchometry2CSS(plaintext);

            // This should replace &amp;pi; by &pi;
            plaintext = plaintext.replace(/&amp;/g, '&');
            plaintext = plaintext.replace(/"/g, "'");

            return plaintext;
        },

        /**
         * Converts the GEONExT tags <overline> and <arrow> to
         * HTML span tags with proper CSS formatting.
         * @private
         * @see JXG.Text.generateTerm
         * @see JXG.Text._setText
         */
        convertGeonext2CSS: function (s) {
            if (Type.isString(s)) {
                s = s.replace(
                    /(<|&lt;)overline(>|&gt;)/g,
                    '<span style=text-decoration:overline;>'
                );
                s = s.replace(
                    /(<|&lt;)\/overline(>|&gt;)/g,
                    '</span>'
                );
                s = s.replace(
                    /(<|&lt;)arrow(>|&gt;)/g,
                    '<span style=text-decoration:overline;>'
                );
                s = s.replace(
                    /(<|&lt;)\/arrow(>|&gt;)/g,
                    '</span>'
                );
            }

            return s;
        },

        /**
         * Converts the sketchometry tag <sketchofont> to
         * HTML span tags with proper CSS formatting.
         * @private
         * @see JXG.Text.generateTerm
         * @see JXG.Text._setText
         */
        convertSketchometry2CSS: function (s) {
            if (Type.isString(s)) {
                s = s.replace(
                    /(<|&lt;)sketchofont(>|&gt;)/g,
                    '<span style=font-family:sketchometry-light;font-weight:500;>'
                );
                s = s.replace(
                    /(<|&lt;)\/sketchofont(>|&gt;)/g,
                    '</span>'
                );
                s = s.replace(
                    /(<|&lt;)sketchometry-light(>|&gt;)/g,
                    '<span style=font-family:sketchometry-light;font-weight:500;>'
                );
                s = s.replace(
                    /(<|&lt;)\/sketchometry-light(>|&gt;)/g,
                    '</span>'
                );
            }

            return s;
        },

        /**
         * Alias for convertGeonext2CSS and convertSketchometry2CSS
         * @private
         * @see JXG.Text.convertGeonext2CSS
         * @see JXG.Text.convertSketchometry2CSS
         */
        convertGeonextAndSketchometry2CSS: function (s){
            s = this.convertGeonext2CSS(s);
            s = this.convertSketchometry2CSS(s);
            return s;
        },

        /**
         * Finds dependencies in a given term and notifies the parents by adding the
         * dependent object to the found objects child elements.
         * @param {String} content String containing dependencies for the given object.
         * @private
         */
        notifyParents: function (content) {
            var search,
                res = null;

            // revert possible jc replacement
            content = content.replace(/&lt;value&gt;/g, '<value>');
            content = content.replace(/&lt;\/value&gt;/g, '</value>');

            do {
                search = /<value>([\w\s*/^\-+()[\],<>=!]+)<\/value>/;
                res = search.exec(content);

                if (res !== null) {
                    GeonextParser.findDependencies(this, res[1], this.board);
                    content = content.substr(res.index);
                    content = content.replace(search, '');
                }
            } while (res !== null);

            return this;
        },

        // documented in element.js
        getParents: function () {
            var p;
            if (this.relativeCoords !== undefined) { // Texts with anchor elements, excluding labels
                p = [this.relativeCoords.usrCoords[1], this.relativeCoords.usrCoords[2], this.orgText];
            } else {                                 // Other texts
                p = [this.Z(), this.X(), this.Y(), this.orgText];
            }

            if (this.parents.length !== 0) {
                p = this.parents;
            }

            return p;
        },

        bounds: function () {
            var c = this.coords.usrCoords;

            if (Type.evaluate(this.visProp.islabel) || this.board.unitY === 0 || this.board.unitX === 0) {
                return [0, 0, 0, 0];
            }
            return [c[1], c[2] + this.size[1] / this.board.unitY, c[1] + this.size[0] / this.board.unitX, c[2]];
        },

        getAnchorX: function () {
            var a = Type.evaluate(this.visProp.anchorx);
            if (a === 'auto') {
                switch (this.visProp.position) {
                    case 'top':
                    case 'bot':
                        return 'middle';
                    case 'rt':
                    case 'lrt':
                    case 'urt':
                        return 'left';
                    case 'lft':
                    case 'llft':
                    case 'ulft':
                    default:
                        return 'right';
                }
            }
            return a;
        },

        getAnchorY: function () {
            var a = Type.evaluate(this.visProp.anchory);
            if (a === 'auto') {
                switch (this.visProp.position) {
                    case 'top':
                    case 'ulft':
                    case 'urt':
                        return 'bottom';
                    case 'bot':
                    case 'lrt':
                    case 'llft':
                        return 'top';
                    case 'rt':
                    case 'lft':
                    default:
                        return 'middle';
                }
            }
            return a;
        },

        /**
         * Computes the number of overlaps of a box of w pixels width, h pixels height
         * and center (x, y)
         *
         * @private
         * @param  {Number} x x-coordinate of the center (screen coordinates)
         * @param  {Number} y y-coordinate of the center (screen coordinates)
         * @param  {Number} w width of the box in pixel
         * @param  {Number} h width of the box in pixel
         * @return {Number}   Number of overlapping elements
         */
        getNumberofConflicts: function (x, y, w, h) {
            var count = 0,
                i, obj, le,
                savePointPrecision;

            // Set the precision of hasPoint to half the max if label isn't too long
            savePointPrecision = this.board.options.precision.hasPoint;
            // this.board.options.precision.hasPoint = Math.max(w, h) * 0.5;
            this.board.options.precision.hasPoint = (w + h) * 0.25;
            // TODO:
            // Make it compatible with the objects' visProp.precision attribute
            for (i = 0, le = this.board.objectsList.length; i < le; i++) {
                obj = this.board.objectsList[i];
                if (obj.visPropCalc.visible &&
                    obj.elType !== 'axis' &&
                    obj.elType !== 'ticks' &&
                    obj !== this.board.infobox &&
                    obj !== this &&
                    obj.hasPoint(x, y)) {

                    count++;
                }
            }
            this.board.options.precision.hasPoint = savePointPrecision;

            return count;
        },

        /**
         * Sets the offset of a label element to the position with the least number
         * of overlaps with other elements, while retaining the distance to its
         * anchor element. Twelve different angles are possible.
         *
         * @returns {JXG.Text} Reference to the text object.
         */
        setAutoPosition: function () {
            var x, y, cx, cy,
                anchorCoords,
                // anchorX, anchorY,
                w = this.size[0],
                h = this.size[1],
                start_angle, angle,
                optimum = {
                    conflicts: Infinity,
                    angle: 0,
                    r: 0
                },
                max_r, delta_r,
                conflicts, offset, r,
                num_positions = 12,
                step = 2 * Math.PI / num_positions,
                j, dx, dy, co, si;

            if (this === this.board.infobox ||
                !this.visPropCalc.visible ||
                !Type.evaluate(this.visProp.islabel) ||
                !this.element) {
                return this;
            }

            // anchorX = Type.evaluate(this.visProp.anchorx);
            // anchorY = Type.evaluate(this.visProp.anchory);
            offset = Type.evaluate(this.visProp.offset);
            anchorCoords = this.element.getLabelAnchor();
            cx = anchorCoords.scrCoords[1];
            cy = anchorCoords.scrCoords[2];

            // Set dx, dy as the relative position of the center of the label
            // to its anchor element ignoring anchorx and anchory.
            dx = offset[0];
            dy = offset[1];

            conflicts = this.getNumberofConflicts(cx + dx, cy - dy, w, h);
            if (conflicts === 0) {
                return this;
            }
            // console.log(this.id, conflicts, w, h);
            // r = Geometry.distance([0, 0], offset, 2);

            r = 12;
            max_r = 28;
            delta_r = 0.2 * r;

            start_angle = Math.atan2(dy, dx);

            optimum.conflicts = conflicts;
            optimum.angle = start_angle;
            optimum.r = r;

            while (optimum.conflicts > 0 && r < max_r) {
                for (j = 1, angle = start_angle + step; j < num_positions && optimum.conflicts > 0; j++) {
                    co = Math.cos(angle);
                    si = Math.sin(angle);

                    x = cx + r * co;
                    y = cy - r * si;

                    conflicts = this.getNumberofConflicts(x, y, w, h);
                    if (conflicts < optimum.conflicts) {
                        optimum.conflicts = conflicts;
                        optimum.angle = angle;
                        optimum.r = r;
                    }
                    if (optimum.conflicts === 0) {
                        break;
                    }
                    angle += step;
                }
                r += delta_r;
            }
            // console.log(this.id, "after", optimum)
            r = optimum.r;
            co = Math.cos(optimum.angle);
            si = Math.sin(optimum.angle);
            this.visProp.offset = [r * co, r * si];

            if (co < -0.2) {
                this.visProp.anchorx = 'right';
            } else if (co > 0.2) {
                this.visProp.anchorx = 'left';
            } else {
                this.visProp.anchorx = 'middle';
            }

            return this;
        }
    });

    /**
     * @class Construct and handle texts.
     *
     * The coordinates can be relative to the coordinates of an element
     * given in {@link JXG.Options#text.anchor}.
     *
     * MathJaX, HTML and GEONExT syntax can be handled.
     * @pseudo
     * @description
     * @name Text
     * @augments JXG.Text
     * @constructor
     * @type JXG.Text
     *
     * @param {number,function_number,function_number,function_String,function} z_,x,y,str Parent elements for text elements.
     *                     <p>
     *   Parent elements can be two or three elements of type number, a string containing a GEONE<sub>x</sub>T
     *   constraint, or a function which takes no parameter and returns a number. Every parent element determines one coordinate. If a coordinate is
     *   given by a number, the number determines the initial position of a free text. If given by a string or a function that coordinate will be constrained
     *   that means the user won't be able to change the texts's position directly by mouse because it will be calculated automatically depending on the string
     *   or the function's return value. If two parent elements are given the coordinates will be interpreted as 2D affine Euclidean coordinates, if three such
     *   parent elements are given they will be interpreted as homogeneous coordinates.
     *                     <p>
     *                     The text to display may be given as string or as function returning a string.
     *
     * There is the attribute 'display' which takes the values 'html' or 'internal'. In case of 'html' a HTML division tag is created to display
     * the text. In this case it is also possible to use ASCIIMathML. Incase of 'internal', a SVG or VML text element is used to display the text.
     * @see JXG.Text
     * @example
     * // Create a fixed text at position [0,1].
     *   var t1 = board.create('text',[0,1,"Hello World"]);
     * </pre><div class="jxgbox" id="JXG896013aa-f24e-4e83-ad50-7bc7df23f6b7" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var t1_board = JXG.JSXGraph.initBoard('JXG896013aa-f24e-4e83-ad50-7bc7df23f6b7', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     *   var t1 = t1_board.create('text',[0,1,"Hello World"]);
     * </script><pre>
     * @example
     * // Create a variable text at a variable position.
     *   var s = board.create('slider',[[0,4],[3,4],[-2,0,2]]);
     *   var graph = board.create('text',
     *                        [function(x){ return s.Value();}, 1,
     *                         function(){return "The value of s is"+JXG.toFixed(s.Value(), 2);}
     *                        ]
     *                     );
     * </pre><div class="jxgbox" id="JXG5441da79-a48d-48e8-9e53-75594c384a1c" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var t2_board = JXG.JSXGraph.initBoard('JXG5441da79-a48d-48e8-9e53-75594c384a1c', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     *   var s = t2_board.create('slider',[[0,4],[3,4],[-2,0,2]]);
     *   var t2 = t2_board.create('text',[function(x){ return s.Value();}, 1, function(){return "The value of s is "+JXG.toFixed(s.Value(), 2);}]);
     * </script><pre>
     * @example
     * // Create a text bound to the point A
     * var p = board.create('point',[0, 1]),
     *     t = board.create('text',[0, -1,"Hello World"], {anchor: p});
     *
     * </pre><div class="jxgbox" id="JXGff5a64b2-2b9a-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGff5a64b2-2b9a-11e5-8dd9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var p = board.create('point',[0, 1]),
     *         t = board.create('text',[0, -1,"Hello World"], {anchor: p});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createText = function (board, parents, attributes) {
        var t,
            attr = Type.copyAttributes(attributes, board.options, 'text'),
            coords = parents.slice(0, -1),
            content = parents[parents.length - 1];

        // downwards compatibility
        attr.anchor = attr.parent || attr.anchor;
        t = CoordsElement.create(JXG.Text, board, coords, attr, content);

        if (!t) {
            throw new Error("JSXGraph: Can't create text with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [x,y], [z,x,y], [element,transformation]");
        }

        if (attr.rotate !== 0 && attr.display === 'internal') { // This is the default value, i.e. no rotation
            t.addRotation(attr.rotate);
        }

        return t;
    };

    JXG.registerElement('text', JXG.createText);

    /**
     * @class Labels are text objects tied to other elements like points, lines and curves.
     * Labels are handled internally by JSXGraph, only. There is NO constructor "board.create('label', ...)".
     *
     * @pseudo
     * @description
     * @name Label
     * @augments JXG.Text
     * @constructor
     * @type JXG.Text
     */
    //  See element.js#createLabel

    /**
     * [[x,y], [w px, h px], [range]
     */
    JXG.createHTMLSlider = function (board, parents, attributes) {
        var t, par,
            attr = Type.copyAttributes(attributes, board.options, 'htmlslider');

        if (parents.length !== 2 || parents[0].length !== 2 || parents[1].length !== 3) {
            throw new Error("JSXGraph: Can't create htmlslider with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parents are: [[x,y], [min, start, max]]");
        }

        // backwards compatibility
        attr.anchor = attr.parent || attr.anchor;
        attr.fixed = attr.fixed || true;

        par = [parents[0][0], parents[0][1],
            '<form style="display:inline">' +
            '<input type="range" /><span></span><input type="text" />' +
            '</form>'];

        t = JXG.createText(board, par, attr);
        t.type = Type.OBJECT_TYPE_HTMLSLIDER;

        t.rendNodeForm = t.rendNode.childNodes[0];

        t.rendNodeRange = t.rendNodeForm.childNodes[0];
        t.rendNodeRange.min = parents[1][0];
        t.rendNodeRange.max = parents[1][2];
        t.rendNodeRange.step = attr.step;
        t.rendNodeRange.value = parents[1][1];

        t.rendNodeLabel = t.rendNodeForm.childNodes[1];
        t.rendNodeLabel.id = t.rendNode.id + '_label';

        if (attr.withlabel) {
            t.rendNodeLabel.innerHTML = t.name + '=';
        }

        t.rendNodeOut = t.rendNodeForm.childNodes[2];
        t.rendNodeOut.value = parents[1][1];

        try {
            t.rendNodeForm.id = t.rendNode.id + '_form';
            t.rendNodeRange.id = t.rendNode.id + '_range';
            t.rendNodeOut.id = t.rendNode.id + '_out';
        } catch (e) {
            JXG.debug(e);
        }

        t.rendNodeRange.style.width = attr.widthrange + 'px';
        t.rendNodeRange.style.verticalAlign = 'middle';
        t.rendNodeOut.style.width = attr.widthout + 'px';

        t._val = parents[1][1];

        if (JXG.supportsVML()) {
            /*
            * OnChange event is used for IE browsers
            * The range element is supported since IE10
            */
            Env.addEvent(t.rendNodeForm, 'change', priv.HTMLSliderInputEventHandler, t);
        } else {
            /*
            * OnInput event is used for non-IE browsers
            */
            Env.addEvent(t.rendNodeForm, 'input', priv.HTMLSliderInputEventHandler, t);
        }

        t.Value = function () {
            return this._val;
        };

        return t;
    };

    JXG.registerElement('htmlslider', JXG.createHTMLSlider);

    return {
        Text: JXG.Text,
        createText: JXG.createText,
        createHTMLSlider: JXG.createHTMLSlider
    };
});

/**
 * Generate a random uuid.
 * Written by http://www.broofa.com (robert@broofa.com)
 *
 * Copyright (c) 2010 Robert Kieffer
 * Dual licensed under the MIT and GPL licenses.
 * @returns {String}
 * @example
 *   var uuid = JXG.Util.genUUID();
 *   > uuid = '92329D39-6F5C-4520-ABFC-AAB64544E172'
 */

/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true, bitwise: true*/

/* depends:
 jxg
 */

define('utils/uuid',['jxg'], function (JXG) {

    'use strict';

    // constants
    var uuidCharsStr = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
        uuidChars = uuidCharsStr.split('');

    /**
     * General utility routines
     * @namespace
     */
    JXG.Util = JXG.Util || {};

    JXG.Util.genUUID = function (prefix) {
        var r, i,
            uuid = [],
            rnd = 0;

        prefix = prefix || '';

        if (prefix !== '' && prefix.substr(prefix.length - 1) !== '-') {
            prefix = prefix + '-';
        }

        for (i = 0; i < 36; i++) {
            if (i === 8 || i === 13 || i === 18 || i === 23) {
                uuid[i] = '-';
            } else if (i === 14) {
                uuid[i] = '4';
            } else {
                if (rnd <= 0x02) {
                    rnd = 0x2000000 + (Math.random() * 0x1000000) | 0;
                }

                r = rnd & 0xf;
                rnd = rnd >> 4;
                uuid[i] = uuidChars[(i === 19) ? (r & 0x3) | 0x8 : r];
            }
        }

        return prefix + uuid.join('');
    };

    return JXG.Util;
});
/*
 JessieCode Interpreter and Compiler

    Copyright 2011-2019
        Michael Gerhaeuser,
        Alfred Wassermann

    JessieCode is free software dual licensed under the GNU LGPL or MIT 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

    JessieCode 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 and
    the MIT License along with JessieCode. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */

/*global JXG: true, define: true, window: true, console: true, self: true, document: true, parser: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 parser/geonext
 base/constants
 base/text
 math/math
 math/geometry
 math/statistics
 utils/type
 utils/uuid
 */

/**
 * @fileoverview JessieCode is a scripting language designed to provide a
 * simple scripting language to build constructions
 * with JSXGraph. It is similar to JavaScript, but prevents access to the DOM.
 * Hence, it can be used in community driven math portals which want to use
 * JSXGraph to display interactive math graphics.
 */

define('parser/jessiecode',[
    'jxg', 'base/constants', 'base/text', 'math/math', 'math/ia', 'math/geometry', 'math/statistics', 'utils/type', 'utils/uuid', 'utils/env'
], function (JXG, Const, Text, Mat, Interval, Geometry, Statistics, Type, UUID, Env) {

    ;

    // IE 6-8 compatibility
    if (!Object.create) {
        Object.create = function(o, properties) {
            if (typeof o !== 'object' && typeof o !== 'function') throw new TypeError('Object prototype may only be an Object: ' + o);
            else if (o === null) throw new Error("This browser's implementation of Object.create is a shim and doesn't support 'null' as the first argument.");

            if (typeof properties != 'undefined') throw new Error("This browser's implementation of Object.create is a shim and doesn't support a second argument.");

            function F() {}

            F.prototype = o;

            return new F();
        };
    }

    var priv = {
            modules: {
                'math': Mat,
                'math/geometry': Geometry,
                'math/statistics': Statistics,
                'math/numerics': Mat.Numerics
            }
        };

    /**
     * A JessieCode object provides an interface to the parser and stores all variables and objects used within a JessieCode script.
     * The optional argument <tt>code</tt> is interpreted after initializing. To evaluate more code after initializing a JessieCode instance
     * please use {@link JXG.JessieCode#parse}. For code snippets like single expressions use {@link JXG.JessieCode#snippet}.
     * @constructor
     * @param {String} [code] Code to parse.
     * @param {Boolean} [geonext=false] Geonext compatibility mode.
     */
    JXG.JessieCode = function (code, geonext) {
        // Control structures

        /**
         * The global scope.
         * @type Object
         */
        this.scope = {
            id: 0,
            hasChild: true,
            args: [],
            locals: {},
            context: null,
            previous: null
        };

        /**
         * Keeps track of all possible scopes every required.
         * @type Array
         */
        this.scopes = [];
        this.scopes.push(this.scope);

        /**
         * A stack to store debug information (like line and column where it was defined) of a parameter
         * @type Array
         * @private
         */
        this.dpstack = [[]];

        /**
         * Determines the parameter stack scope.
         * @type Number
         * @private
         */
        this.pscope = 0;

        /**
         * Used to store the property-value definition while parsing an object literal.
         * @type Array
         * @private
         */
        this.propstack = [{}];

        /**
         * The current scope of the object literal stack {@link JXG.JessieCode#propstack}.
         * @type Number
         * @private
         */
        this.propscope = 0;

        /**
         * Store the left hand side of an assignment. If an element is constructed and no attributes are given, this is
         * used as the element's name.
         * @type Array
         * @private
         */
        this.lhs = [];

        /**
         * lhs flag, used by JXG.JessieCode#replaceNames
         * @type Boolean
         * @default false
         */
        this.isLHS = false;

        /**
         * The id of an HTML node in which innerHTML all warnings are stored (if no <tt>console</tt> object is available).
         * @type String
         * @default 'jcwarn'
         */
        this.warnLog = 'jcwarn';

        /**
         * Store $log messages in case there's no console.
         * @type Array
         */
        this.$log = [];

        /**
         * Built-in functions and constants
         * @type Object
         */
        this.builtIn = this.defineBuiltIn();

        /**
         * List of all possible operands in JessieCode (except of JSXGraph objects).
         * @type Object
         */
        this.operands = this.getPossibleOperands();

        /**
         * The board which currently is used to create and look up elements.
         * @type JXG.Board
         */
        this.board = null;

        /**
         * Keep track of which element is created in which line.
         * @type Object
         */
        this.lineToElement = {};

        this.parCurLine = 1;
        this.parCurColumn = 0;
        this.line = 1;
        this.col = 1;

        if (JXG.CA) {
            this.CA = new JXG.CA(this.node, this.createNode, this);
        }

        this.code = '';

        if (typeof code === 'string') {
            this.parse(code, geonext);
        }
    };

    JXG.extend(JXG.JessieCode.prototype, /** @lends JXG.JessieCode.prototype */ {
        /**
         * Create a new parse tree node.
         * @param {String} type Type of node, e.g. node_op, node_var, or node_const
         * @param value The nodes value, e.g. a variables value or a functions body.
         * @param {Array} children Arbitrary number of child nodes.
         */
        node: function (type, value, children) {
            return {
                type: type,
                value: value,
                children: children
            };
        },

        /**
         * Create a new parse tree node. Basically the same as node(), but this builds
         * the children part out of an arbitrary number of parameters, instead of one
         * array parameter.
         * @param {String} type Type of node, e.g. node_op, node_var, or node_const
         * @param value The nodes value, e.g. a variables value or a functions body.
         * @param children Arbitrary number of parameters; define the child nodes.
         */
        createNode: function (type, value, children) {
            var n = this.node(type, value, []),
                i;

            for (i = 2; i < arguments.length; i++) {
                n.children.push(arguments[i]);
            }

            if (n.type == 'node_const' && Type.isNumber(n.value)) {
                n.isMath = true;
            }

            n.line = this.parCurLine;
            n.col = this.parCurColumn;

            return n;
        },

        /**
         * Create a new scope.
         * @param {Array} args
         * @returns {Object}
         */
        pushScope: function (args) {
            var scope = {
                    args: args,
                    locals: {},
                    context: null,
                    previous: this.scope
                };

            this.scope.hasChild = true;
            this.scope = scope;
            scope.id = this.scopes.push(scope) - 1;

            return scope;
        },

        /**
         * Remove the current scope and reinstate the previous scope
         * @returns {Object}
         */
        popScope: function () {
            var s = this.scope.previous;

            // make sure the global scope is not lost
            this.scope = s !== null ? s : this.scope;

            return this.scope;
        },

        /**
         * Looks up an {@link JXG.GeometryElement} by its id.
         * @param {String} id
         * @returns {JXG.GeometryElement}
         */
        getElementById: function (id) {
            return this.board.objects[id];
        },

        log: function () {
            this.$log.push(arguments);

            if (typeof console === 'object' && console.log) {
                console.log.apply(console, arguments);
            }
        },

        /**
         * Returns a element creator function which takes two parameters: the parents array and the attributes object.
         * @param {String} vname The element type, e.g. 'point', 'line', 'midpoint'
         * @returns {function}
         */
        creator: (function () {
            // stores the already defined creators
            var _ccache = {}, r;

            r = function (vname) {
                var f;

                // _ccache is global, i.e. it is the same for ALL JessieCode instances.
                // That's why we need the board id here
                if (typeof _ccache[this.board.id + vname] === 'function') {
                    f =  _ccache[this.board.id + vname];
                } else {
                    f = (function (that) {
                        return function (parameters, attributes) {
                            var attr;

                            if (Type.exists(attributes)) {
                                attr = attributes;
                            } else {
                                attr = {};
                            }
                            if (attr.name === undefined && attr.id === undefined) {
                                attr.name = (that.lhs[that.scope.id] !== 0 ? that.lhs[that.scope.id] : '');
                            }
                            return that.board.create(vname, parameters, attr);
                        };
                    }(this));

                    f.creator = true;
                    _ccache[this.board.id + vname] = f;
                }

                return f;
            };

            r.clearCache = function () {
                _ccache = {};
            };

            return r;
        }()),

        /**
         * Assigns a value to a variable in the current scope.
         * @param {String} vname Variable name
         * @param value Anything
         * @see JXG.JessieCode#sstack
         * @see JXG.JessieCode#scope
         */
        letvar: function (vname, value) {
            if (this.builtIn[vname]) {
                this._warn('"' + vname + '" is a predefined value.');
            }

            this.scope.locals[vname] = value;
        },

        /**
         * Checks if the given variable name can be found in the current scope chain.
         * @param {String} vname
         * @returns {Object} A reference to the scope object the variable can be found in or null if it can't be found.
         */
        isLocalVariable: function (vname) {
            var s = this.scope;

            while (s !== null) {
                if (Type.exists(s.locals[vname])) {
                    return s;
                }

                s = s.previous;
            }

            return null;
        },

        /**
         * Checks if the given variable name is a parameter in any scope from the current to the global scope.
         * @param {String} vname
         * @returns {Object} A reference to the scope object that contains the variable in its arg list.
         */
        isParameter: function (vname) {
            var s = this.scope;

            while (s !== null) {
                if (Type.indexOf(s.args, vname) > -1) {
                    return s;
                }

                s = s.previous;
            }

            return null;
        },

        /**
         * Checks if the given variable name is a valid creator method.
         * @param {String} vname
         * @returns {Boolean}
         */
        isCreator: function (vname) {
            // check for an element with this name
            return !!JXG.elements[vname];
        },

        /**
         * Checks if the given variable identifier is a valid member of the JavaScript Math Object.
         * @param {String} vname
         * @returns {Boolean}
         */
        isMathMethod: function (vname) {
            return vname !== 'E' && !!Math[vname];
        },

        /**
         * Returns true if the given identifier is a builtIn variable/function.
         * @param {String} vname
         * @returns {Boolean}
         */
        isBuiltIn: function (vname) {
            return !!this.builtIn[vname];
        },

        /**
         * Looks up the value of the given variable. We use a simple type inspection.
         *
         * @param {String} vname Name of the variable
         * @param {Boolean} [local=false] Only look up the internal symbol table and don't look for
         * the <tt>vname</tt> in Math or the element list.
         * @param {Boolean} [isFunctionName=false] Lookup function of tpye builtIn, Math.*, creator.
         *
         * @see JXG.JessieCode#resolveType
         */
         getvar: function (vname, local, isFunctionName) {
            var s;

            local = Type.def(local, false);

            // Local scope has always precedence
            s = this.isLocalVariable(vname);
            if (s !== null) {
                return s.locals[vname];
            }

            // Handle the - so far only - few constants by hard coding them.
            if (vname === '$board' || vname === 'EULER' || vname === 'PI') {
                return this.builtIn[vname];
            }

            if (!!isFunctionName) {
                if (this.isBuiltIn(vname)) {
                    return this.builtIn[vname];
                }

                if (this.isMathMethod(vname)) {
                    return Math[vname];
                }

                // check for an element with this name
                if (this.isCreator(vname)) {
                    return this.creator(vname);
                }
            }

            if (!local) {
                s = this.board.select(vname);
                if (s !== vname) {
                    return s;
                }
            }
        },

        /**
         * Look up the value of a local variable.
         * @param {string} vname
         * @returns {*}
         */
        resolve: function (vname) {
            var s = this.scope;

            while (s !== null) {
                if (Type.exists(s.locals[vname])) {
                    return s.locals[vname];
                }

                s = s.previous;
            }
        },

        /**
         * TODO this needs to be called from JS and should not generate JS code
         * Looks up a variable identifier in various tables and generates JavaScript code that could be eval'd to get the value.
         * @param {String} vname Identifier
         * @param {Boolean} [local=false] Don't resolve ids and names of elements
         * @param {Boolean} [withProps=false]
         */
        getvarJS: function (vname, local, withProps) {
            var s, r = '', re;

            local = Type.def(local, false);
            withProps = Type.def(withProps, false);

            s = this.isParameter(vname);
            if (s !== null) {
                return vname;
            }

            s = this.isLocalVariable(vname);
            if (s !== null && !withProps) {
                return '$jc$.resolve(\'' + vname + '\')';
            }

            // check for an element with this name
            if (this.isCreator(vname)) {
                return '(function () { var a = Array.prototype.slice.call(arguments, 0), props = ' + (withProps ? 'a.pop()' : '{}') + '; return $jc$.board.create.apply($jc$.board, [\'' + vname + '\'].concat([a, props])); })';
            }

            if (withProps) {
                this._error('Syntax error (attribute values are allowed with element creators only)');
            }

            if (this.isBuiltIn(vname)) {
                // If src does not exist, it is a number. In that case, just return the value.
                r = this.builtIn[vname].src || this.builtIn[vname];

                // Get the "real" name of the function
                if (Type.isNumber(r)) {
                    return r;
                }
                // Search a JSXGraph object in board
                if (r.match(/board\.select/)) {
                    return r;
                }

                vname = r.split('.').pop();
                if (Type.exists(this.board.mathLib)) {
                    // Handle builtin case: ln(x) -> Math.log
                    re = new RegExp('^Math\.' + vname);
                    if (re.exec(r) !== null) {
                        return r.replace(re, '$jc$.board.mathLib.' + vname);
                    }
                }
                if (Type.exists(this.board.mathLibJXG)) {
                    // Handle builtin case: factorial(x) -> JXG.Math.factorial
                    re = new RegExp('^JXG\.Math\.');
                    if (re.exec(r) !== null) {
                        return r.replace(re, '$jc$.board.mathLibJXG.');
                    }
                    return r;
                }
                return r;

                // return this.builtIn[vname].src || this.builtIn[vname];
            }

            if (this.isMathMethod(vname)) {
                return '$jc$.board.mathLib.' + vname;
//                return 'Math.' + vname;
            }

            // if (!local) {
            //     if (Type.isId(this.board, vname)) {
            //         r = '$jc$.board.objects[\'' + vname + '\']';
            //     } else if (Type.isName(this.board, vname)) {
            //         r = '$jc$.board.elementsByName[\'' + vname + '\']';
            //     } else if (Type.isGroup(this.board, vname)) {
            //         r = '$jc$.board.groups[\'' + vname + '\']';
            //     }

            //     return r;
            // }
            if (!local) {
                if (Type.isId(this.board, vname)) {
                    r = '$jc$.board.objects[\'' + vname + '\']';
                    if (this.board.objects[vname].elType === 'slider') {
                        r += '.Value()';
                    }
                } else if (Type.isName(this.board, vname)) {
                    r = '$jc$.board.elementsByName[\'' + vname + '\']';
                    if (this.board.elementsByName[vname].elType === 'slider') {
                        r += '.Value()';
                    }
                } else if (Type.isGroup(this.board, vname)) {
                    r = '$jc$.board.groups[\'' + vname + '\']';
                }

                return r;
            }

            return '';
        },

        /**
         * Adds the property <tt>isMap</tt> to a function and sets it to true.
         * @param {function} f
         * @returns {function}
         */
        makeMap: function (f) {
            f.isMap = true;

            return f;
        },

        functionCodeJS: function (node) {
            var p = node.children[0].join(', '),
                bo = '',
                bc = '';

            if (node.value === 'op_map') {
                bo = '{ return  ';
                bc = ' }';
            }

            return 'function (' + p + ') {\n' +
                    'var $oldscope$ = $jc$.scope;\n' +
                    '$jc$.scope = $jc$.scopes[' + this.scope.id + '];\n' +
                    'var r = (function () ' + bo + this.compile(node.children[1], true) + bc + ')();\n' +
                    '$jc$.scope = $oldscope$;\n' +
                    'return r;\n' +
                '}';
        },

        /**
         * Converts a node type <tt>node_op</tt> and value <tt>op_map</tt> or <tt>op_function</tt> into a executable
         * function. Does a simple type inspection.
         * @param {Object} node
         * @returns {function}
         * @see JXG.JessieCode#resolveType
         */
         defineFunction: function (node) {
            var fun, i, that = this,
                list = node.children[0],
                scope = this.pushScope(list);

            if (this.board.options.jc.compile) {
                this.isLHS = false;

                // we currently need to put the parameters into the local scope
                // until the compiled JS variable lookup code is fixed
                for (i = 0; i < list.length; i++) {
                    scope.locals[list[i]] = list[i];
                }

                this.replaceNames(node.children[1]);

                /** @ignore */
                fun = (function ($jc$) {
                    var fun,
                        str = 'var f = ' + $jc$.functionCodeJS(node) + '; f;';

                    try {
                        // yeah, eval is evil, but we don't have much choice here.
                        // the str is well defined and there is no user input in it that we didn't check before

                        /*jslint evil:true*/
                        fun = eval(str);
                        /*jslint evil:false*/

                        scope.argtypes = [];
                        for (i = 0; i < list.length; i++) {
                            scope.argtypes.push(that.resolveType(list[i], node));
                        }

                        return fun;
                    } catch (e) {
                        $jc$._warn('error compiling function\n\n' + str + '\n\n' + e.toString());
                        return function () {};
                    }
                }(this));

                // clean up scope
                this.popScope();
            } else {
                /** @ignore */
                fun = (function (_pstack, that, id) {
                    return function () {
                        var r, oldscope;

                        oldscope = that.scope;
                        that.scope = that.scopes[id];

                        for (r = 0; r < _pstack.length; r++) {
                            that.scope.locals[_pstack[r]] = arguments[r];
                        }

                        r = that.execute(node.children[1]);
                        that.scope = oldscope;

                        return r;
                    };
                }(list, this, scope.id));
            }

            fun.node = node;
            fun.scope = scope;
            fun.toJS = fun.toString;
            fun.toString = (function (_that) {
                return function () {
                    return _that.compile(_that.replaceIDs(Type.deepCopy(node)));
                };
            }(this));

            fun.deps = {};
            this.collectDependencies(node.children[1], fun.deps);

            return fun;
        },

        /**
         * Merge all attribute values given with an element creator into one object.
         * @param {Object} o An arbitrary number of objects
         * @returns {Object} All given objects merged into one. If properties appear in more (case sensitive) than one
         * object the last value is taken.
         */
        mergeAttributes: function (o) {
            var i, attr = {};

            for (i = 0; i < arguments.length; i++) {
                attr = Type.deepCopy(attr, arguments[i], true);
            }

            return attr;
        },

        /**
         * Sets the property <tt>what</tt> of <tt>o</tt> to <tt>value</tt>
         * @param {JXG.Point|JXG.Text} o
         * @param {String} what
         * @param value
         */
        setProp: function (o, what, value) {
            var par = {}, x, y;

            if (o.elementClass === Const.OBJECT_CLASS_POINT && (what === 'X' || what === 'Y')) {
                // set coords

                what = what.toLowerCase();

                // we have to deal with three cases here:
                // o.isDraggable && typeof value === number:
                //   stay draggable, just set the new coords (e.g. via moveTo)
                // o.isDraggable && typeof value === function:
                //   convert to !o.isDraggable, set the new coords via o.addConstraint()
                // !o.isDraggable:
                //   stay !o.isDraggable, update the given coord by overwriting X/YEval

                if (o.isDraggable && typeof value === 'number') {
                    x = what === 'x' ? value : o.X();
                    y = what === 'y' ? value : o.Y();

                    o.setPosition(Const.COORDS_BY_USER, [x, y]);
                } else if (o.isDraggable && (typeof value === 'function' || typeof value === 'string')) {
                    x = what === 'x' ? value : o.coords.usrCoords[1];
                    y = what === 'y' ? value : o.coords.usrCoords[2];

                    o.addConstraint([x, y]);
                } else if (!o.isDraggable) {
                    x = what === 'x' ? value : o.XEval.origin;
                    y = what === 'y' ? value : o.YEval.origin;

                    o.addConstraint([x, y]);
                }

                this.board.update();
            } else if (o.elementClass === Const.OBJECT_CLASS_TEXT && (what === 'X' || what === 'Y')) {
                if (typeof value === 'number') {
                    o[what] = function () { return value; };
                } else if (typeof value === 'function') {
                    o.isDraggable = false;
                    o[what] = value;
                } else if (typeof value === 'string') {
                    o.isDraggable = false;
                    o[what] = Type.createFunction(value, this.board, null, true);
                    o[what + 'jc'] = value;
                }

                o[what].origin = value;

                this.board.update();
            } else if (o.type && o.elementClass && o.visProp) {
                if (Type.exists(o[o.methodMap[what]]) && typeof o[o.methodMap[what]] !== 'function') {
                    o[o.methodMap[what]] = value;
                } else {
                    par[what] = value;
                    o.setAttribute(par);
                }
            } else {
                o[what] = value;
            }
        },

        /**
         * Generic method to parse JessieCode.
         * This consists of generating an AST with parser.parse,
         * apply simplifying rules from CA and
         * manipulate the AST according to the second parameter "cmd".
         * @param  {String} code      JessieCode code to be parsed
         * @param  {String} cmd       Type of manipulation to be done with AST
         * @param {Boolean} [geonext=false] Geonext compatibility mode.
         * @param {Boolean} dontstore If false, the code string is stored in this.code.
         * @return {Object}           Returns result of computation as directed in cmd.
         */
        _genericParse:  function (code, cmd, geonext, dontstore) {
            var i, setTextBackup, ast, result,
                ccode = code.replace(/\r\n/g, '\n').split('\n'),
                cleaned = [];

            if (!dontstore) {
                this.code += code + '\n';
            }

            if (Text) {
                setTextBackup = Text.Text.prototype.setText;
                Text.Text.prototype.setText = Text.Text.prototype.setTextJessieCode;
            }

            try {
                if (!Type.exists(geonext)) {
                    geonext = false;
                }

                for (i = 0; i < ccode.length; i++) {
                    if (geonext) {
                        ccode[i] = JXG.GeonextParser.geonext2JS(ccode[i], this.board);
                    }
                    cleaned.push(ccode[i]);
                }

                code = cleaned.join('\n');
                ast = parser.parse(code);
                if (this.CA) {
                    ast = this.CA.expandDerivatives(ast, null, ast);
                    ast = this.CA.removeTrivialNodes(ast);
                }
                switch (cmd) {
                    case 'parse':
                        result = this.execute(ast);
                        break;
                    case 'manipulate':
                        result = this.compile(ast);
                        break;
                    case 'getAst':
                        result = ast;
                        break;
                    default:
                        result = false;
                }
            } catch (e) {  // catch is mandatory in old IEs
                // console.log(e);
                // We throw the error again,
                // so the user can catch it.
                throw e;
            } finally {
                // make sure the original text method is back in place
                if (Text) {
                    Text.Text.prototype.setText = setTextBackup;
                }
            }

            return result;
        },

        /**
         * Parses JessieCode.
         * This consists of generating an AST with parser.parse, apply simplifying rules
         * from CA and executing the ast by calling this.execute(ast).
         *
         * @param {String} code             JessieCode code to be parsed
         * @param {Boolean} [geonext=false] Geonext compatibility mode.
         * @param {Boolean} dontstore       If false, the code string is stored in this.code.
         * @return {Object}                 Parse JessieCode code and execute it.
         */
        parse: function (code, geonext, dontstore) {
            return this._genericParse(code, 'parse', geonext, dontstore);
        },

        /**
         * Manipulate JessieCode.
         * This consists of generating an AST with parser.parse,
         * apply simlifying rules from CA
         * and compile the AST back to JessieCode.
         *
         * @param {String} code             JessieCode code to be parsed
         * @param {Boolean} [geonext=false] Geonext compatibility mode.
         * @param {Boolean} dontstore       If false, the code string is stored in this.code.
         * @return {String}                 Simplified JessieCode code
         */
        manipulate: function (code, geonext, dontstore) {
            return this._genericParse(code, 'manipulate', geonext, dontstore);
        },

        /**
         * Get abstract syntax tree (AST) from JessieCode code.
         * This consists of generating an AST with parser.parse.
         *
         * @param {String} code
         * @param {Boolean} [geonext=false] Geonext compatibility mode.
         * @param {Boolean} dontstore
         * @return {Node}  AST
         */
        getAST: function (code, geonext, dontstore) {
            return this._genericParse(code, 'getAst', geonext, dontstore);
        },

        /**
         * Parses a JessieCode snippet, e.g. "3+4", and wraps it into a function, if desired.
         * @param {String} code A small snippet of JessieCode. Must not be an assignment.
         * @param {Boolean} funwrap If true, the code is wrapped in a function.
         * @param {String} varname Name of the parameter(s)
         * @param {Boolean} [geonext=false] Geonext compatibility mode.
         */
        snippet: function (code, funwrap, varname, geonext) {
            var c;

            funwrap = Type.def(funwrap, true);
            varname = Type.def(varname, '');
            geonext = Type.def(geonext, false);

            c = (funwrap ? ' function (' + varname + ') { return ' : '') + code + (funwrap ? '; }' : '') + ';';

            return this.parse(c, geonext, true);
        },

        /**
         * Traverses through the given subtree and changes all values of nodes with the replaced flag set by
         * {@link JXG.JessieCode#replaceNames} to the name of the element (if not empty).
         * @param {Object} node
         */
        replaceIDs: function (node) {
            var i, v;

            if (node.replaced) {
                // These children exist, if node.replaced is set.
                v = this.board.objects[node.children[1][0].value];

                if (Type.exists(v) && v.name !== "") {
                    node.type = 'node_var';
                    node.value = v.name;

                    // Maybe it's not necessary, but just to be sure that everything is cleaned up we better delete all
                    // children and the replaced flag
                    node.children.length = 0;
                    delete node.replaced;
                }
            }

            if (Type.isArray(node)) {
                for (i = 0; i < node.length; i++) {
                    node[i] = this.replaceIDs(node[i]);
                }
            }

            if (node.children) {
                // assignments are first evaluated on the right hand side
                for (i = node.children.length; i > 0; i--) {
                    if (Type.exists(node.children[i - 1])) {
                        node.children[i - 1] = this.replaceIDs(node.children[i - 1]);
                    }

                }
            }

            return node;
        },

        /**
         * Traverses through the given subtree and changes all elements referenced by names through referencing them by ID.
         * An identifier is only replaced if it is not found in all scopes above the current scope and if it
         * has not been blacklisted within the codeblock determined by the given subtree.
         * @param {Object} node
         */
        replaceNames: function (node) {
            var i, v;

            v = node.value;

            // We are interested only in nodes of type node_var and node_op > op_lhs.
            // Currently, we are not checking if the id is a local variable. in this case, we're stuck anyway.

            if (node.type === 'node_op' && v === 'op_lhs' && node.children.length === 1) {
                this.isLHS = true;
            } else if (node.type === 'node_var') {
                if (this.isLHS) {
                    this.letvar(v, true);
                } else if (!Type.exists(this.getvar(v, true)) && Type.exists(this.board.elementsByName[v])) {
                    node = this.createReplacementNode(node);
                }
            }

            if (Type.isArray(node)) {
                for (i = 0; i < node.length; i++) {
                    node[i] = this.replaceNames(node[i]);
                }
            }

            if (node.children) {
                // Assignments are first evaluated on the right hand side
                for (i = node.children.length; i > 0; i--) {
                    if (Type.exists(node.children[i - 1])) {
                        node.children[i - 1] = this.replaceNames(node.children[i - 1]);
                    }
                }
            }

            if (node.type === 'node_op' && node.value === 'op_lhs' && node.children.length === 1) {
                this.isLHS = false;
            }

            return node;
        },

        /**
         * Replaces node_var nodes with node_op&gt;op_execfun nodes, calling the internal $() function with the id of the
         * element accessed by the node_var node.
         * @param {Object} node
         * @returns {Object} op_execfun node
         */
        createReplacementNode: function (node) {
            var v = node.value,
                el = this.board.elementsByName[v];

            node = this.createNode('node_op', 'op_execfun',
                this.createNode('node_var', '$'),
                [this.createNode('node_str', el.id)]);

            node.replaced = true;

            return node;
        },

        /**
         * Search the parse tree below <tt>node</tt> for <em>stationary</em> dependencies, i.e. dependencies hard coded into
         * the function.
         * @param {Object} node
         * @param {Object} result An object where the referenced elements will be stored. Access key is their id.
         */
        collectDependencies: function (node, result) {
            var i, v, e, le;

            if (Type.isArray(node)) {
                le = node.length;
                for (i = 0; i < le; i++) {
                    this.collectDependencies(node[i], result);
                }
                return;
            }

            v = node.value;

            if (node.type === 'node_var') {
                e = this.getvar(v);
                if (e && e.visProp && e.type && e.elementClass && e.id) {
                    result[e.id] = e;
                }
            }

            // The $()-function-calls are special because their parameter is given as a string, not as a node_var.
            if (node.type === 'node_op' && node.value === 'op_execfun' &&
                node.children.length > 1 && node.children[0].value === '$' &&
                node.children[1].length > 0) {

                e = node.children[1][0].value;
                result[e] = this.board.objects[e];
            }

            if (node.children) {
                for (i = node.children.length; i > 0; i--) {
                    if (Type.exists(node.children[i - 1])) {
                        this.collectDependencies(node.children[i - 1], result);
                    }

                }
            }
        },

        resolveProperty: function (e, v, compile) {
            compile = Type.def(compile, false);

            // is it a geometry element or a board?
            if (e /*&& e.type && e.elementClass*/ && e.methodMap) {
                // yeah, it is. but what does the user want?
                if (Type.exists(e.subs) && Type.exists(e.subs[v])) {
                    // a subelement it is, good sir.
                    e = e.subs;
                } else if (Type.exists(e.methodMap[v])) {
                    // the user wants to call a method
                    v = e.methodMap[v];
                } else {
                    // the user wants to change an attribute
                    e = e.visProp;
                    v = v.toLowerCase();
                }
            }

            if (Type.isFunction(e)) {
                this._error('Accessing function properties is not allowed.');
            }

            if (!Type.exists(e)) {
                this._error(e + ' is not an object');
            }

            if (!Type.exists(e[v])) {
                this._error('unknown property ' + v);
            }

            if (compile && typeof e[v] === 'function') {
                return function () { return e[v].apply(e, arguments); };
            }

            return e[v];
        },

        /**
         * Type inspection: check if the string vname appears as function name in the
         * AST node. Used in "op_execfun". This allows the JessieCode exmples below.
         *
         * @private
         * @param {String} vname
         * @param {Object} node
         * @returns 'any' or 'function'
         * @see JXG.JessieCode#execute
         * @see JXG.JessieCode#getvar
         *
         * @example
         *  var p = board.create('point', [2, 0], {name: 'X'});
         *  var txt = 'X(X)';
         *  console.log(board.jc.parse(txt));
         *
         * @example
         *  var p = board.create('point', [2, 0], {name: 'X'});
         *  var txt = 'f = function(el, X) { return X(el); }; f(X, X);';
         *  console.log(board.jc.parse(txt));
         *
         * @example
         *  var p = board.create('point', [2, 0], {name: 'point'});
         *  var txt = 'B = point(1,3); X(point);';
         *  console.log(board.jc.parse(txt));
         *
         * @example
         *  var p = board.create('point', [2, 0], {name: 'A'});
         *  var q = board.create('point', [-2, 0], {name: 'X'});
         *  var txt = 'getCoord=function(p, f){ return f(p); }; getCoord(A, X);';
         *  console.log(board.jc.parse(txt));
         */
         resolveType: function(vname, node) {
            var i, t,
                type = 'any'; // Possible values: 'function', 'any'

            if (Type.isArray(node)) {
                // node contains the parameters of a function call or function declaration
                for (i = 0; i < node.length; i++) {
                    t = this.resolveType(vname, node[i]);
                    if (t !== 'any') {
                        type = t;
                        return type;
                    }
                }
            }

            if (node.type === 'node_op' && node.value === 'op_execfun' &&
                node.children[0].type === 'node_var' && node.children[0].value === vname) {
                return 'function';
            }

            if (node.type === 'node_op') {
                for (i = 0; i < node.children.length; i++) {
                    if (node.children[0].type === 'node_var' && node.children[0].value === vname &&
                        (node.value === 'op_add' || node.value === 'op_sub' || node.value === 'op_mul' ||
                            node.value === 'op_div' || node.value === 'op_mod' || node.value === 'op_exp' ||
                            node.value === 'op_neg')) {
                        return 'any';
                    }
                }

                for (i = 0; i < node.children.length; i++) {
                    t = this.resolveType(vname, node.children[i]);
                    if (t !== 'any') {
                        type = t;
                        return type;
                    }
                }
            }

            return 'any';
        },

        /**
         * Resolves the lefthand side of an assignment operation
         * @param node
         * @returns {Object} An object with two properties. <strong>o</strong> which contains the object, and
         * a string <strong>what</strong> which contains the property name.
         */
        getLHS: function (node) {
            var res;

            if (node.type === 'node_var') {
                res = {
                    o: this.scope.locals,
                    what: node.value
                };
            } else if (node.type === 'node_op' && node.value === 'op_property') {
                res = {
                    o: this.execute(node.children[0]),
                    what: node.children[1]
                };
            } else if (node.type === 'node_op' && node.value === 'op_extvalue') {
                res = {
                    o: this.execute(node.children[0]),
                    what: this.execute(node.children[1])
                };
            } else {
                throw new Error('Syntax error: Invalid left-hand side of assignment.');
            }

            return res;
        },

        getLHSCompiler: function (node, js) {
            var res;

            if (node.type === 'node_var') {
                res = node.value;
            } else if (node.type === 'node_op' && node.value === 'op_property') {
                res = [
                    this.compile(node.children[0], js),
                    "'" + node.children[1] + "'"
                ];
            } else if (node.type === 'node_op' && node.value === 'op_extvalue') {
                res = [
                    this.compile(node.children[0], js),
                    node.children[1].type === 'node_const' ? node.children[1].value : this.compile(node.children[1], js)
                ];
            } else {
                throw new Error('Syntax error: Invalid left-hand side of assignment.');
            }

            return res;
        },

        /**
         * Executes a parse subtree.
         * @param {Object} node
         * @returns {Number|String|Object|Boolean} Something
         * @private
         */
        execute: function (node) {
            var ret, v, i, e, l, undef, list, ilist,
                parents = [],
                // exec fun
                fun, attr, sc;

            ret = 0;

            if (!node) {
                return ret;
            }

            this.line = node.line;
            this.col = node.col;

            switch (node.type) {
            case 'node_op':
                switch (node.value) {
                case 'op_none':
                    if (node.children[0]) {
                        this.execute(node.children[0]);
                    }
                    if (node.children[1]) {
                        ret = this.execute(node.children[1]);
                    }
                    break;
                case 'op_assign':
                    v = this.getLHS(node.children[0]);
                    this.lhs[this.scope.id] = v.what;

                    if (v.o.type && v.o.elementClass && v.o.methodMap && v.what === 'label') {
                        this._error('Left-hand side of assignment is read-only.');
                    }

                    ret = this.execute(node.children[1]);
                    if (v.o !== this.scope.locals || (Type.isArray(v.o) && typeof v.what === 'number')) {
                        // it is either an array component being set or a property of an object.
                        this.setProp(v.o, v.what, ret);
                    } else {
                        // this is just a local variable inside JessieCode
                        this.letvar(v.what, ret);
                    }
                    this.lhs[this.scope.id] = 0;
                    break;
                case 'op_if':
                    if (this.execute(node.children[0])) {
                        ret = this.execute(node.children[1]);
                    }
                    break;
                case 'op_conditional':
                    // fall through
                case 'op_if_else':
                    if (this.execute(node.children[0])) {
                        ret = this.execute(node.children[1]);
                    } else {
                        ret = this.execute(node.children[2]);
                    }
                    break;
                case 'op_while':
                    while (this.execute(node.children[0])) {
                        this.execute(node.children[1]);
                    }
                    break;
                case 'op_do':
                    do {
                        this.execute(node.children[0]);
                    } while (this.execute(node.children[1]));
                    break;
                case 'op_for':
                    for (this.execute(node.children[0]); this.execute(node.children[1]); this.execute(node.children[2])) {
                        this.execute(node.children[3]);
                    }
                    break;
                case 'op_proplst':
                    if (node.children[0]) {
                        this.execute(node.children[0]);
                    }
                    if (node.children[1]) {
                        this.execute(node.children[1]);
                    }
                    break;
                case 'op_emptyobject':
                    ret = {};
                    break;
                case 'op_proplst_val':
                    this.propstack.push({});
                    this.propscope++;

                    this.execute(node.children[0]);
                    ret = this.propstack[this.propscope];

                    this.propstack.pop();
                    this.propscope--;
                    break;
                case 'op_prop':
                    // child 0: Identifier
                    // child 1: Value
                    this.propstack[this.propscope][node.children[0]] = this.execute(node.children[1]);
                    break;
                case 'op_array':
                    ret = [];
                    l = node.children[0].length;

                    for (i = 0; i < l; i++) {
                        ret.push(this.execute(node.children[0][i]));
                    }

                    break;
                case 'op_extvalue':
                    ret = this.execute(node.children[0]);
                    i = this.execute(node.children[1]);

                    if (typeof i === 'number' && Math.abs(Math.round(i) - i) < Mat.eps) {
                        ret = ret[i];
                    } else {
                        ret = undef;
                    }
                    break;
                case 'op_return':
                    if (this.scope === 0) {
                        this._error('Unexpected return.');
                    } else {
                        return this.execute(node.children[0]);
                    }
                    break;
                case 'op_map':
                    if (!node.children[1].isMath && node.children[1].type !== 'node_var') {
                        this._error('execute: In a map only function calls and mathematical expressions are allowed.');
                    }

                    /** @ignore */
                    fun = this.defineFunction(node);
                    fun.isMap = true;

                    ret = fun;
                    break;
                case 'op_function':
                    // parse the parameter list
                    // after this, the parameters are in pstack

                    /** @ignore */
                    fun = this.defineFunction(node);
                    fun.isMap = false;

                    ret = fun;
                    break;
                case 'op_execfun':
                    // node.children:
                    //   [0]: Name of the function
                    //   [1]: Parameter list as a parse subtree
                    //   [2]: Properties, only used in case of a create function
                    this.dpstack.push([]);
                    this.pscope++;

                    // parameter parsing is done below
                    list = node.children[1];

                    // parse the properties only if given
                    if (Type.exists(node.children[2])) {
                        if (node.children[3]) {
                            ilist = node.children[2];
                            attr = {};

                            for (i = 0; i < ilist.length; i++) {
                                attr = Type.deepCopy(attr, this.execute(ilist[i]), true);
                            }
                        } else {
                            attr = this.execute(node.children[2]);
                        }
                    }

                    // look up the variables name in the variable table
                    node.children[0]._isFunctionName = true;
                    fun = this.execute(node.children[0]);
                    delete node.children[0]._isFunctionName;

                    // determine the scope the function wants to run in
                    if (fun && fun.sc) {
                        sc = fun.sc;
                    } else {
                        sc = this;
                    }

                    if (!fun.creator && Type.exists(node.children[2])) {
                        this._error('Unexpected value. Only element creators are allowed to have a value after the function call.');
                    }

                    // interpret ALL the parameters
                    for (i = 0; i < list.length; i++) {
                        if (Type.exists(fun.scope) && Type.exists(fun.scope.argtypes) &&fun.scope.argtypes[i] === 'function') {
                            // Type inspection
                            list[i]._isFunctionName = true;
                            parents[i] = this.execute(list[i]);
                            delete list[i]._isFunctionName;
                        } else {
                            parents[i] = this.execute(list[i]);
                        }
                        //parents[i] = Type.evalSlider(this.execute(list[i]));
                        this.dpstack[this.pscope].push({
                            line: node.children[1][i].line,
                            // SketchBin currently works only if the last column of the
                            // parent position is taken. This is due to how I patched JS/CC
                            // to count the lines and columns. So, ecol will do for now
                            col: node.children[1][i].ecol
                        });
                    }

                    // check for the function in the variable table
                    if (typeof fun === 'function' && !fun.creator) {
                        ret = fun.apply(sc, parents);
                    } else if (typeof fun === 'function' && !!fun.creator) {
                        e = this.line;

                        // creator methods are the only ones that take properties, hence this special case
                        try {
                            ret = fun(parents, attr);
                            ret.jcLineStart = e;
                            ret.jcLineEnd = node.eline;

                            for (i = e; i <= node.line; i++) {
                                this.lineToElement[i] = ret;
                            }

                            ret.debugParents = this.dpstack[this.pscope];
                        } catch (ex) {
                            this._error(ex.toString());
                        }
                    } else {
                        this._error('Function \'' + fun + '\' is undefined.');
                    }

                    // clear parameter stack
                    this.dpstack.pop();
                    this.pscope--;
                    break;
                case 'op_property':
                    e = this.execute(node.children[0]);
                    v = node.children[1];

                    ret = this.resolveProperty(e, v, false);

                    // set the scope, in case this is a method the user wants to call
                    if (Type.exists(ret)) {
                        ret.sc = e;
                    }

                    break;
                case 'op_use':
                    this._warn('Use of the \'use\' operator is deprecated.');
                    this.use(node.children[0].toString());
                    break;
                case 'op_delete':
                    this._warn('Use of the \'delete\' operator is deprecated. Please use the remove() function.');
                    v = this.getvar(node.children[0]);
                    ret = this.del(v);
                    break;
                case 'op_eq':
                    // == is intentional
                    /*jslint eqeq:true*/
                    ret = this.execute(node.children[0]) == this.execute(node.children[1]);
                    /*jslint eqeq:false*/
                    break;
                case 'op_neq':
                    // != is intentional
                    /*jslint eqeq:true*/
                    ret = this.execute(node.children[0]) != this.execute(node.children[1]);
                    /*jslint eqeq:true*/
                    break;
                case 'op_approx':
                    ret = Math.abs(this.execute(node.children[0]) - this.execute(node.children[1])) < Mat.eps;
                    break;
                case 'op_gt':
                    ret = this.execute(node.children[0]) > this.execute(node.children[1]);
                    break;
                case 'op_lt':
                    ret = this.execute(node.children[0]) < this.execute(node.children[1]);
                    break;
                case 'op_geq':
                    ret = this.execute(node.children[0]) >= this.execute(node.children[1]);
                    break;
                case 'op_leq':
                    ret = this.execute(node.children[0]) <= this.execute(node.children[1]);
                    break;
                case 'op_or':
                    ret = this.execute(node.children[0]) || this.execute(node.children[1]);
                    break;
                case 'op_and':
                    ret = this.execute(node.children[0]) && this.execute(node.children[1]);
                    break;
                case 'op_not':
                    ret = !this.execute(node.children[0]);
                    break;
                case 'op_add':
                    ret = this.add(this.execute(node.children[0]), this.execute(node.children[1]));
                    break;
                case 'op_sub':
                    ret = this.sub(this.execute(node.children[0]), this.execute(node.children[1]));
                    break;
                case 'op_div':
                    ret = this.div(this.execute(node.children[0]), this.execute(node.children[1]));
                    break;
                case 'op_mod':
                    // use mathematical modulo, JavaScript implements the symmetric modulo.
                    ret = this.mod(this.execute(node.children[0]), this.execute(node.children[1]), true);
                    break;
                case 'op_mul':
                    ret = this.mul(this.execute(node.children[0]), this.execute(node.children[1]));
                    break;
                case 'op_exp':
                    ret = this.pow(this.execute(node.children[0]),  this.execute(node.children[1]));
                    break;
                case 'op_neg':
                    ret = this.neg(this.execute(node.children[0]));
                    break;
                }
                break;

            case 'node_var':
                // node._isFunctionName is set in execute: at op_execfun.
                ret = this.getvar(node.value, false, node._isFunctionName);
                break;

            case 'node_const':
                if(node.value === null) {
                    ret = null;
                } else {
                    ret = Number(node.value);
                }
                break;

            case 'node_const_bool':
                ret = node.value;
                break;

            case 'node_str':
                //ret = node.value.replace(/\\'/, "'").replace(/\\"/, '"').replace(/\\\\/, '\\');
                /*jslint regexp:true*/
                ret = node.value.replace(/\\(.)/, '$1');
                /*jslint regexp:false*/
                break;
            }

            return ret;
        },

        /**
         * Compiles a parse tree back to JessieCode.
         * @param {Object} node
         * @param {Boolean} [js=false] Compile either to JavaScript or back to JessieCode (required for the UI).
         * @returns Something
         * @private
         */
        compile: function (node, js) {
            var e, i, list, scope,
                ret = '';

            if (!Type.exists(js)) {
                js = false;
            }

            if (!node) {
                return ret;
            }

            switch (node.type) {
            case 'node_op':
                switch (node.value) {
                case 'op_none':
                    if (node.children[0]) {
                        ret = this.compile(node.children[0], js);
                    }
                    if (node.children[1]) {
                        ret += this.compile(node.children[1], js);
                    }
                    break;
                case 'op_assign':
                    //e = this.compile(node.children[0], js);
                    if (js) {
                        e = this.getLHSCompiler(node.children[0], js);
                        if (Type.isArray(e)) {
                            ret = '$jc$.setProp(' + e[0] + ', ' + e[1] + ', ' + this.compile(node.children[1], js) + ');\n';
                        } else {
                            if (this.isLocalVariable(e) !== this.scope) {
                                this.scope.locals[e] = true;
                            }
                            ret = '$jc$.scopes[' + this.scope.id + '].locals[\'' + e + '\'] = ' + this.compile(node.children[1], js) + ';\n';
                        }
                    } else {
                        e = this.compile(node.children[0]);
                        ret = e + ' = ' + this.compile(node.children[1], js) + ';\n';
                    }
                    break;
                case 'op_if':
                    ret = ' if (' + this.compile(node.children[0], js) + ') ' + this.compile(node.children[1], js);
                    break;
                case 'op_if_else':
                    ret = ' if (' + this.compile(node.children[0], js) + ')' + this.compile(node.children[1], js);
                    ret += ' else ' + this.compile(node.children[2], js);
                    break;
                case 'op_conditional':
                    ret = '((' + this.compile(node.children[0], js) + ')?(' + this.compile(node.children[1], js);
                    ret += '):(' + this.compile(node.children[2], js) + '))';
                    break;
                case 'op_while':
                    ret = ' while (' + this.compile(node.children[0], js) + ') {\n' + this.compile(node.children[1], js) + '}\n';
                    break;
                case 'op_do':
                    ret = ' do {\n' + this.compile(node.children[0], js) + '} while (' + this.compile(node.children[1], js) + ');\n';
                    break;
                case 'op_for':
                    //ret = ' for (' + this.compile(node.children[0], js) + '; ' + this.compile(node.children[1], js) + '; ' + this.compile(node.children[2], js) + ') {\n' + this.compile(node.children[3], js) + '\n}\n';
                    ret = ' for (' + this.compile(node.children[0], js) +               // Assignment ends with ";"
                                    this.compile(node.children[1], js) + '; ' +         // Logical test comes without ";"
                                    this.compile(node.children[2], js).slice(0, -2) +   // Counting comes with ";" which has to be removed
                                    ') {\n' + this.compile(node.children[3], js) + '\n}\n';
                    break;
                case 'op_proplst':
                    if (node.children[0]) {
                        ret = this.compile(node.children[0], js) + ', ';
                    }

                    ret += this.compile(node.children[1], js);
                    break;
                case 'op_prop':
                    // child 0: Identifier
                    // child 1: Value
                    ret = node.children[0] + ': ' + this.compile(node.children[1], js);
                    break;
                case 'op_emptyobject':
                    ret = js ? '{}' : '<< >>';
                    break;
                case 'op_proplst_val':
                    ret = this.compile(node.children[0], js);
                    break;
                case 'op_array':
                    list = [];
                    for (i = 0; i < node.children[0].length; i++) {
                        list.push(this.compile(node.children[0][i], js));
                    }
                    ret = '[' + list.join(', ') + ']';
                    break;
                case 'op_extvalue':
                    ret = this.compile(node.children[0], js) + '[' + this.compile(node.children[1], js) + ']';
                    break;
                case 'op_return':
                    ret = ' return ' + this.compile(node.children[0], js) + ';\n';
                    break;
                case 'op_map':
                    if (!node.children[1].isMath && node.children[1].type !== 'node_var') {
                        this._error('compile: In a map only function calls and mathematical expressions are allowed.');
                    }

                    list = node.children[0];
                    if (js) {
                        ret = ' $jc$.makeMap(function (' + list.join(', ') + ') { return ' + this.compile(node.children[1], js) + '; })';
                    } else {
                        ret = 'map (' + list.join(', ') + ') -> ' + this.compile(node.children[1], js);
                    }

                    break;
                case 'op_function':
                    list = node.children[0];
                    scope = this.pushScope(list);
                    if (js) {
                        ret = this.functionCodeJS(node);
                    } else {
                        ret = ' function (' + list.join(', ') + ') ' + this.compile(node.children[1], js);
                    }
                    this.popScope();
                    break;
                case 'op_execfunmath':
                    console.log('op_execfunmath: TODO');
                    ret = '-1';
                    break;
                case 'op_execfun':
                    // parse the properties only if given
                    if (node.children[2]) {
                        list = [];
                        for (i = 0; i < node.children[2].length; i++) {
                            list.push(this.compile(node.children[2][i], js));
                        }

                        if (js) {
                            e = '$jc$.mergeAttributes(' + list.join(', ') + ')';
                        }
                    }
                    node.children[0].withProps = !!node.children[2];
                    list = [];
                    for (i = 0; i < node.children[1].length; i++) {
                        list.push(this.compile(node.children[1][i], js));
                    }
                    ret = this.compile(node.children[0], js) + '(' + list.join(', ') + (node.children[2] && js ? ', ' + e : '') + ')' + (node.children[2] && !js ? e : '');
                    if (js) {
                        // Inserting a newline here allows simulataneously
                        // - procedural calls like Q.moveTo(...); and
                        // - function calls in expressions like log(x) + 1;
                        // Problem: procedural calls will not be ended by a semicolon.
                        ret += '\n';
                    }

                    // save us a function call when compiled to javascript
                    if (js && node.children[0].value === '$') {
                        ret = '$jc$.board.objects[' + this.compile(node.children[1][0], js) + ']';
                    }
                    break;
                case 'op_property':
                    if (js && node.children[1] !== 'X' && node.children[1] !== 'Y') {
                        ret = '$jc$.resolveProperty(' + this.compile(node.children[0], js) + ', \'' + node.children[1] + '\', true)';
                    } else {
                        ret = this.compile(node.children[0], js) + '.' + node.children[1];
                    }
                    break;
                case 'op_use':
                    this._warn('Use of the \'use\' operator is deprecated.');
                    if (js) {
                        ret = '$jc$.use(\'';
                    } else {
                        ret = 'use(\'';
                    }

                    ret += node.children[0].toString() + '\');';
                    break;
                case 'op_delete':
                    this._warn('Use of the \'delete\' operator is deprecated. Please use the remove() function.');
                    if (js) {
                        ret = '$jc$.del(';
                    } else {
                        ret = 'remove(';
                    }

                    ret += this.compile(node.children[0], js) + ')';
                    break;
                case 'op_eq':
                    ret = '(' + this.compile(node.children[0], js) + ' === ' + this.compile(node.children[1], js) + ')';
                    break;
                case 'op_neq':
                    ret = '(' + this.compile(node.children[0], js) + ' !== ' + this.compile(node.children[1], js) + ')';
                    break;
                case 'op_approx':
                    ret = '(' + this.compile(node.children[0], js) + ' ~= ' + this.compile(node.children[1], js) + ')';
                    break;
                case 'op_gt':
                    if (js) {
                        ret = '$jc$.gt(' + this.compile(node.children[0], js) + ', ' + this.compile(node.children[1], js) + ')';
                    } else {
                        ret = '(' + this.compile(node.children[0], js) + ' > ' + this.compile(node.children[1], js) + ')';
                    }
                    break;
                case 'op_lt':
                    if (js) {
                        ret = '$jc$.lt(' + this.compile(node.children[0], js) + ', ' + this.compile(node.children[1], js) + ')';
                    } else {
                        ret = '(' + this.compile(node.children[0], js) + ' < ' + this.compile(node.children[1], js) + ')';
                    }
                    break;
                case 'op_geq':
                    if (js) {
                        ret = '$jc$.geq(' + this.compile(node.children[0], js) + ', ' + this.compile(node.children[1], js) + ')';
                    } else {
                        ret = '(' + this.compile(node.children[0], js) + ' >= ' + this.compile(node.children[1], js) + ')';
                    }
                    break;
                case 'op_leq':
                    if (js) {
                        ret = '$jc$.leq(' + this.compile(node.children[0], js) + ', ' + this.compile(node.children[1], js) + ')';
                    } else {
                        ret = '(' + this.compile(node.children[0], js) + ' <= ' + this.compile(node.children[1], js) + ')';
                    }
                    break;
                case 'op_or':
                    ret = '(' + this.compile(node.children[0], js) + ' || ' + this.compile(node.children[1], js) + ')';
                    break;
                case 'op_and':
                    ret = '(' + this.compile(node.children[0], js) + ' && ' + this.compile(node.children[1], js) + ')';
                    break;
                case 'op_not':
                    ret = '!(' + this.compile(node.children[0], js) + ')';
                    break;
                case 'op_add':
                    if (js) {
                        ret = '$jc$.add(' + this.compile(node.children[0], js) + ', ' + this.compile(node.children[1], js) + ')';
                    } else {
                        ret = '(' + this.compile(node.children[0], js) + ' + ' + this.compile(node.children[1], js) + ')';
                    }
                    break;
                case 'op_sub':
                    if (js) {
                        ret = '$jc$.sub(' + this.compile(node.children[0], js) + ', ' + this.compile(node.children[1], js) + ')';
                    } else {
                        ret = '(' + this.compile(node.children[0], js) + ' - ' + this.compile(node.children[1], js) + ')';
                    }
                    break;
                case 'op_div':
                    if (js) {
                        ret = '$jc$.div(' + this.compile(node.children[0], js) + ', ' + this.compile(node.children[1], js) + ')';
                    } else {
                        ret = '(' + this.compile(node.children[0], js) + ' / ' + this.compile(node.children[1], js) + ')';
                    }
                    break;
                case 'op_mod':
                    if (js) {
                        ret = '$jc$.mod(' + this.compile(node.children[0], js) + ', ' + this.compile(node.children[1], js) + ', true)';
                    } else {
                        ret = '(' + this.compile(node.children[0], js) + ' % ' + this.compile(node.children[1], js) + ')';
                    }
                    break;
                case 'op_mul':
                    if (js) {
                        ret = '$jc$.mul(' + this.compile(node.children[0], js) + ', ' + this.compile(node.children[1], js) + ')';
                    } else {
                        ret = '(' + this.compile(node.children[0], js) + ' * ' + this.compile(node.children[1], js) + ')';
                    }
                    break;
                case 'op_exp':
                    if (js) {
                        ret = '$jc$.pow(' + this.compile(node.children[0], js) + ', ' + this.compile(node.children[1], js) + ')';
                    } else {
                        ret = '(' + this.compile(node.children[0], js) + '^' + this.compile(node.children[1], js) + ')';
                    }
                    break;
                case 'op_neg':
                    if (js) {
                        ret = '$jc$.neg(' + this.compile(node.children[0], js) + ')';
                    } else {
                        ret = '(-' + this.compile(node.children[0], js) + ')';
                    }
                    break;
                }
                break;

            case 'node_var':
                if (js) {
                    ret = this.getvarJS(node.value, false, node.withProps);
                } else {
                    ret = node.value;
                }
                break;

            case 'node_const':
                ret = node.value;
                break;

            case 'node_const_bool':
                ret = node.value;
                break;

            case 'node_str':
                ret = '\'' + node.value + '\'';
                break;
            }

            if (node.needsBrackets) {
                ret = '{\n' + ret + '\n}\n';
            }

            return ret;
        },

        /**
         * This is used as the global getName() function.
         * @param {JXG.GeometryElement} obj
         * @param {Boolean} useId
         * @returns {String}
         */
        getName: function (obj,useId) {
            var name = '';

            if (Type.exists(obj) && Type.exists(obj.getName)) {
                name = obj.getName();
                if ((!Type.exists(name) || name === '') && !!useId) {
                    name = obj.id;
                }
            } else if (!!useId) {
                name = obj.id;
            }

            return name;
        },

        /**
         * This is used as the global X() function.
         * @param {JXG.Point|JXG.Text} e
         * @returns {Number}
         */
        X: function (e) {
            return e.X();
        },

        /**
         * This is used as the global Y() function.
         * @param {JXG.Point|JXG.Text} e
         * @returns {Number}
         */
        Y: function (e) {
            return e.Y();
        },

        /**
         * This is used as the global V() function.
         * @param {Glider|Slider} e
         * @returns {Number}
         */
        V: function (e) {
            return e.Value();
        },

        /**
         * This is used as the global L() function.
         * @param {JXG.Line} e
         * @returns {Number}
         */
        L: function (e) {
            return e.L();
        },

        /**
         * This is used as the global area() function.
         * @param {JXG.Circle|JXG.Polygon} obj
         * @returns {Number}
         */
        area: function (obj) {
            if (!Type.exists(obj) || !Type.exists(obj.Area)) {
                this._error('Error: Can\'t calculate area.');
            }

            return obj.Area();
        },

        /**
         * This is used as the global dist() function.
         * @param {JXG.Point} p1
         * @param {JXG.Point} p2
         * @returns {Number}
         */
        dist: function (p1, p2) {
            if (!Type.exists(p1) || !Type.exists(p1.Dist)) {
                this._error('Error: Can\'t calculate distance.');
            }

            return p1.Dist(p2);
        },

        /**
         * This is used as the global radius() function.
         * @param {JXG.Circle|Sector} obj
         * @returns {Number}
         */
        radius: function (obj) {
            if (!Type.exists(obj) || !Type.exists(obj.Radius)) {
                this._error('Error: Can\'t calculate radius.');
            }

            return obj.Radius();
        },

        /**
         * + operator implementation
         * @param {Number|Array|JXG.Point} a
         * @param {Number|Array|JXG.Point} b
         * @returns {Number|Array}
         */
        add: function (a, b) {
            var i, len, res;

            a = Type.evalSlider(a);
            b = Type.evalSlider(b);

            if (Interval.isInterval(a) || Interval.isInterval(b)) {
                res = Interval.add(a, b);
            } else if (Type.isArray(a) && Type.isArray(b)) {
                len = Math.min(a.length, b.length);
                res = [];

                for (i = 0; i < len; i++) {
                    res[i] = a[i] + b[i];
                }
            } else if (Type.isNumber(a) && Type.isNumber(b)) {
                res = a + b;
            } else if (Type.isString(a) || Type.isString(b)) {
                res = a.toString() + b.toString();
            } else {
                this._error('Operation + not defined on operands ' + typeof a + ' and ' + typeof b);
            }

            return res;
        },

        /**
         * - operator implementation
         * @param {Number|Array|JXG.Point} a
         * @param {Number|Array|JXG.Point} b
         * @returns {Number|Array}
         */
        sub: function (a, b) {
            var i, len, res;

            a = Type.evalSlider(a);
            b = Type.evalSlider(b);

            if (Interval.isInterval(a) || Interval.isInterval(b)) {
                res = Interval.sub(a, b);
            } else if (Type.isArray(a) && Type.isArray(b)) {
                len = Math.min(a.length, b.length);
                res = [];

                for (i = 0; i < len; i++) {
                    res[i] = a[i] - b[i];
                }
            } else if (Type.isNumber(a) && Type.isNumber(b)) {
                res = a - b;
            } else {
                this._error('Operation - not defined on operands ' + typeof a + ' and ' + typeof b);
            }

            return res;
        },

        /**
         * unary - operator implementation
         * @param {Number|Array|JXG.Point} a
         * @returns {Number|Array}
         */
        neg: function (a) {
            var i, len, res;

            a = Type.evalSlider(a);

            if (Interval.isInterval(a)) {
                res = Interval.negative(a);
            } else if (Type.isArray(a)) {
                len = a.length;
                res = [];

                for (i = 0; i < len; i++) {
                    res[i] = -a[i];
                }
            } else if (Type.isNumber(a)) {
                res = -a;
            } else {
                this._error('Unary operation - not defined on operand ' + typeof a);
            }

            return res;
        },

        /**
         * Multiplication of vectors and numbers
         * @param {Number|Array} a
         * @param {Number|Array} b
         * @returns {Number|Array} (Inner) product of the given input values.
         */
        mul: function (a, b) {
            var i, len, res;

            a = Type.evalSlider(a);
            b = Type.evalSlider(b);

            if (Type.isArray(a) && Type.isNumber(b)) {
                // swap b and a
                i = a;
                a = b;
                b = a;
            }

            if (Interval.isInterval(a) || Interval.isInterval(b)) {
                res = Interval.mul(a, b);
            } else if (Type.isArray(a) && Type.isArray(b)) {
                len = Math.min(a.length, b.length);
                res = Mat.innerProduct(a, b, len);
            } else if (Type.isNumber(a) && Type.isArray(b)) {
                len = b.length;
                res = [];

                for (i = 0; i < len; i++) {
                    res[i] = a * b[i];
                }
            } else if (Type.isNumber(a) && Type.isNumber(b)) {
                res = a * b;
            } else {
                this._error('Operation * not defined on operands ' + typeof a + ' and ' + typeof b);
            }

            return res;
        },

        /**
         * Implementation of the / operator.
         * @param {Number|Array} a
         * @param {Number} b
         * @returns {Number|Array}
         */
        div: function (a, b) {
            var i, len, res;

            a = Type.evalSlider(a);
            b = Type.evalSlider(b);

            if (Interval.isInterval(a) || Interval.isInterval(b)) {
                res = Interval.div(a, b);
            } else if (Type.isArray(a) && Type.isNumber(b)) {
                len = a.length;
                res = [];

                for (i = 0; i < len; i++) {
                    res[i] = a[i] / b;
                }
            } else if (Type.isNumber(a) && Type.isNumber(b)) {
                res = a / b;
            } else {
                this._error('Operation * not defined on operands ' + typeof a + ' and ' + typeof b);
            }

            return res;
        },

        /**
         * Implementation of the % operator.
         * @param {Number|Array} a
         * @param {Number} b
         * @returns {Number|Array}
         */
        mod: function (a, b) {
            var i, len, res;

            a = Type.evalSlider(a);
            b = Type.evalSlider(b);

            if (Interval.isInterval(a) || Interval.isInterval(b)) {
                return Interval.fmod(a, b);
            } else if (Type.isArray(a) && Type.isNumber(b)) {
                len = a.length;
                res = [];

                for (i = 0; i < len; i++) {
                    res[i] = Mat.mod(a[i], b, true);
                }
            } else if (Type.isNumber(a) && Type.isNumber(b)) {
                res = Mat.mod(a, b, true);
            } else {
                this._error('Operation * not defined on operands ' + typeof a + ' and ' + typeof b);
            }

            return res;
        },

        /**
         * Pow function wrapper to allow direct usage of sliders.
         * @param {Number|Slider} a
         * @param {Number|Slider} b
         * @returns {Number}
         */
        pow: function (a, b) {
            a = Type.evalSlider(a);
            b = Type.evalSlider(b);

            if (Interval.isInterval(a) || Interval.isInterval(b)) {
                return Interval.pow(a, b);
            }
            return Mat.pow(a, b);
        },

        lt: function (a, b) {
            if (Interval.isInterval(a) || Interval.isInterval(b)) {
                return Interval.lt(a, b);
            }
            return a < b;
        },
        leq: function (a, b) {
            if (Interval.isInterval(a) || Interval.isInterval(b)) {
                return Interval.leq(a, b);
            }
            return a <= b;
        },
        gt: function (a, b) {
            if (Interval.isInterval(a) || Interval.isInterval(b)) {
                return Interval.gt(a, b);
            }
            return a > b;
        },
        geq: function (a, b) {
            if (Interval.isInterval(a) || Interval.isInterval(b)) {
                return Intervalt.geq(a, b);
            }
            return a >= b;
        },

        randint: function (min, max, step) {
            if (!Type.exists(step)) {
                step = 1;
            }
            return Math.round(Math.random() * (max - min) / step) * step + min;
        },

        DDD: function (f) {
            console.log('Dummy derivative function. This should never appear!');
        },

        /**
         * Implementation of the ?: operator
         * @param {Boolean} cond Condition
         * @param {*} v1
         * @param {*} v2
         * @returns {*} Either v1 or v2.
         */
        ifthen: function (cond, v1, v2) {
            if (cond) {
                return v1;
            }

            return v2;
        },

        /**
         * Implementation of the delete() builtin function
         * @param {JXG.GeometryElement} element
         */
        del: function (element) {
            if (typeof element === 'object' && JXG.exists(element.type) && JXG.exists(element.elementClass)) {
                this.board.removeObject(element);
            }
        },

        /**
         * Implementation of the use() builtin function
         * @param {String} board
         */
        use: function (board) {
            var b, ref,
                found = false;

            if (typeof board === 'string') {
                // search all the boards for the one with the appropriate container div
                for (b in JXG.boards) {
                    if (JXG.boards.hasOwnProperty(b) && JXG.boards[b].container === board) {
                        ref = JXG.boards[b];
                        found = true;
                        break;
                    }
                }
            } else {
                ref = board;
                found = true;
            }

            if (found) {
                this.board = ref;
                this.builtIn.$board = ref;
                this.builtIn.$board.src = '$jc$.board';
            } else {
                this._error('Board \'' + board + '\' not found!');
            }
        },

        /**
         * Find the first symbol to the given value from the given scope upwards.
         * @param v Value
         * @param {Number} [scope=-1] The scope, default is to start with current scope (-1).
         * @returns {Array} An array containing the symbol and the scope if a symbol could be found,
         * an empty array otherwise;
         */
        findSymbol: function (v, scope) {
            var i, s;

            scope = Type.def(scope, -1);

            if (scope === -1) {
                s = this.scope;
            } else {
                s = this.scopes[scope];
            }

            while (s !== null) {
                for (i in s.locals) {
                    if (s.locals.hasOwnProperty(i) && s.locals[i] === v) {
                        return [i, s];
                    }
                }

                s = s.previous;
            }

            return [];
        },

        /**
         * Import modules into a JessieCode script.
         * @param {String} module
         */
        importModule: function (module) {
            return priv.modules[module.toLowerCase()];
        },

        /**
         * Defines built in methods and constants.
         * @returns {Object} BuiltIn control object
         */
        defineBuiltIn: function () {
            var that = this,
                builtIn = {
                    PI: Math.PI,
                    EULER: Math.E,
                    D: that.DDD,
                    X: that.X,
                    Y: that.Y,
                    V: that.V,
                    L: that.L,

                    acosh: Mat.acosh,
                    acot: Mat.acot,
                    asinh: Mat.asinh,
                    binomial: Mat.binomial,
                    cbrt: Mat.cbrt,
                    cosh: Mat.cosh,
                    cot: Mat.cot,
                    deg: Geometry.trueAngle,
                    A: that.area,
                    area: that.area,
                    dist: that.dist,
                    R: that.radius,
                    radius: that.radius,
                    erf: Mat.erf,
                    erfc: Mat.erfc,
                    erfi: Mat.erfi,
                    factorial: Mat.factorial,
                    gcd: Mat.gcd,
                    lb: Mat.log2,
                    lcm: Mat.lcm,
                    ld: Mat.log2,
                    lg: Mat.log10,
                    ln: Math.log,
                    log: Mat.log,
                    log10: Mat.log10,
                    log2: Mat.log2,
                    ndtr: Mat.ndtr,
                    ndtri: Mat.ndtri,
                    nthroot: Mat.nthroot,
                    pow: Mat.pow,
                    rad: Geometry.rad,
                    ratpow: Mat.ratpow,
                    trunc: Type.trunc,
                    sinh: Mat.sinh,

                    randint: that.randint,

                    IfThen: that.ifthen,
                    'import': that.importModule,
                    'use': that.use,
                    'remove': that.del,
                    '$': that.getElementById,
                    getName: that.getName,
                    name: that.getName,
                    '$board': that.board,
                    '$log': that.log
                };

            // special scopes for factorial, deg, and rad
            builtIn.rad.sc = Geometry;
            builtIn.deg.sc = Geometry;
            builtIn.factorial.sc = Mat;

            // set the javascript equivalent for the builtIns
            // some of the anonymous functions should be replaced by global methods later on
            // EULER and PI don't get a source attribute - they will be lost anyways and apparently
            // some browser will throw an exception when a property is assigned to a primitive value.
            builtIn.X.src = '$jc$.X';
            builtIn.Y.src = '$jc$.Y';
            builtIn.V.src = '$jc$.V';
            builtIn.L.src = '$jc$.L';

            builtIn.acosh.src = 'JXG.Math.acosh';
            builtIn.acot.src = 'JXG.Math.acot';
            builtIn.asinh.src = 'JXG.Math.asinh';
            builtIn.binomial.src = 'JXG.Math.binomial';
            builtIn.cbrt.src = 'JXG.Math.cbrt';
            builtIn.cot.src = 'JXG.Math.cot';
            builtIn.cosh.src = 'JXG.Math.cosh';
            builtIn.deg.src = 'JXG.Math.Geometry.trueAngle';
            builtIn.erf.src = 'JXG.Math.erf';
            builtIn.erfc.src = 'JXG.Math.erfc';
            builtIn.erfi.src = 'JXG.Math.erfi';
            builtIn.A.src = '$jc$.area';
            builtIn.area.src = '$jc$.area';
            builtIn.dist.src = '$jc$.dist';
            builtIn.R.src = '$jc$.radius';
            builtIn.radius.src = '$jc$.radius';
            builtIn.factorial.src = 'JXG.Math.factorial';
            builtIn.gcd.src = 'JXG.Math.gcd';
            builtIn.lb.src = 'JXG.Math.log2';
            builtIn.lcm.src = 'JXG.Math.lcm';
            builtIn.ld.src = 'JXG.Math.log2';
            builtIn.lg.src = 'JXG.Math.log10';
            builtIn.ln.src = 'Math.log';
            builtIn.log.src = 'JXG.Math.log';
            builtIn.log10.src = 'JXG.Math.log10';
            builtIn.log2.src = 'JXG.Math.log2';
            builtIn.ndtr.src = 'JXG.Math.ndtr';
            builtIn.ndtri.src = 'JXG.Math.ndtri';
            builtIn.nthroot.src = 'JXG.Math.nthroot';
            builtIn.pow.src = 'JXG.Math.pow';
            builtIn.rad.src = 'JXG.Math.Geometry.rad';
            builtIn.ratpow.src = 'JXG.Math.ratpow';
            builtIn.trunc.src = 'JXG.trunc';
            builtIn.sinh.src = 'JXG.Math.sinh';

            builtIn.randint.src = '$jc$.randint';

            builtIn['import'].src = '$jc$.importModule';
            builtIn.use.src = '$jc$.use';
            builtIn.remove.src = '$jc$.del';
            builtIn.IfThen.src = '$jc$.ifthen';
            // usually unused, see node_op > op_execfun
            builtIn.$.src = '(function (n) { return $jc$.board.select(n); })';
            builtIn.getName.src = '$jc$.getName';
            builtIn.name.src = '$jc$.getName';
            if (builtIn.$board) {
                builtIn.$board.src = '$jc$.board';
            }
            builtIn.$log.src = '$jc$.log';

            return builtIn;
        },

        /**
         * Returns information about the possible functions and constants.
         * @returns {Object}
         */
        getPossibleOperands: function () {
            var FORBIDDEN = ['E'],
                jessiecode = this.defineBuiltIn(),
                math = Math,
                jc, ma, merge,
                i, j, p, len, e,
                funcs, funcsJC, consts, operands,
                sort, pack;

            sort = function (a, b) {
                return a.toLowerCase().localeCompare(b.toLowerCase());
            };

            pack = function (name, origin) {
                var that = null;

                if (origin === 'jc') that = jessiecode[name];
                else if (origin === 'Math') that = math[name];
                else return;

                if (FORBIDDEN.includes(name)) {
                    return;
                } else if (JXG.isFunction(that)) {
                    return {
                        name: name,
                        type: 'function',
                        numParams: that.length,
                        origin: origin,
                    };
                } else if (JXG.isNumber(that)) {
                    return {
                        name: name,
                        type: 'constant',
                        value: that,
                        origin: origin,
                    };
                } else if (that !== undefined) {
                    console.error('undefined type', that);
                }
            };

            jc = Object.getOwnPropertyNames(jessiecode).sort(sort);
            ma = Object.getOwnPropertyNames(math).sort(sort);
            merge = [];
            i = 0;
            j = 0;

            while (i < jc.length || j < ma.length) {
                if (jc[i] === ma[j]) {
                    p = pack(ma[j], 'Math');
                    if (JXG.exists(p)) merge.push(p);
                    i++;
                    j++;
                } else if (!JXG.exists(ma[j]) || jc[i].toLowerCase().localeCompare(ma[j].toLowerCase()) < 0) {
                    p = pack(jc[i], 'jc');
                    if (JXG.exists(p)) merge.push(p);
                    i++;
                } else {
                    p = pack(ma[j], 'Math');
                    if (JXG.exists(p)) merge.push(p);
                    j++;
                }
            }

            funcs = [];
            funcsJC = [];
            consts = [];
            operands = {};
            len = merge.length;
            for (i = 0; i < len; i++) {
                e = merge[i];
                switch (e.type) {
                    case 'function':
                        funcs.push(e.name);
                        if (e.origin === 'jc')
                            funcsJC.push(e.name);
                        break;
                    case 'constant':
                        consts.push(e.name);
                        break;
                }
                operands[e.name] = e;
            }

            return {
                all: operands,
                list: merge,
                functions: funcs,
                functions_jessiecode: funcsJC,
                constants: consts,
            };
        },

        /**
         * Output a debugging message. Uses debug console, if available. Otherwise an HTML element with the
         * id "debug" and an innerHTML property is used.
         * @param {String} log
         * @private
         */
        _debug: function (log) {
            if (typeof console === 'object') {
                console.log(log);
            } else if (Env.isBrowser && document && document.getElementById('debug') !== null) {
                document.getElementById('debug').innerHTML += log + '<br />';
            }
        },

        /**
         * Throws an exception with the given error message.
         * @param {String} msg Error message
         */
        _error: function (msg) {
            var e = new Error('Error(' + this.line + '): ' + msg);
            e.line = this.line;
            throw e;
        },

        /**
         * Output a warning message using {@link JXG#debug} and precedes the message with "Warning: ".
         * @param {String} msg
         */
        _warn: function (msg) {
            if (typeof console === 'object') {
                console.log('Warning(' + this.line + '): ' + msg);
            } else if (Env.isBrowser && document && document.getElementById(this.warnLog) !== null) {
                document.getElementById(this.warnLog).innerHTML += 'Warning(' + this.line + '): ' + msg + '<br />';
            }
        },

        _log: function (msg) {
            if (typeof window !== 'object' && typeof self === 'object' && self.postMessage) {
                self.postMessage({type: 'log', msg: 'Log: ' + msg.toString()});
            } else {
                console.log('Log: ', arguments);
            }
        }

    });

/* parser generated by jison 0.4.18 */
/*
  Returns a Parser object of the following structure:

  Parser: {
    yy: {}
  }

  Parser.prototype: {
    yy: {},
    trace: function(),
    symbols_: {associative list: name ==> number},
    terminals_: {associative list: number ==> name},
    productions_: [...],
    performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
    table: [...],
    defaultActions: {...},
    parseError: function(str, hash),
    parse: function(input),

    lexer: {
        EOF: 1,
        parseError: function(str, hash),
        setInput: function(input),
        input: function(),
        unput: function(str),
        more: function(),
        less: function(n),
        pastInput: function(),
        upcomingInput: function(),
        showPosition: function(),
        test_match: function(regex_match_array, rule_index),
        next: function(),
        lex: function(),
        begin: function(condition),
        popState: function(),
        _currentRules: function(),
        topState: function(),
        pushState: function(condition),

        options: {
            ranges: boolean           (optional: true ==> token location info will include a .range[] member)
            flex: boolean             (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
            backtrack_lexer: boolean  (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
        },

        performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
        rules: [...],
        conditions: {associative list: name ==> set},
    }
  }


  token location info (@$, _$, etc.): {
    first_line: n,
    last_line: n,
    first_column: n,
    last_column: n,
    range: [start_number, end_number]       (where the numbers are indexes into the input string, regular zero-based)
  }


  the parseError function receives a 'hash' object with these members for lexer and parser errors: {
    text:        (matched text)
    token:       (the produced terminal token, if any)
    line:        (yylineno)
  }
  while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
    loc:         (yylloc)
    expected:    (string describing the set of expected tokens)
    recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
  }
*/
var parser = (function(){
var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[2,14],$V1=[1,13],$V2=[1,37],$V3=[1,14],$V4=[1,15],$V5=[1,21],$V6=[1,16],$V7=[1,17],$V8=[1,33],$V9=[1,18],$Va=[1,19],$Vb=[1,12],$Vc=[1,59],$Vd=[1,60],$Ve=[1,58],$Vf=[1,46],$Vg=[1,48],$Vh=[1,49],$Vi=[1,50],$Vj=[1,51],$Vk=[1,52],$Vl=[1,53],$Vm=[1,54],$Vn=[1,45],$Vo=[1,38],$Vp=[1,39],$Vq=[5,7,8,14,15,16,17,19,20,21,23,26,27,50,51,58,65,74,75,76,77,78,79,80,82,91,93],$Vr=[5,7,8,12,14,15,16,17,19,20,21,23,26,27,50,51,58,65,74,75,76,77,78,79,80,82,91,93],$Vs=[8,10,16,32,34,35,37,39,41,42,43,45,46,47,48,50,51,53,54,55,57,64,65,66,83,86],$Vt=[2,48],$Vu=[1,72],$Vv=[10,16,32,34,35,37,39,41,42,43,45,46,47,48,50,51,53,54,55,57,66,83,86],$Vw=[1,78],$Vx=[8,10,16,32,34,35,37,41,42,43,45,46,47,48,50,51,53,54,55,57,64,65,66,83,86],$Vy=[1,82],$Vz=[8,10,16,32,34,35,37,39,45,46,47,48,50,51,53,54,55,57,64,65,66,83,86],$VA=[1,83],$VB=[1,84],$VC=[1,85],$VD=[8,10,16,32,34,35,37,39,41,42,43,50,51,53,54,55,57,64,65,66,83,86],$VE=[1,89],$VF=[1,90],$VG=[1,91],$VH=[1,92],$VI=[1,97],$VJ=[8,10,16,32,34,35,37,39,41,42,43,45,46,47,48,53,54,55,57,64,65,66,83,86],$VK=[1,103],$VL=[1,104],$VM=[8,10,16,32,34,35,37,39,41,42,43,45,46,47,48,50,51,57,64,65,66,83,86],$VN=[1,105],$VO=[1,106],$VP=[1,107],$VQ=[1,126],$VR=[1,139],$VS=[83,86],$VT=[1,150],$VU=[10,66,86],$VV=[8,10,16,20,32,34,35,37,39,41,42,43,45,46,47,48,50,51,53,54,55,57,64,65,66,82,83,86],$VW=[1,167],$VX=[10,86];
var parser = {trace: function trace () { },
yy: {},
symbols_: {"error":2,"Program":3,"StatementList":4,"EOF":5,"IfStatement":6,"IF":7,"(":8,"Expression":9,")":10,"Statement":11,"ELSE":12,"LoopStatement":13,"WHILE":14,"FOR":15,";":16,"DO":17,"UnaryStatement":18,"USE":19,"IDENTIFIER":20,"DELETE":21,"ReturnStatement":22,"RETURN":23,"EmptyStatement":24,"StatementBlock":25,"{":26,"}":27,"ExpressionStatement":28,"AssignmentExpression":29,"ConditionalExpression":30,"LeftHandSideExpression":31,"=":32,"LogicalORExpression":33,"?":34,":":35,"LogicalANDExpression":36,"||":37,"EqualityExpression":38,"&&":39,"RelationalExpression":40,"==":41,"!=":42,"~=":43,"AdditiveExpression":44,"<":45,">":46,"<=":47,">=":48,"MultiplicativeExpression":49,"+":50,"-":51,"UnaryExpression":52,"*":53,"/":54,"%":55,"ExponentExpression":56,"^":57,"!":58,"MemberExpression":59,"CallExpression":60,"PrimaryExpression":61,"FunctionExpression":62,"MapExpression":63,".":64,"[":65,"]":66,"BasicLiteral":67,"ObjectLiteral":68,"ArrayLiteral":69,"NullLiteral":70,"BooleanLiteral":71,"StringLiteral":72,"NumberLiteral":73,"NULL":74,"TRUE":75,"FALSE":76,"STRING":77,"NUMBER":78,"NAN":79,"INFINITY":80,"ElementList":81,"<<":82,">>":83,"PropertyList":84,"Property":85,",":86,"PropertyName":87,"Arguments":88,"AttributeList":89,"Attribute":90,"FUNCTION":91,"ParameterDefinitionList":92,"MAP":93,"->":94,"$accept":0,"$end":1},
terminals_: {2:"error",5:"EOF",7:"IF",8:"(",10:")",12:"ELSE",14:"WHILE",15:"FOR",16:";",17:"DO",19:"USE",20:"IDENTIFIER",21:"DELETE",23:"RETURN",26:"{",27:"}",32:"=",34:"?",35:":",37:"||",39:"&&",41:"==",42:"!=",43:"~=",45:"<",46:">",47:"<=",48:">=",50:"+",51:"-",53:"*",54:"/",55:"%",57:"^",58:"!",64:".",65:"[",66:"]",74:"NULL",75:"TRUE",76:"FALSE",77:"STRING",78:"NUMBER",79:"NAN",80:"INFINITY",82:"<<",83:">>",86:",",91:"FUNCTION",93:"MAP",94:"->"},
productions_: [0,[3,2],[6,5],[6,7],[13,5],[13,9],[13,7],[18,2],[18,2],[22,2],[22,3],[24,1],[25,3],[4,2],[4,0],[11,1],[11,1],[11,1],[11,1],[11,1],[11,1],[11,1],[28,2],[9,1],[29,1],[29,3],[30,1],[30,5],[33,1],[33,3],[36,1],[36,3],[38,1],[38,3],[38,3],[38,3],[40,1],[40,3],[40,3],[40,3],[40,3],[44,1],[44,3],[44,3],[49,1],[49,3],[49,3],[49,3],[56,1],[56,3],[52,1],[52,2],[52,2],[52,2],[31,1],[31,1],[59,1],[59,1],[59,1],[59,3],[59,4],[61,1],[61,1],[61,1],[61,1],[61,3],[67,1],[67,1],[67,1],[67,1],[70,1],[71,1],[71,1],[72,1],[73,1],[73,1],[73,1],[69,2],[69,3],[68,2],[68,3],[84,1],[84,3],[85,3],[87,1],[87,1],[87,1],[60,2],[60,3],[60,2],[60,4],[60,3],[88,2],[88,3],[89,1],[89,3],[90,1],[90,1],[81,1],[81,3],[62,4],[62,5],[63,5],[63,6],[92,1],[92,3]],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
/* this == yyval */

var $0 = $$.length - 1;
switch (yystate) {
case 1:
 return $$[$0-1]; 
break;
case 2:
 this.$ = AST.createNode(lc(_$[$0-4]), 'node_op', 'op_if', $$[$0-2], $$[$0]); 
break;
case 3:
 this.$ = AST.createNode(lc(_$[$0-6]), 'node_op', 'op_if_else', $$[$0-4], $$[$0-2], $$[$0]); 
break;
case 4:
 this.$ = AST.createNode(lc(_$[$0-4]), 'node_op', 'op_while', $$[$0-2], $$[$0]); 
break;
case 5:
 this.$ = AST.createNode(lc(_$[$0-8]), 'node_op', 'op_for', $$[$0-6], $$[$0-4], $$[$0-2], $$[$0]); 
break;
case 6:
 this.$ = AST.createNode(lc(_$[$0-6]), 'node_op', 'op_do', $$[$0-5], $$[$0-2]); 
break;
case 7:
 this.$ = AST.createNode(lc(_$[$0-1]), 'node_op', 'op_use', $$[$0]); 
break;
case 8:
 this.$ = AST.createNode(lc(_$[$0-1]), 'node_op', 'op_delete', $$[$0]); 
break;
case 9:
 this.$ = AST.createNode(lc(_$[$0-1]), 'node_op', 'op_return', undefined); 
break;
case 10:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_return', $$[$0-1]); 
break;
case 11: case 14:
 this.$ = AST.createNode(lc(_$[$0]), 'node_op', 'op_none'); 
break;
case 12:
 this.$ = $$[$0-1]; this.$.needsBrackets = true; 
break;
case 13:
 this.$ = AST.createNode(lc(_$[$0-1]), 'node_op', 'op_none', $$[$0-1], $$[$0]); 
break;
case 15: case 16: case 17: case 18: case 19: case 20: case 21: case 23: case 24: case 26: case 28: case 30: case 32: case 36: case 41: case 44: case 48: case 50: case 52: case 54: case 55: case 56: case 58: case 62: case 81: case 84: case 85: case 86:
 this.$ = $$[$0]; 
break;
case 22: case 65: case 93:
 this.$ = $$[$0-1]; 
break;
case 25:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_assign', $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 27:
 this.$ = AST.createNode(lc(_$[$0-4]), 'node_op', 'op_conditional', $$[$0-4], $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 29:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_or', $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 31:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_and', $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 33:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_eq', $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 34:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_neq', $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 35:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_approx', $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 37:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_lt', $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 38:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_gt', $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 39:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_leq', $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 40:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_geq', $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 42:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_add', $$[$0-2], $$[$0]); this.$.isMath = true; 
break;
case 43:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_sub', $$[$0-2], $$[$0]); this.$.isMath = true; 
break;
case 45:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_mul', $$[$0-2], $$[$0]); this.$.isMath = true; 
break;
case 46:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_div', $$[$0-2], $$[$0]); this.$.isMath = true; 
break;
case 47:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_mod', $$[$0-2], $$[$0]); this.$.isMath = true; 
break;
case 49:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_exp', $$[$0-2], $$[$0]); this.$.isMath = true; 
break;
case 51:
 this.$ = AST.createNode(lc(_$[$0-1]), 'node_op', 'op_not', $$[$0]); this.$.isMath = false; 
break;
case 53:
 this.$ = AST.createNode(lc(_$[$0-1]), 'node_op', 'op_neg', $$[$0]); this.$.isMath = true; 
break;
case 57: case 63: case 64: case 66: case 67: case 68: case 97:
 this.$ = $$[$0]; this.$.isMath = false; 
break;
case 59: case 91:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_property', $$[$0-2], $$[$0]); this.$.isMath = true; 
break;
case 60: case 90:
 this.$ = AST.createNode(lc(_$[$0-3]), 'node_op', 'op_extvalue', $$[$0-3], $$[$0-1]); this.$.isMath = true; 
break;
case 61:
 this.$ = AST.createNode(lc(_$[$0]), 'node_var', $$[$0]); 
break;
case 69:
 this.$ = $$[$0]; this.$.isMath = true; 
break;
case 70:
 this.$ = AST.createNode(lc(_$[$0]), 'node_const', null); 
break;
case 71:
 this.$ = AST.createNode(lc(_$[$0]), 'node_const_bool', true); 
break;
case 72:
 this.$ = AST.createNode(lc(_$[$0]), 'node_const_bool', false); 
break;
case 73:
 this.$ = AST.createNode(lc(_$[$0]), 'node_str', $$[$0].substring(1, $$[$0].length - 1)); 
break;
case 74:
 this.$ = AST.createNode(lc(_$[$0]), 'node_const', parseFloat($$[$0])); 
break;
case 75:
 this.$ = AST.createNode(lc(_$[$0]), 'node_const', NaN); 
break;
case 76:
 this.$ = AST.createNode(lc(_$[$0]), 'node_const', Infinity); 
break;
case 77:
 this.$ = AST.createNode(lc(_$[$0-1]), 'node_op', 'op_array', []); 
break;
case 78:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_array', $$[$0-1]); 
break;
case 79:
 this.$ = AST.createNode(lc(_$[$0-1]), 'node_op', 'op_emptyobject', {}); this.$.needsBrackets = true; 
break;
case 80:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_proplst_val', $$[$0-1]); this.$.needsBrackets = true; 
break;
case 82:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_proplst', $$[$0-2], $$[$0]); 
break;
case 83:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_prop', $$[$0-2], $$[$0]); 
break;
case 87: case 89:
 this.$ = AST.createNode(lc(_$[$0-1]), 'node_op', 'op_execfun', $$[$0-1], $$[$0]); this.$.isMath = true; 
break;
case 88:
 this.$ = AST.createNode(lc(_$[$0-2]), 'node_op', 'op_execfun', $$[$0-2], $$[$0-1], $$[$0], true); this.$.isMath = false; 
break;
case 92:
 this.$ = []; 
break;
case 94: case 98: case 104:
 this.$ = [$$[$0]]; 
break;
case 95: case 99: case 105:
 this.$ = $$[$0-2].concat($$[$0]); 
break;
case 96:
 this.$ = AST.createNode(lc(_$[$0]), 'node_var', $$[$0]); this.$.isMath = true; 
break;
case 100:
 this.$ = AST.createNode(lc(_$[$0-3]), 'node_op', 'op_function', [], $$[$0]); this.$.isMath = false; 
break;
case 101:
 this.$ = AST.createNode(lc(_$[$0-4]), 'node_op', 'op_function', $$[$0-2], $$[$0]); this.$.isMath = false; 
break;
case 102:
 this.$ = AST.createNode(lc(_$[$0-4]), 'node_op', 'op_map', [], $$[$0]); 
break;
case 103:
 this.$ = AST.createNode(lc(_$[$0-5]), 'node_op', 'op_map', $$[$0-3], $$[$0]); 
break;
}
},
table: [o([5,7,8,14,15,16,17,19,20,21,23,26,50,51,58,65,74,75,76,77,78,79,80,82,91,93],$V0,{3:1,4:2}),{1:[3]},{5:[1,3],6:6,7:$V1,8:$V2,9:20,11:4,13:7,14:$V3,15:$V4,16:$V5,17:$V6,18:8,19:$V7,20:$V8,21:$V9,22:9,23:$Va,24:11,25:5,26:$Vb,28:10,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{1:[2,1]},o($Vq,[2,13]),o($Vr,[2,15]),o($Vr,[2,16]),o($Vr,[2,17]),o($Vr,[2,18]),o($Vr,[2,19]),o($Vr,[2,20]),o($Vr,[2,21]),o([7,8,14,15,16,17,19,20,21,23,26,27,50,51,58,65,74,75,76,77,78,79,80,82,91,93],$V0,{4:61}),{8:[1,62]},{8:[1,63]},{8:[1,64]},{6:6,7:$V1,8:$V2,9:20,11:65,13:7,14:$V3,15:$V4,16:$V5,17:$V6,18:8,19:$V7,20:$V8,21:$V9,22:9,23:$Va,24:11,25:5,26:$Vb,28:10,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{20:[1,66]},{20:[1,67]},{8:$V2,9:69,16:[1,68],20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{16:[1,70]},o($Vr,[2,11]),o($Vs,[2,23]),o($Vs,[2,24]),o([8,10,16,34,35,37,39,41,42,43,45,46,47,48,50,51,53,54,55,64,65,66,83,86],$Vt,{32:[1,71],57:$Vu}),o([8,10,16,32,35,39,41,42,43,45,46,47,48,50,51,53,54,55,57,64,65,66,83,86],[2,26],{34:[1,73],37:[1,74]}),o($Vv,[2,54],{88:77,8:$Vw,64:[1,75],65:[1,76]}),o($Vv,[2,55],{88:79,8:$Vw,64:[1,81],65:[1,80]}),o($Vx,[2,28],{39:$Vy}),o($Vs,[2,56]),o($Vs,[2,57]),o($Vs,[2,58]),o($Vz,[2,30],{41:$VA,42:$VB,43:$VC}),o($Vs,[2,61]),o($Vs,[2,62]),o($Vs,[2,63]),o($Vs,[2,64]),{8:$V2,9:86,20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:[1,87]},{8:[1,88]},o($VD,[2,32],{45:$VE,46:$VF,47:$VG,48:$VH}),o($Vs,[2,66]),o($Vs,[2,67]),o($Vs,[2,68]),o($Vs,[2,69]),{20:$VI,72:98,73:99,77:$Vj,78:$Vk,79:$Vl,80:$Vm,83:[1,93],84:94,85:95,87:96},{8:$V2,20:$V8,29:102,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,66:[1,100],67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,81:101,82:$Vn,91:$Vo,93:$Vp},o($VJ,[2,36],{50:$VK,51:$VL}),o($Vs,[2,70]),o($Vs,[2,71]),o($Vs,[2,72]),o($Vs,[2,73]),o($Vs,[2,74]),o($Vs,[2,75]),o($Vs,[2,76]),o($VM,[2,41],{53:$VN,54:$VO,55:$VP}),o($Vs,[2,44]),o($Vs,[2,50]),{8:$V2,20:$V8,31:109,50:$Vc,51:$Vd,52:108,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,50:$Vc,51:$Vd,52:110,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,50:$Vc,51:$Vd,52:111,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{6:6,7:$V1,8:$V2,9:20,11:4,13:7,14:$V3,15:$V4,16:$V5,17:$V6,18:8,19:$V7,20:$V8,21:$V9,22:9,23:$Va,24:11,25:5,26:$Vb,27:[1,112],28:10,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,9:113,20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,9:114,20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,9:115,20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{14:[1,116]},o($Vr,[2,7]),o($Vr,[2,8]),o($Vr,[2,9]),{16:[1,117]},o($Vr,[2,22]),{8:$V2,20:$V8,29:118,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,50:$Vc,51:$Vd,52:119,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,29:120,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,36:121,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{20:[1,122]},{8:$V2,9:123,20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},o($Vs,[2,87],{89:124,90:125,68:127,20:$VQ,82:$Vn}),{8:$V2,10:[1,128],20:$V8,29:102,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,81:129,82:$Vn,91:$Vo,93:$Vp},o($Vs,[2,89]),{8:$V2,9:130,20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{20:[1,131]},{8:$V2,20:$V8,31:109,38:132,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,40:133,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,40:134,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,40:135,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{10:[1,136]},{10:[1,137],20:$VR,92:138},{10:[1,140],20:$VR,92:141},{8:$V2,20:$V8,31:109,44:142,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,44:143,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,44:144,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,44:145,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},o($Vs,[2,79]),{83:[1,146],86:[1,147]},o($VS,[2,81]),{35:[1,148]},{35:[2,84]},{35:[2,85]},{35:[2,86]},o($Vs,[2,77]),{66:[1,149],86:$VT},o($VU,[2,98]),{8:$V2,20:$V8,31:109,49:151,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,49:152,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,50:$Vc,51:$Vd,52:153,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,50:$Vc,51:$Vd,52:154,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,31:109,50:$Vc,51:$Vd,52:155,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},o($Vs,[2,51]),o([8,10,16,32,34,35,37,39,41,42,43,45,46,47,48,50,51,53,54,55,64,65,66,83,86],$Vt,{57:$Vu}),o($Vs,[2,52]),o($Vs,[2,53]),o([5,7,8,10,12,14,15,16,17,19,20,21,23,26,27,32,34,35,37,39,41,42,43,45,46,47,48,50,51,53,54,55,57,58,64,65,66,74,75,76,77,78,79,80,82,83,86,91,93],[2,12]),{10:[1,156]},{10:[1,157]},{16:[1,158]},{8:[1,159]},o($Vr,[2,10]),o($Vs,[2,25]),o($Vs,[2,49]),{35:[1,160]},o($Vx,[2,29],{39:$Vy}),o($Vs,[2,59]),{66:[1,161]},o([8,10,16,32,34,35,37,39,41,42,43,45,46,47,48,50,51,53,54,55,57,64,65,66,83],[2,88],{86:[1,162]}),o($Vs,[2,94]),o($Vs,[2,96]),o($Vs,[2,97]),o($VV,[2,92]),{10:[1,163],86:$VT},{66:[1,164]},o($Vs,[2,91]),o($Vz,[2,31],{41:$VA,42:$VB,43:$VC}),o($VD,[2,33],{45:$VE,46:$VF,47:$VG,48:$VH}),o($VD,[2,34],{45:$VE,46:$VF,47:$VG,48:$VH}),o($VD,[2,35],{45:$VE,46:$VF,47:$VG,48:$VH}),o($Vs,[2,65]),{25:165,26:$Vb},{10:[1,166],86:$VW},o($VX,[2,104]),{94:[1,168]},{10:[1,169],86:$VW},o($VJ,[2,37],{50:$VK,51:$VL}),o($VJ,[2,38],{50:$VK,51:$VL}),o($VJ,[2,39],{50:$VK,51:$VL}),o($VJ,[2,40],{50:$VK,51:$VL}),o($Vs,[2,80]),{20:$VI,72:98,73:99,77:$Vj,78:$Vk,79:$Vl,80:$Vm,85:170,87:96},{8:$V2,20:$V8,29:171,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},o($Vs,[2,78]),{8:$V2,20:$V8,29:172,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},o($VM,[2,42],{53:$VN,54:$VO,55:$VP}),o($VM,[2,43],{53:$VN,54:$VO,55:$VP}),o($Vs,[2,45]),o($Vs,[2,46]),o($Vs,[2,47]),{6:6,7:$V1,8:$V2,9:20,11:173,13:7,14:$V3,15:$V4,16:$V5,17:$V6,18:8,19:$V7,20:$V8,21:$V9,22:9,23:$Va,24:11,25:5,26:$Vb,28:10,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{6:6,7:$V1,8:$V2,9:20,11:174,13:7,14:$V3,15:$V4,16:$V5,17:$V6,18:8,19:$V7,20:$V8,21:$V9,22:9,23:$Va,24:11,25:5,26:$Vb,28:10,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,9:175,20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,9:176,20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,20:$V8,29:177,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},o($Vs,[2,60]),{20:$VQ,68:127,82:$Vn,90:178},o($VV,[2,93]),o($Vs,[2,90]),o($Vs,[2,100]),{25:179,26:$Vb},{20:[1,180]},{8:$V2,9:181,20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{94:[1,182]},o($VS,[2,82]),o($VS,[2,83]),o($VU,[2,99]),o($Vq,[2,2],{12:[1,183]}),o($Vr,[2,4]),{16:[1,184]},{10:[1,185]},o($Vs,[2,27]),o($Vs,[2,95]),o($Vs,[2,101]),o($VX,[2,105]),o($Vs,[2,102]),{8:$V2,9:186,20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{6:6,7:$V1,8:$V2,9:20,11:187,13:7,14:$V3,15:$V4,16:$V5,17:$V6,18:8,19:$V7,20:$V8,21:$V9,22:9,23:$Va,24:11,25:5,26:$Vb,28:10,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{8:$V2,9:188,20:$V8,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},{16:[1,189]},o($Vs,[2,103]),o($Vr,[2,3]),{10:[1,190]},o($Vr,[2,6]),{6:6,7:$V1,8:$V2,9:20,11:191,13:7,14:$V3,15:$V4,16:$V5,17:$V6,18:8,19:$V7,20:$V8,21:$V9,22:9,23:$Va,24:11,25:5,26:$Vb,28:10,29:22,30:23,31:24,33:25,36:28,38:32,40:40,44:47,49:55,50:$Vc,51:$Vd,52:56,56:57,58:$Ve,59:26,60:27,61:29,62:30,63:31,65:$Vf,67:34,68:35,69:36,70:41,71:42,72:43,73:44,74:$Vg,75:$Vh,76:$Vi,77:$Vj,78:$Vk,79:$Vl,80:$Vm,82:$Vn,91:$Vo,93:$Vp},o($Vr,[2,5])],
defaultActions: {3:[2,1],97:[2,84],98:[2,85],99:[2,86]},
parseError: function parseError (str, hash) {
    if (hash.recoverable) {
        this.trace(str);
    } else {
        var error = new Error(str);
        error.hash = hash;
        throw error;
    }
},
parse: function parse(input) {
    var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
    var args = lstack.slice.call(arguments, 1);
    var lexer = Object.create(this.lexer);
    var sharedState = { yy: {} };
    for (var k in this.yy) {
        if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
            sharedState.yy[k] = this.yy[k];
        }
    }
    lexer.setInput(input, sharedState.yy);
    sharedState.yy.lexer = lexer;
    sharedState.yy.parser = this;
    if (typeof lexer.yylloc == 'undefined') {
        lexer.yylloc = {};
    }
    var yyloc = lexer.yylloc;
    lstack.push(yyloc);
    var ranges = lexer.options && lexer.options.ranges;
    if (typeof sharedState.yy.parseError === 'function') {
        this.parseError = sharedState.yy.parseError;
    } else {
        this.parseError = Object.getPrototypeOf(this).parseError;
    }
    function popStack(n) {
        stack.length = stack.length - 2 * n;
        vstack.length = vstack.length - n;
        lstack.length = lstack.length - n;
    }
    _token_stack:
        var lex = function () {
            var token;
            token = lexer.lex() || EOF;
            if (typeof token !== 'number') {
                token = self.symbols_[token] || token;
            }
            return token;
        };
    var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
    while (true) {
        state = stack[stack.length - 1];
        if (this.defaultActions[state]) {
            action = this.defaultActions[state];
        } else {
            if (symbol === null || typeof symbol == 'undefined') {
                symbol = lex();
            }
            action = table[state] && table[state][symbol];
        }
                    if (typeof action === 'undefined' || !action.length || !action[0]) {
                var errStr = '';
                expected = [];
                for (p in table[state]) {
                    if (this.terminals_[p] && p > TERROR) {
                        expected.push('\'' + this.terminals_[p] + '\'');
                    }
                }
                if (lexer.showPosition) {
                    errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
                } else {
                    errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
                }
                this.parseError(errStr, {
                    text: lexer.match,
                    token: this.terminals_[symbol] || symbol,
                    line: lexer.yylineno,
                    loc: yyloc,
                    expected: expected
                });
            }
        if (action[0] instanceof Array && action.length > 1) {
            throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
        }
        switch (action[0]) {
        case 1:
            stack.push(symbol);
            vstack.push(lexer.yytext);
            lstack.push(lexer.yylloc);
            stack.push(action[1]);
            symbol = null;
            if (!preErrorSymbol) {
                yyleng = lexer.yyleng;
                yytext = lexer.yytext;
                yylineno = lexer.yylineno;
                yyloc = lexer.yylloc;
                if (recovering > 0) {
                    recovering--;
                }
            } else {
                symbol = preErrorSymbol;
                preErrorSymbol = null;
            }
            break;
        case 2:
            len = this.productions_[action[1]][1];
            yyval.$ = vstack[vstack.length - len];
            yyval._$ = {
                first_line: lstack[lstack.length - (len || 1)].first_line,
                last_line: lstack[lstack.length - 1].last_line,
                first_column: lstack[lstack.length - (len || 1)].first_column,
                last_column: lstack[lstack.length - 1].last_column
            };
            if (ranges) {
                yyval._$.range = [
                    lstack[lstack.length - (len || 1)].range[0],
                    lstack[lstack.length - 1].range[1]
                ];
            }
            r = this.performAction.apply(yyval, [
                yytext,
                yyleng,
                yylineno,
                sharedState.yy,
                action[1],
                vstack,
                lstack
            ].concat(args));
            if (typeof r !== 'undefined') {
                return r;
            }
            if (len) {
                stack = stack.slice(0, -1 * len * 2);
                vstack = vstack.slice(0, -1 * len);
                lstack = lstack.slice(0, -1 * len);
            }
            stack.push(this.productions_[action[1]][0]);
            vstack.push(yyval.$);
            lstack.push(yyval._$);
            newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
            stack.push(newState);
            break;
        case 3:
            return true;
        }
    }
    return true;
}};


    var AST = {
        node: function (type, value, children) {
            return {
                type: type,
                value: value,
                children: children
            };
        },

        createNode: function (pos, type, value, children) {
            var i,
                n = this.node(type, value, []);

            for (i = 3; i < arguments.length; i++) {
                n.children.push(arguments[i]);
            }

            n.line = pos[0];
            n.col = pos[1];
            n.eline = pos[2];
            n.ecol = pos[3];

            return n;
        }
    };

    var lc = function (lc1) {
        return [lc1.first_line, lc1.first_column, lc1.last_line, lc1.last_column];
    };

/* generated by jison-lex 0.3.4 */
var lexer = (function(){
var lexer = ({

EOF:1,

parseError:function parseError(str, hash) {
        if (this.yy.parser) {
            this.yy.parser.parseError(str, hash);
        } else {
            throw new Error(str);
        }
    },

// resets the lexer, sets new input
setInput:function (input, yy) {
        this.yy = yy || this.yy || {};
        this._input = input;
        this._more = this._backtrack = this.done = false;
        this.yylineno = this.yyleng = 0;
        this.yytext = this.matched = this.match = '';
        this.conditionStack = ['INITIAL'];
        this.yylloc = {
            first_line: 1,
            first_column: 0,
            last_line: 1,
            last_column: 0
        };
        if (this.options.ranges) {
            this.yylloc.range = [0,0];
        }
        this.offset = 0;
        return this;
    },

// consumes and returns one char from the input
input:function () {
        var ch = this._input[0];
        this.yytext += ch;
        this.yyleng++;
        this.offset++;
        this.match += ch;
        this.matched += ch;
        var lines = ch.match(/(?:\r\n?|\n).*/g);
        if (lines) {
            this.yylineno++;
            this.yylloc.last_line++;
        } else {
            this.yylloc.last_column++;
        }
        if (this.options.ranges) {
            this.yylloc.range[1]++;
        }

        this._input = this._input.slice(1);
        return ch;
    },

// unshifts one char (or a string) into the input
unput:function (ch) {
        var len = ch.length;
        var lines = ch.split(/(?:\r\n?|\n)/g);

        this._input = ch + this._input;
        this.yytext = this.yytext.substr(0, this.yytext.length - len);
        //this.yyleng -= len;
        this.offset -= len;
        var oldLines = this.match.split(/(?:\r\n?|\n)/g);
        this.match = this.match.substr(0, this.match.length - 1);
        this.matched = this.matched.substr(0, this.matched.length - 1);

        if (lines.length - 1) {
            this.yylineno -= lines.length - 1;
        }
        var r = this.yylloc.range;

        this.yylloc = {
            first_line: this.yylloc.first_line,
            last_line: this.yylineno + 1,
            first_column: this.yylloc.first_column,
            last_column: lines ?
                (lines.length === oldLines.length ? this.yylloc.first_column : 0)
                 + oldLines[oldLines.length - lines.length].length - lines[0].length :
              this.yylloc.first_column - len
        };

        if (this.options.ranges) {
            this.yylloc.range = [r[0], r[0] + this.yyleng - len];
        }
        this.yyleng = this.yytext.length;
        return this;
    },

// When called from action, caches matched text and appends it on next action
more:function () {
        this._more = true;
        return this;
    },

// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
reject:function () {
        if (this.options.backtrack_lexer) {
            this._backtrack = true;
        } else {
            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
                text: "",
                token: null,
                line: this.yylineno
            });

        }
        return this;
    },

// retain first n characters of the match
less:function (n) {
        this.unput(this.match.slice(n));
    },

// displays already matched input, i.e. for error messages
pastInput:function () {
        var past = this.matched.substr(0, this.matched.length - this.match.length);
        return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
    },

// displays upcoming input, i.e. for error messages
upcomingInput:function () {
        var next = this.match;
        if (next.length < 20) {
            next += this._input.substr(0, 20-next.length);
        }
        return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
    },

// displays the character position where the lexing error occurred, i.e. for error messages
showPosition:function () {
        var pre = this.pastInput();
        var c = new Array(pre.length + 1).join("-");
        return pre + this.upcomingInput() + "\n" + c + "^";
    },

// test the lexed token: return FALSE when not a match, otherwise return token
test_match:function(match, indexed_rule) {
        var token,
            lines,
            backup;

        if (this.options.backtrack_lexer) {
            // save context
            backup = {
                yylineno: this.yylineno,
                yylloc: {
                    first_line: this.yylloc.first_line,
                    last_line: this.last_line,
                    first_column: this.yylloc.first_column,
                    last_column: this.yylloc.last_column
                },
                yytext: this.yytext,
                match: this.match,
                matches: this.matches,
                matched: this.matched,
                yyleng: this.yyleng,
                offset: this.offset,
                _more: this._more,
                _input: this._input,
                yy: this.yy,
                conditionStack: this.conditionStack.slice(0),
                done: this.done
            };
            if (this.options.ranges) {
                backup.yylloc.range = this.yylloc.range.slice(0);
            }
        }

        lines = match[0].match(/(?:\r\n?|\n).*/g);
        if (lines) {
            this.yylineno += lines.length;
        }
        this.yylloc = {
            first_line: this.yylloc.last_line,
            last_line: this.yylineno + 1,
            first_column: this.yylloc.last_column,
            last_column: lines ?
                         lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
                         this.yylloc.last_column + match[0].length
        };
        this.yytext += match[0];
        this.match += match[0];
        this.matches = match;
        this.yyleng = this.yytext.length;
        if (this.options.ranges) {
            this.yylloc.range = [this.offset, this.offset += this.yyleng];
        }
        this._more = false;
        this._backtrack = false;
        this._input = this._input.slice(match[0].length);
        this.matched += match[0];
        token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
        if (this.done && this._input) {
            this.done = false;
        }
        if (token) {
            return token;
        } else if (this._backtrack) {
            // recover context
            for (var k in backup) {
                this[k] = backup[k];
            }
            return false; // rule action called reject() implying the next rule should be tested instead.
        }
        return false;
    },

// return next match in input
next:function () {
        if (this.done) {
            return this.EOF;
        }
        if (!this._input) {
            this.done = true;
        }

        var token,
            match,
            tempMatch,
            index;
        if (!this._more) {
            this.yytext = '';
            this.match = '';
        }
        var rules = this._currentRules();
        for (var i = 0; i < rules.length; i++) {
            tempMatch = this._input.match(this.rules[rules[i]]);
            if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
                match = tempMatch;
                index = i;
                if (this.options.backtrack_lexer) {
                    token = this.test_match(tempMatch, rules[i]);
                    if (token !== false) {
                        return token;
                    } else if (this._backtrack) {
                        match = false;
                        continue; // rule action called reject() implying a rule MISmatch.
                    } else {
                        // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
                        return false;
                    }
                } else if (!this.options.flex) {
                    break;
                }
            }
        }
        if (match) {
            token = this.test_match(match, rules[index]);
            if (token !== false) {
                return token;
            }
            // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
            return false;
        }
        if (this._input === "") {
            return this.EOF;
        } else {
            return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
                text: "",
                token: null,
                line: this.yylineno
            });
        }
    },

// return next match that has a token
lex:function lex () {
        var r = this.next();
        if (r) {
            return r;
        } else {
            return this.lex();
        }
    },

// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
begin:function begin (condition) {
        this.conditionStack.push(condition);
    },

// pop the previously active lexer condition state off the condition stack
popState:function popState () {
        var n = this.conditionStack.length - 1;
        if (n > 0) {
            return this.conditionStack.pop();
        } else {
            return this.conditionStack[0];
        }
    },

// produce the lexer rule set which is active for the currently active lexer condition state
_currentRules:function _currentRules () {
        if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
            return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
        } else {
            return this.conditions["INITIAL"].rules;
        }
    },

// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
topState:function topState (n) {
        n = this.conditionStack.length - 1 - Math.abs(n || 0);
        if (n >= 0) {
            return this.conditionStack[n];
        } else {
            return "INITIAL";
        }
    },

// alias for begin(condition)
pushState:function pushState (condition) {
        this.begin(condition);
    },

// return the number of states currently on the stack
stateStackSize:function stateStackSize() {
        return this.conditionStack.length;
    },
options: {},
performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
var YYSTATE=YY_START;
switch($avoiding_name_collisions) {
case 0:/* ignore */
break;
case 1:return 78
break;
case 2:return 78
break;
case 3: return 77; 
break;
case 4: return 77; 
break;
case 5:/* ignore comment */
break;
case 6:/* ignore multiline comment */
break;
case 7:return 7
break;
case 8:return 12
break;
case 9:return 14
break;
case 10:return 17
break;
case 11:return 15
break;
case 12:return 91
break;
case 13:return 93
break;
case 14:return 19
break;
case 15:return 23
break;
case 16:return 21
break;
case 17:return 75
break;
case 18:return 76
break;
case 19:return 74
break;
case 20:return 80
break;
case 21:return 94
break;
case 22:return 94
break;
case 23:return 82
break;
case 24:return 83
break;
case 25:return 26
break;
case 26:return 27
break;
case 27:return 16
break;
case 28:return '#'
break;
case 29:return 34
break;
case 30:return 35
break;
case 31:return 79
break;
case 32:return 64
break;
case 33:return 65
break;
case 34:return 66
break;
case 35:return 8
break;
case 36:return 10
break;
case 37:return 58
break;
case 38:return 57
break;
case 39:return 53
break;
case 40:return 54
break;
case 41:return 55
break;
case 42:return 50
break;
case 43:return 51
break;
case 44:return 47
break;
case 45:return 45
break;
case 46:return 48
break;
case 47:return 46
break;
case 48:return 41
break;
case 49:return 43
break;
case 50:return 42
break;
case 51:return 39
break;
case 52:return 37
break;
case 53:return 32
break;
case 54:return 86
break;
case 55:return 5
break;
case 56:return 20
break;
case 57:return 'INVALID'
break;
}
},
rules: [/^(?:\s+)/,/^(?:[0-9]+\.[0-9]*|[0-9]*\.[0-9]+\b)/,/^(?:[0-9]+)/,/^(?:"(\\["]|[^"])*")/,/^(?:'(\\[']|[^'])*')/,/^(?:\/\/.*)/,/^(?:\/\*(.|\n|\r)*?\*\/)/,/^(?:if\b)/,/^(?:else\b)/,/^(?:while\b)/,/^(?:do\b)/,/^(?:for\b)/,/^(?:function\b)/,/^(?:map\b)/,/^(?:use\b)/,/^(?:return\b)/,/^(?:delete\b)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:Infinity\b)/,/^(?:->)/,/^(?:=>)/,/^(?:<<)/,/^(?:>>)/,/^(?:\{)/,/^(?:\})/,/^(?:;)/,/^(?:#)/,/^(?:\?)/,/^(?::)/,/^(?:NaN\b)/,/^(?:\.)/,/^(?:\[)/,/^(?:\])/,/^(?:\()/,/^(?:\))/,/^(?:!)/,/^(?:\^)/,/^(?:\*)/,/^(?:\/)/,/^(?:%)/,/^(?:\+)/,/^(?:-)/,/^(?:<=)/,/^(?:<)/,/^(?:>=)/,/^(?:>)/,/^(?:==)/,/^(?:~=)/,/^(?:!=)/,/^(?:&&)/,/^(?:\|\|)/,/^(?:=)/,/^(?:,)/,/^(?:$)/,/^(?:[A-Za-z_\$][A-Za-z0-9_]*)/,/^(?:.)/],
conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57],"inclusive":true}}
});
return lexer;
})();
parser.lexer = lexer;
function Parser () {
  this.yy = {};
}
Parser.prototype = parser;parser.Parser = Parser;
return new Parser;
})();


if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
exports.parser = parser;
exports.Parser = parser.Parser;
exports.parse = function () { return parser.parse.apply(parser, arguments); };
exports.main = function commonjsMain (args) {
    if (!args[1]) {
        console.log('Usage: '+args[0]+' FILE');
        process.exit(1);
    }
    var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8");
    return exports.parser.parse(source);
};
if (typeof module !== 'undefined' && require.main === module) {
  exports.main(process.argv.slice(1));
}
}
    // Work around an issue with browsers that don't support Object.getPrototypeOf()
    parser.yy.parseError = parser.parseError;

    return JXG.JessieCode;
});

/*
 Copyright 2008-2022
 Matthias Ehmann,
 Michael Gerhaeuser,
 Carsten Miller,
 Bianca Valentin,
 Alfred Wassermann,
 Peter Wilfahrt

 This file is part of JSXGraph.

 JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

 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 and
 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/type
 */

define('base/composition',['jxg', 'utils/type'], function (JXG, Type) {

    "use strict";

    /**
     * A composition is a simple container that manages none or more {@link JXG.GeometryElement}s.
     * @param {Object} elements A list of elements with a descriptive name for the element as the key and a reference
     * to the element as the value of every list entry. The name is used to access the element later on.
     * @example
     * var p1 = board.create('point', [1, 2]),
     *     p2 = board.create('point', [2, 3]),
     *     c = new JXG.Composition({
     *         start: p1,
     *         end: p2
     *     });
     *
     * // moves p1 to [3, 3]
     * c.start.moveTo([3, 3]);
     * @class JXG.Composition
     */
    JXG.Composition = function (elements) {
        var e,
            that = this,
            genericMethods = [
                /**
                 * Invokes setAttribute for every stored element with a setAttribute method and hands over the given arguments.
                 * See {@link JXG.GeometryElement#setAttribute} for further description, valid parameters and return values.
                 * @name setAttribute
                 * @memberOf JXG.Composition.prototype
                 * @function
                 */
                'setAttribute',

                /**
                * Invokes setParents for every stored element with a setParents method and hands over the given arguments.
                * See {@link JXG.GeometryElement#setParents} for further description, valid parameters and return values.
                * @name setParents
                * @memberOf JXG.Composition.prototype
                * @function
                */
                'setParents',

                /**
                 * Invokes prepareUpdate for every stored element with a prepareUpdate method and hands over the given arguments.
                 * See {@link JXG.GeometryElement#prepareUpdate} for further description, valid parameters and return values.
                 * @name prepareUpdate
                 * @memberOf JXG.Composition.prototype
                 * @function
                 */
                'prepareUpdate',

                /**
                 * Invokes updateRenderer for every stored element with a updateRenderer method and hands over the given arguments.
                 * See {@link JXG.GeometryElement#updateRenderer} for further description, valid parameters and return values.
                 * @name updateRenderer
                 * @memberOf JXG.Composition.prototype
                 * @function
                 */
                'updateRenderer',

                /**
                 * Invokes update for every stored element with a update method and hands over the given arguments.
                 * See {@link JXG.GeometryElement#update} for further description, valid parameters and return values.
                 * @name update
                 * @memberOf JXG.Composition.prototype
                 * @function
                 */
                'update',

                /**
                 * Invokes fullUpdate for every stored element with a fullUpdate method and hands over the given arguments.
                 * See {@link JXG.GeometryElement#fullUpdate} for further description, valid parameters and return values.
                 * @name fullUpdate
                 * @memberOf JXG.Composition.prototype
                 * @function
                 */
                'fullUpdate',

                /**
                 * Invokes highlight for every stored element with a highlight method and hands over the given arguments.
                 * See {@link JXG.GeometryElement#highlight} for further description, valid parameters and return values.
                 * @name highlight
                 * @memberOf JXG.Composition.prototype
                 * @function
                 */
                'highlight',

                /**
                 * Invokes noHighlight for every stored element with a noHighlight method and hands over the given arguments.
                 * See {@link JXG.GeometryElement#noHighlight} for further description, valid parameters and return values.
                 * @name noHighlight
                 * @memberOf JXG.Composition.prototype
                 * @function
                 */
                'noHighlight'
            ],
            generateMethod = function (what) {
                return function () {
                    var i;

                    for (i in that.elements) {
                        if (that.elements.hasOwnProperty(i)) {
                            if (Type.exists(that.elements[i][what])) {
                                that.elements[i][what].apply(that.elements[i], arguments);
                            }
                        }
                    }
                    return that;
                };
            };

        for (e = 0; e < genericMethods.length; e++) {
            this[genericMethods[e]] = generateMethod(genericMethods[e]);
        }

        this.elements = {};
        this.objects = this.elements;

        this.elementsByName = {};
        this.objectsList = [];

        // unused, required for select()
        this.groups = {};

        this.methodMap = {
            setAttribute: 'setAttribute',
            setProperty: 'setAttribute',
            setParents: 'setParents',
            add: 'add',
            remove: 'remove',
            select: 'select'
        };

        for (e in elements) {
            if (elements.hasOwnProperty(e)) {
                this.add(e, elements[e]);
            }
        }

        this.dump = true;
        this.subs = {};
    };

    JXG.extend(JXG.Composition.prototype, /** @lends JXG.Composition.prototype */ {

        /**
         * Adds an element to the composition container.
         * @param {String} what Descriptive name for the element, e.g. <em>startpoint</em> or <em>area</em>. This is used to
         * access the element later on. There are some reserved names: <em>elements, add, remove, update, prepareUpdate,
         * updateRenderer, highlight, noHighlight</em>, and all names that would form invalid object property names in
         * JavaScript.
         * @param {JXG.GeometryElement|JXG.Composition} element A reference to the element that is to be added. This can be
         * another composition, too.
         * @returns {Boolean} True, if the element was added successfully. Reasons why adding the element failed include
         * using a reserved name and providing an invalid element.
         */
        add: function (what, element) {
            if (!Type.exists(this[what]) && Type.exists(element)) {
                if (Type.exists(element.id)) {
                    this.elements[element.id] = element;
                } else {
                    this.elements[what] = element;
                }

                if (Type.exists(element.name)) {
                    this.elementsByName[element.name] = element;
                }

                element.on('attribute:name', this.nameListener, this);

                this.objectsList.push(element);
                this[what] = element;
                this.methodMap[what] = element;

                return true;
            }

            return false;
        },

        /**
         * Remove an element from the composition container.
         * @param {String} what The name used to access the element.
         * @returns {Boolean} True, if the element has been removed successfully.
         */
        remove: function (what) {
            var found = false,
                e;

            for (e in this.elements) {
                if (this.elements.hasOwnProperty(e)) {
                    if (this.elements[e].id === this[what].id) {
                        found = true;
                        break;
                    }
                }
            }

            if (found) {
                delete this.elements[this[what].id];
                delete this[what];
            }

            return found;
        },

        nameListener: function (oval, nval, el) {
            delete this.elementsByName[oval];
            this.elementsByName[nval] = el;
        },

        select: function (filter) {
            // for now, hijack JXG.Board's select() method
            if (Type.exists(JXG.Board)) {
                return JXG.Board.prototype.select.call(this, filter);
            }

            return new JXG.Composition();
        },

        getParents: function () {
            return this.parents;
        },

        getType: function () {
            return this.elType;
        },

        getAttributes: function () {
            var attr = {},
                e;

            for (e in this.subs) {
                if (this.subs.hasOwnProperty(e)) {
                    attr[e] = this.subs[e].visProp;
                }
            }

            return this.attr;
        }
    });

    return JXG.Composition;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, AMprocessNode: true, MathJax: true, window: true, document: true, init: true, translateASCIIMath: true, google: true*/

/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 base/coords
 options
 math/numerics
 math/math
 math/geometry
 math/complex
 parser/jessiecode
 parser/geonext
 utils/color
 utils/type
 utils/event
 utils/env
  elements:
   transform
   point
   line
   text
   grid
 */

/**
 * @fileoverview The JXG.Board class is defined in this file. JXG.Board controls all properties and methods
 * used to manage a geonext board like managing geometric elements, managing mouse and touch events, etc.
 */

define('base/board',[
    'jxg', 'base/constants', 'base/coords', 'options', 'math/numerics', 'math/math', 'math/geometry', 'math/complex',
    'math/statistics',
    'parser/jessiecode', 'utils/color', 'utils/type', 'utils/event', 'utils/env',
    'base/composition'
], function (JXG, Const, Coords, Options, Numerics, Mat, Geometry, Complex, Statistics, JessieCode, Color, Type,
                EventEmitter, Env, Composition) {

    'use strict';

    /**
     * Constructs a new Board object.
     * @class JXG.Board controls all properties and methods used to manage a geonext board like managing geometric
     * elements, managing mouse and touch events, etc. You probably don't want to use this constructor directly.
     * Please use {@link JXG.JSXGraph.initBoard} to initialize a board.
     * @constructor
     * @param {String} container The id or reference of the HTML DOM element the board is drawn in. This is usually a HTML div.
     * @param {JXG.AbstractRenderer} renderer The reference of a renderer.
     * @param {String} id Unique identifier for the board, may be an empty string or null or even undefined.
     * @param {JXG.Coords} origin The coordinates where the origin is placed, in user coordinates.
     * @param {Number} zoomX Zoom factor in x-axis direction
     * @param {Number} zoomY Zoom factor in y-axis direction
     * @param {Number} unitX Units in x-axis direction
     * @param {Number} unitY Units in y-axis direction
     * @param {Number} canvasWidth  The width of canvas
     * @param {Number} canvasHeight The height of canvas
     * @param {Object} attributes The attributes object given to {@link JXG.JSXGraph.initBoard}
     * @borrows JXG.EventEmitter#on as this.on
     * @borrows JXG.EventEmitter#off as this.off
     * @borrows JXG.EventEmitter#triggerEventHandlers as this.triggerEventHandlers
     * @borrows JXG.EventEmitter#eventHandlers as this.eventHandlers
     */
    JXG.Board = function (container, renderer, id, origin, zoomX, zoomY, unitX, unitY, canvasWidth, canvasHeight, attributes) {
        /**
         * Board is in no special mode, objects are highlighted on mouse over and objects may be
         * clicked to start drag&drop.
         * @type Number
         * @constant
         */
        this.BOARD_MODE_NONE = 0x0000;

        /**
         * Board is in drag mode, objects aren't highlighted on mouse over and the object referenced in
         * {@link JXG.Board#mouse} is updated on mouse movement.
         * @type Number
         * @constant
         * @see JXG.Board#drag_obj
         */
        this.BOARD_MODE_DRAG = 0x0001;

        /**
         * In this mode a mouse move changes the origin's screen coordinates.
         * @type Number
         * @constant
         */
        this.BOARD_MODE_MOVE_ORIGIN = 0x0002;

        /**
         * Update is made with high quality, e.g. graphs are evaluated at much more points.
         * @type Number
         * @constant
         * @see JXG.Board#updateQuality
         */
        this.BOARD_MODE_ZOOM = 0x0011;

        /**
         * Update is made with low quality, e.g. graphs are evaluated at a lesser amount of points.
         * @type Number
         * @constant
         * @see JXG.Board#updateQuality
         */
        this.BOARD_QUALITY_LOW = 0x1;

        /**
         * Update is made with high quality, e.g. graphs are evaluated at much more points.
         * @type Number
         * @constant
         * @see JXG.Board#updateQuality
         */
        this.BOARD_QUALITY_HIGH = 0x2;

        /**
         * Pointer to the document element containing the board.
         * @type Object
         */
        // Former version:
        // this.document = attributes.document || document;
        if (Type.exists(attributes.document) && attributes.document !== false) {
            this.document = attributes.document;
        } else if (document !== undefined && Type.isObject(document)) {
            this.document = document;
        }

        /**
         * The html-id of the html element containing the board.
         * @type String
         */
        this.container = container;

        /**
         * Pointer to the html element containing the board.
         * @type Object
         */
        this.containerObj = (Env.isBrowser ? this.document.getElementById(this.container) : null);

        if (Env.isBrowser && renderer.type !== 'no' && this.containerObj === null) {
            throw new Error("\nJSXGraph: HTML container element '" + container + "' not found.");
        }

        /**
         * A reference to this boards renderer.
         * @type JXG.AbstractRenderer
         * @name JXG.Board#renderer
         * @private
         * @ignore
         */
        this.renderer = renderer;

        /**
         * Grids keeps track of all grids attached to this board.
         * @type Array
         * @private
         */
        this.grids = [];

        /**
         * Some standard options
         * @type JXG.Options
         */
        this.options = Type.deepCopy(Options);
        this.attr = attributes;

        /**
         * Dimension of the board.
         * @default 2
         * @type Number
         */
        this.dimension = 2;

        this.jc = new JessieCode();
        this.jc.use(this);

        /**
         * Coordinates of the boards origin. This a object with the two properties
         * usrCoords and scrCoords. usrCoords always equals [1, 0, 0] and scrCoords
         * stores the boards origin in homogeneous screen coordinates.
         * @type Object
         * @private
         */
        this.origin = {};
        this.origin.usrCoords = [1, 0, 0];
        this.origin.scrCoords = [1, origin[0], origin[1]];

        /**
         * Zoom factor in X direction. It only stores the zoom factor to be able
         * to get back to 100% in zoom100().
         * @name JXG.Board.zoomX
         * @type Number
         * @private
         * @ignore
         */
        this.zoomX = zoomX;

        /**
         * Zoom factor in Y direction. It only stores the zoom factor to be able
         * to get back to 100% in zoom100().
         * @name JXG.Board.zoomY
         * @type Number
         * @private
         * @ignore
         */
        this.zoomY = zoomY;

        /**
         * The number of pixels which represent one unit in user-coordinates in x direction.
         * @type Number
         * @private
         */
        this.unitX = unitX * this.zoomX;

        /**
         * The number of pixels which represent one unit in user-coordinates in y direction.
         * @type Number
         * @private
         */
        this.unitY = unitY * this.zoomY;

        /**
         * Keep aspect ratio if bounding box is set and the width/height ratio differs from the
         * width/height ratio of the canvas.
         * @type Boolean
         * @private
         */
        this.keepaspectratio = false;

        /**
         * Canvas width.
         * @type Number
         * @private
         */
        this.canvasWidth = canvasWidth;

        /**
         * Canvas Height
         * @type Number
         * @private
         */
        this.canvasHeight = canvasHeight;

        // If the given id is not valid, generate an unique id
        if (Type.exists(id) && id !== '' && Env.isBrowser && !Type.exists(this.document.getElementById(id))) {
            this.id = id;
        } else {
            this.id = this.generateId();
        }

        EventEmitter.eventify(this);

        this.hooks = [];

        /**
         * An array containing all other boards that are updated after this board has been updated.
         * @type Array
         * @see JXG.Board#addChild
         * @see JXG.Board#removeChild
         */
        this.dependentBoards = [];

        /**
         * During the update process this is set to false to prevent an endless loop.
         * @default false
         * @type Boolean
         */
        this.inUpdate = false;

        /**
         * An associative array containing all geometric objects belonging to the board. Key is the id of the object and value is a reference to the object.
         * @type Object
         */
        this.objects = {};

        /**
         * An array containing all geometric objects on the board in the order of construction.
         * @type Array
         */
        this.objectsList = [];

        /**
         * An associative array containing all groups belonging to the board. Key is the id of the group and value is a reference to the object.
         * @type Object
         */
        this.groups = {};

        /**
         * Stores all the objects that are currently running an animation.
         * @type Object
         */
        this.animationObjects = {};

        /**
         * An associative array containing all highlighted elements belonging to the board.
         * @type Object
         */
        this.highlightedObjects = {};

        /**
         * Number of objects ever created on this board. This includes every object, even invisible and deleted ones.
         * @type Number
         */
        this.numObjects = 0;

        /**
         * An associative array to store the objects of the board by name. the name of the object is the key and value is a reference to the object.
         * @type Object
         */
        this.elementsByName = {};

        /**
         * The board mode the board is currently in. Possible values are
         * <ul>
         * <li>JXG.Board.BOARD_MODE_NONE</li>
         * <li>JXG.Board.BOARD_MODE_DRAG</li>
         * <li>JXG.Board.BOARD_MODE_MOVE_ORIGIN</li>
         * </ul>
         * @type Number
         */
        this.mode = this.BOARD_MODE_NONE;

        /**
         * The update quality of the board. In most cases this is set to {@link JXG.Board#BOARD_QUALITY_HIGH}.
         * If {@link JXG.Board#mode} equals {@link JXG.Board#BOARD_MODE_DRAG} this is set to
         * {@link JXG.Board#BOARD_QUALITY_LOW} to speed up the update process by e.g. reducing the number of
         * evaluation points when plotting functions. Possible values are
         * <ul>
         * <li>BOARD_QUALITY_LOW</li>
         * <li>BOARD_QUALITY_HIGH</li>
         * </ul>
         * @type Number
         * @see JXG.Board#mode
         */
        this.updateQuality = this.BOARD_QUALITY_HIGH;

        /**
         * If true updates are skipped.
         * @type Boolean
         */
        this.isSuspendedRedraw = false;

        this.calculateSnapSizes();

        /**
         * The distance from the mouse to the dragged object in x direction when the user clicked the mouse button.
         * @type Number
         * @see JXG.Board#drag_dy
         * @see JXG.Board#drag_obj
         */
        this.drag_dx = 0;

        /**
         * The distance from the mouse to the dragged object in y direction when the user clicked the mouse button.
         * @type Number
         * @see JXG.Board#drag_dx
         * @see JXG.Board#drag_obj
         */
        this.drag_dy = 0;

        /**
         * The last position where a drag event has been fired.
         * @type Array
         * @see JXG.Board#moveObject
         */
        this.drag_position = [0, 0];

        /**
         * References to the object that is dragged with the mouse on the board.
         * @type JXG.GeometryElement
         * @see JXG.Board#touches
         */
        this.mouse = {};

        /**
         * Keeps track on touched elements, like {@link JXG.Board#mouse} does for mouse events.
         * @type Array
         * @see JXG.Board#mouse
         */
        this.touches = [];

        /**
         * A string containing the XML text of the construction.
         * This is set in {@link JXG.FileReader.parseString}.
         * Only useful if a construction is read from a GEONExT-, Intergeo-, Geogebra-, or Cinderella-File.
         * @type String
         */
        this.xmlString = '';

        /**
         * Cached result of getCoordsTopLeftCorner for touch/mouseMove-Events to save some DOM operations.
         * @type Array
         */
        this.cPos = [];

        /**
         * Contains the last time (epoch, msec) since the last touchMove event which was not thrown away or since
         * touchStart because Android's Webkit browser fires too much of them.
         * @type Number
         */
        this.touchMoveLast = 0;

        /**
         * Contains the pointerId of the last touchMove event which was not thrown away or since
         * touchStart because Android's Webkit browser fires too much of them.
         * @type Number
         */
         this.touchMoveLastId = Infinity;

        /**
         * Contains the last time (epoch, msec) since the last getCoordsTopLeftCorner call which was not thrown away.
         * @type Number
         */
        this.positionAccessLast = 0;

        /**
         * Collects all elements that triggered a mouse down event.
         * @type Array
         */
        this.downObjects = [];

        if (this.attr.showcopyright) {
            this.renderer.displayCopyright(Const.licenseText, parseInt(this.options.text.fontSize, 10));
        }

        /**
         * Full updates are needed after zoom and axis translates. This saves some time during an update.
         * @default false
         * @type Boolean
         */
        this.needsFullUpdate = false;

        /**
         * If reducedUpdate is set to true then only the dragged element and few (e.g. 2) following
         * elements are updated during mouse move. On mouse up the whole construction is
         * updated. This enables us to be fast even on very slow devices.
         * @type Boolean
         * @default false
         */
        this.reducedUpdate = false;

        /**
         * The current color blindness deficiency is stored in this property. If color blindness is not emulated
         * at the moment, it's value is 'none'.
         */
        this.currentCBDef = 'none';

        /**
         * If GEONExT constructions are displayed, then this property should be set to true.
         * At the moment there should be no difference. But this may change.
         * This is set in {@link JXG.GeonextReader.readGeonext}.
         * @type Boolean
         * @default false
         * @see JXG.GeonextReader.readGeonext
         */
        this.geonextCompatibilityMode = false;

        if (this.options.text.useASCIIMathML && translateASCIIMath) {
            init();
        } else {
            this.options.text.useASCIIMathML = false;
        }

        /**
         * A flag which tells if the board registers mouse events.
         * @type Boolean
         * @default false
         */
        this.hasMouseHandlers = false;

        /**
         * A flag which tells if the board registers touch events.
         * @type Boolean
         * @default false
         */
        this.hasTouchHandlers = false;

        /**
         * A flag which stores if the board registered pointer events.
         * @type Boolean
         * @default false
         */
        this.hasPointerHandlers = false;

        /**
         * A flag which tells if the board the JXG.Board#mouseUpListener is currently registered.
         * @type Boolean
         * @default false
         */
        this.hasMouseUp = false;

        /**
         * A flag which tells if the board the JXG.Board#touchEndListener is currently registered.
         * @type Boolean
         * @default false
         */
        this.hasTouchEnd = false;

        /**
         * A flag which tells us if the board has a pointerUp event registered at the moment.
         * @type Boolean
         * @default false
         */
        this.hasPointerUp = false;

        /**
         * Offset for large coords elements like images
         * @type Array
         * @private
         * @default [0, 0]
         */
        this._drag_offset = [0, 0];

        /**
         * Stores the input device used in the last down or move event.
         * @type String
         * @private
         * @default 'mouse'
         */
        this._inputDevice = 'mouse';

        /**
         * Keeps a list of pointer devices which are currently touching the screen.
         * @type Array
         * @private
         */
        this._board_touches = [];

        /**
         * A flag which tells us if the board is in the selecting mode
         * @type Boolean
         * @default false
         */
        this.selectingMode = false;

        /**
         * A flag which tells us if the user is selecting
         * @type Boolean
         * @default false
         */
        this.isSelecting = false;

        /**
         * A flag which tells us if the user is scrolling the viewport
         * @type Boolean
         * @private
         * @default false
         * @see JXG.Board#scrollListener
         */
        this._isScrolling = false;

        /**
         * A flag which tells us if a resize is in process
         * @type Boolean
         * @private
         * @default false
         * @see JXG.Board#resizeListener
         */
        this._isResizing = false;

        /**
         * A bounding box for the selection
         * @type Array
         * @default [ [0,0], [0,0] ]
         */
        this.selectingBox = [[0, 0], [0, 0]];

        this.mathLib = Math;        // Math or JXG.Math.IntervalArithmetic
        this.mathLibJXG = JXG.Math; // JXG.Math or JXG.Math.IntervalArithmetic

        if (this.attr.registerevents) {
            this.addEventHandlers();
        }

        this.methodMap = {
            update: 'update',
            fullUpdate: 'fullUpdate',
            on: 'on',
            off: 'off',
            trigger: 'trigger',
            setView: 'setBoundingBox',
            setBoundingBox: 'setBoundingBox',
            migratePoint: 'migratePoint',
            colorblind: 'emulateColorblindness',
            suspendUpdate: 'suspendUpdate',
            unsuspendUpdate: 'unsuspendUpdate',
            clearTraces: 'clearTraces',
            left: 'clickLeftArrow',
            right: 'clickRightArrow',
            up: 'clickUpArrow',
            down: 'clickDownArrow',
            zoomIn: 'zoomIn',
            zoomOut: 'zoomOut',
            zoom100: 'zoom100',
            zoomElements: 'zoomElements',
            remove: 'removeObject',
            removeObject: 'removeObject'
        };
    };

    JXG.extend(JXG.Board.prototype, /** @lends JXG.Board.prototype */ {

        /**
         * Generates an unique name for the given object. The result depends on the objects type, if the
         * object is a {@link JXG.Point}, capital characters are used, if it is of type {@link JXG.Line}
         * only lower case characters are used. If object is of type {@link JXG.Polygon}, a bunch of lower
         * case characters prefixed with P_ are used. If object is of type {@link JXG.Circle} the name is
         * generated using lower case characters. prefixed with k_ is used. In any other case, lower case
         * chars prefixed with s_ is used.
         * @param {Object} object Reference of an JXG.GeometryElement that is to be named.
         * @returns {String} Unique name for the object.
         */
        generateName: function (object) {
            var possibleNames, i,
                maxNameLength = this.attr.maxnamelength,
                pre = '',
                post = '',
                indices = [],
                name = '';

            if (object.type === Const.OBJECT_TYPE_TICKS) {
                return '';
            }

            if (Type.isPoint(object)) {
                // points have capital letters
                possibleNames = ['', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O',
                    'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'];
            } else if (object.type === Const.OBJECT_TYPE_ANGLE) {
                possibleNames = ['', '&alpha;', '&beta;', '&gamma;', '&delta;', '&epsilon;', '&zeta;', '&eta;', '&theta;',
                    '&iota;', '&kappa;', '&lambda;', '&mu;', '&nu;', '&xi;', '&omicron;', '&pi;', '&rho;',
                    '&sigma;', '&tau;', '&upsilon;', '&phi;', '&chi;', '&psi;', '&omega;'];
            } else {
                // all other elements get lowercase labels
                possibleNames = ['', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o',
                    'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
            }

            if (!Type.isPoint(object) &&
                    object.elementClass !== Const.OBJECT_CLASS_LINE &&
                    object.type !== Const.OBJECT_TYPE_ANGLE) {
                if (object.type === Const.OBJECT_TYPE_POLYGON) {
                    pre = 'P_{';
                } else if (object.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                    pre = 'k_{';
                } else if (object.elementClass === Const.OBJECT_CLASS_TEXT) {
                    pre = 't_{';
                } else {
                    pre = 's_{';
                }
                post = '}';
            }

            for (i = 0; i < maxNameLength; i++) {
                indices[i] = 0;
            }

            while (indices[maxNameLength - 1] < possibleNames.length) {
                for (indices[0] = 1; indices[0] < possibleNames.length; indices[0]++) {
                    name = pre;

                    for (i = maxNameLength; i > 0; i--) {
                        name += possibleNames[indices[i - 1]];
                    }

                    if (!Type.exists(this.elementsByName[name + post])) {
                        return name + post;
                    }

                }
                indices[0] = possibleNames.length;

                for (i = 1; i < maxNameLength; i++) {
                    if (indices[i - 1] === possibleNames.length) {
                        indices[i - 1] = 1;
                        indices[i] += 1;
                    }
                }
            }

            return '';
        },

        /**
         * Generates unique id for a board. The result is randomly generated and prefixed with 'jxgBoard'.
         * @returns {String} Unique id for a board.
         */
        generateId: function () {
            var r = 1;

            // as long as we don't have a unique id generate a new one
            while (Type.exists(JXG.boards['jxgBoard' + r])) {
                r = Math.round(Math.random() * 65535);
            }

            return ('jxgBoard' + r);
        },

        /**
         * Composes an id for an element. If the ID is empty ('' or null) a new ID is generated, depending on the
         * object type. As a side effect {@link JXG.Board#numObjects}
         * is updated.
         * @param {Object} obj Reference of an geometry object that needs an id.
         * @param {Number} type Type of the object.
         * @returns {String} Unique id for an element.
         */
        setId: function (obj, type) {
            var randomNumber,
                num = this.numObjects,
                elId = obj.id;

            this.numObjects += 1;

            // If no id is provided or id is empty string, a new one is chosen
            if (elId === '' || !Type.exists(elId)) {
                elId = this.id + type + num;
                while (Type.exists(this.objects[elId])) {
                    randomNumber = Math.round(Math.random() * 65535);
                    elId = this.id + type + num + '-' + randomNumber;
                }
            }

            obj.id = elId;
            this.objects[elId] = obj;
            obj._pos = this.objectsList.length;
            this.objectsList[this.objectsList.length] = obj;

            return elId;
        },

        /**
         * After construction of the object the visibility is set
         * and the label is constructed if necessary.
         * @param {Object} obj The object to add.
         */
        finalizeAdding: function (obj) {
            if (Type.evaluate(obj.visProp.visible) === false) {
                this.renderer.display(obj, false);
            }
        },

        finalizeLabel: function (obj) {
            if (obj.hasLabel &&
                !Type.evaluate(obj.label.visProp.islabel) &&
                Type.evaluate(obj.label.visProp.visible) === false) {
                this.renderer.display(obj.label, false);
            }
        },

        /**********************************************************
         *
         * Event Handler helpers
         *
         **********************************************************/

        /**
         * Returns false if the event has been triggered faster than the maximum frame rate.
         *
         * @param {Event} evt Event object given by the browser (unused)
         * @returns {Boolean} If the event has been triggered faster than the maximum frame rate, false is returned.
         * @private
         * @see JXG.Board#pointerMoveListener
         * @see JXG.Board#touchMoveListener
         * @see JXG.Board#mouseMoveListener
         */
        checkFrameRate: function(evt) {
            var handleEvt = false,
                time = new Date().getTime();

            if (Type.exists(evt.pointerId) && this.touchMoveLastId !== evt.pointerId) {
                handleEvt = true;
                this.touchMoveLastId = evt.pointerId;
            }
            if (!handleEvt && (time - this.touchMoveLast) * this.attr.maxframerate >= 1000) {
                handleEvt = true;
            }
            if (handleEvt) {
                this.touchMoveLast = time;
            }
            return handleEvt;
        },

        /**
         * Calculates mouse coordinates relative to the boards container.
         * @returns {Array} Array of coordinates relative the boards container top left corner.
         */
        getCoordsTopLeftCorner: function () {
            var cPos, doc, crect,
                docElement = this.document.documentElement || this.document.body.parentNode,
                docBody = this.document.body,
                container = this.containerObj,
                // viewport, content,
                zoom, o;

            /**
             * During drags and origin moves the container element is usually not changed.
             * Check the position of the upper left corner at most every 1000 msecs
             */
            if (this.cPos.length > 0 &&
                    (this.mode === this.BOARD_MODE_DRAG || this.mode === this.BOARD_MODE_MOVE_ORIGIN ||
                    (new Date()).getTime() - this.positionAccessLast < 1000)) {
                return this.cPos;
            }
            this.positionAccessLast = (new Date()).getTime();

            // Check if getBoundingClientRect exists. If so, use this as this covers *everything*
            // even CSS3D transformations etc.
            // Supported by all browsers but IE 6, 7.

            if (container.getBoundingClientRect) {
                crect = container.getBoundingClientRect();


                zoom = 1.0;
                // Recursively search for zoom style entries.
                // This is necessary for reveal.js on webkit.
                // It fails if the user does zooming
                o = container;
                while (o && Type.exists(o.parentNode)) {
                    if (Type.exists(o.style) && Type.exists(o.style.zoom) && o.style.zoom !== '') {
                        zoom *= parseFloat(o.style.zoom);
                    }
                    o = o.parentNode;
                }
                cPos = [crect.left * zoom, crect.top * zoom];

                // add border width
                cPos[0] += Env.getProp(container, 'border-left-width');
                cPos[1] += Env.getProp(container, 'border-top-width');

                // vml seems to ignore paddings
                if (this.renderer.type !== 'vml') {
                    // add padding
                    cPos[0] += Env.getProp(container, 'padding-left');
                    cPos[1] += Env.getProp(container, 'padding-top');
                }

                this.cPos = cPos.slice();
                return this.cPos;
            }

            //
            //  OLD CODE
            //  IE 6-7 only:
            //
            cPos = Env.getOffset(container);
            doc = this.document.documentElement.ownerDocument;

            if (!this.containerObj.currentStyle && doc.defaultView) {     // Non IE
                // this is for hacks like this one used in wordpress for the admin bar:
                // html { margin-top: 28px }
                // seems like it doesn't work in IE

                cPos[0] += Env.getProp(docElement, 'margin-left');
                cPos[1] += Env.getProp(docElement, 'margin-top');

                cPos[0] += Env.getProp(docElement, 'border-left-width');
                cPos[1] += Env.getProp(docElement, 'border-top-width');

                cPos[0] += Env.getProp(docElement, 'padding-left');
                cPos[1] += Env.getProp(docElement, 'padding-top');
            }

            if (docBody) {
                cPos[0] += Env.getProp(docBody, 'left');
                cPos[1] += Env.getProp(docBody, 'top');
            }

            // Google Translate offers widgets for web authors. These widgets apparently tamper with the clientX
            // and clientY coordinates of the mouse events. The minified sources seem to be the only publicly
            // available version so we're doing it the hacky way: Add a fixed offset.
            // see https://groups.google.com/d/msg/google-translate-general/H2zj0TNjjpY/jw6irtPlCw8J
            if (typeof google === 'object' && google.translate) {
                cPos[0] += 10;
                cPos[1] += 25;
            }

            // add border width
            cPos[0] += Env.getProp(container, 'border-left-width');
            cPos[1] += Env.getProp(container, 'border-top-width');

            // vml seems to ignore paddings
            if (this.renderer.type !== 'vml') {
                // add padding
                cPos[0] += Env.getProp(container, 'padding-left');
                cPos[1] += Env.getProp(container, 'padding-top');
            }

            cPos[0] += this.attr.offsetx;
            cPos[1] += this.attr.offsety;

            this.cPos = cPos.slice();
            return this.cPos;
        },

        /**
         * Get the position of the mouse in screen coordinates, relative to the upper left corner
         * of the host tag.
         * @param {Event} e Event object given by the browser.
         * @param {Number} [i] Only use in case of touch events. This determines which finger to use and should not be set
         * for mouseevents.
         * @returns {Array} Contains the mouse coordinates in screen coordinates, ready for {@link JXG.Coords}
         */
        getMousePosition: function (e, i) {
            var cPos = this.getCoordsTopLeftCorner(),
                absPos,
                v;

            // Position of cursor using clientX/Y
            absPos = Env.getPosition(e, i, this.document);

            /**
             * In case there has been no down event before.
             */
            if (!Type.exists(this.cssTransMat)) {
                this.updateCSSTransforms();
            }
            // Position relative to the top left corner
            v = [1, absPos[0] - cPos[0], absPos[1] - cPos[1]];
            v = Mat.matVecMult(this.cssTransMat, v);
            v[1] /= v[0];
            v[2] /= v[0];
            return [v[1], v[2]];

            // Method without CSS transformation
            /*
             return [absPos[0] - cPos[0], absPos[1] - cPos[1]];
             */
        },

        /**
         * Initiate moving the origin. This is used in mouseDown and touchStart listeners.
         * @param {Number} x Current mouse/touch coordinates
         * @param {Number} y Current mouse/touch coordinates
         */
        initMoveOrigin: function (x, y) {
            this.drag_dx = x - this.origin.scrCoords[1];
            this.drag_dy = y - this.origin.scrCoords[2];

            this.mode = this.BOARD_MODE_MOVE_ORIGIN;
            this.updateQuality = this.BOARD_QUALITY_LOW;
        },

        /**
         * Collects all elements below the current mouse pointer and fulfilling the following constraints:
         * <ul><li>isDraggable</li><li>visible</li><li>not fixed</li><li>not frozen</li></ul>
         * @param {Number} x Current mouse/touch coordinates
         * @param {Number} y current mouse/touch coordinates
         * @param {Object} evt An event object
         * @param {String} type What type of event? 'touch', 'mouse' or 'pen'.
         * @returns {Array} A list of geometric elements.
         */
        initMoveObject: function (x, y, evt, type) {
            var pEl,
                el,
                collect = [],
                offset = [],
                haspoint,
                len = this.objectsList.length,
                dragEl = {visProp: {layer: -10000}};

            //for (el in this.objects) {
            for (el = 0; el < len; el++) {
                pEl = this.objectsList[el];
                haspoint = pEl.hasPoint && pEl.hasPoint(x, y);

                if (pEl.visPropCalc.visible && haspoint) {
                    pEl.triggerEventHandlers([type + 'down', 'down'], [evt]);
                    this.downObjects.push(pEl);
                }

                if (haspoint &&
                    pEl.isDraggable &&
                    pEl.visPropCalc.visible &&
                    ((this.geonextCompatibilityMode &&
                        (Type.isPoint(pEl) ||
                         pEl.elementClass === Const.OBJECT_CLASS_TEXT)
                     ) ||
                     !this.geonextCompatibilityMode
                    ) &&
                    !Type.evaluate(pEl.visProp.fixed)
                    /*(!pEl.visProp.frozen) &&*/
                    ) {

                    // Elements in the highest layer get priority.
                    if (pEl.visProp.layer > dragEl.visProp.layer ||
                            (pEl.visProp.layer === dragEl.visProp.layer &&
                             pEl.lastDragTime.getTime() >= dragEl.lastDragTime.getTime()
                            )) {
                        // If an element and its label have the focus
                        // simultaneously, the element is taken.
                        // This only works if we assume that every browser runs
                        // through this.objects in the right order, i.e. an element A
                        // added before element B turns up here before B does.
                        if (!this.attr.ignorelabels ||
                            (!Type.exists(dragEl.label) || pEl !== dragEl.label)) {
                            dragEl = pEl;
                            collect.push(dragEl);

                            // Save offset for large coords elements.
                            if (Type.exists(dragEl.coords)) {
                                offset.push(Statistics.subtract(dragEl.coords.scrCoords.slice(1), [x, y]));
                            } else {
                                offset.push([0, 0]);
                            }

                            // we can't drop out of this loop because of the event handling system
                            //if (this.attr.takefirst) {
                            //    return collect;
                            //}
                        }
                    }
                }
            }

            if (this.attr.drag.enabled && collect.length > 0) {
                this.mode = this.BOARD_MODE_DRAG;
            }

            // A one-element array is returned.
            if (this.attr.takefirst) {
                collect.length = 1;
                this._drag_offset = offset[0];
            } else {
                collect = collect.slice(-1);
                this._drag_offset = offset[offset.length - 1];
            }

            if (!this._drag_offset) {
                this._drag_offset = [0, 0];
            }

            // Move drag element to the top of the layer
            if (this.renderer.type === 'svg' &&
                Type.exists(collect[0]) &&
                Type.evaluate(collect[0].visProp.dragtotopoflayer) &&
                collect.length === 1 &&
                Type.exists(collect[0].rendNode)) {

                collect[0].rendNode.parentNode.appendChild(collect[0].rendNode);
            }

            // Init rotation angle and scale factor for two finger movements
            this.previousRotation = 0.0;
            this.previousScale = 1.0;

            if (collect.length >= 1) {
                collect[0].highlight(true);
                this.triggerEventHandlers(['mousehit', 'hit'], [evt, collect[0]]);
            }

            return collect;
        },

        /**
         * Moves an object.
         * @param {Number} x Coordinate
         * @param {Number} y Coordinate
         * @param {Object} o The touch object that is dragged: {JXG.Board#mouse} or {JXG.Board#touches}.
         * @param {Object} evt The event object.
         * @param {String} type Mouse or touch event?
         */
        moveObject: function (x, y, o, evt, type) {
            var newPos = new Coords(Const.COORDS_BY_SCREEN, this.getScrCoordsOfMouse(x, y), this),
                drag,
                dragScrCoords, newDragScrCoords;

            if (!(o && o.obj)) {
                return;
            }
            drag = o.obj;

            // Save updates for very small movements of coordsElements, see below
            if (drag.coords) {
                dragScrCoords = drag.coords.scrCoords.slice();
            }

            /*
             * Save the position.
             */
            this.drag_position = [newPos.scrCoords[1], newPos.scrCoords[2]];
            this.drag_position = Statistics.add(this.drag_position, this._drag_offset);
            //
            // We have to distinguish between CoordsElements and other elements like lines.
            // The latter need the difference between two move events.
            if (Type.exists(drag.coords)) {
                drag.setPositionDirectly(Const.COORDS_BY_SCREEN, this.drag_position);
            } else {
                this.displayInfobox(false);
                                    // Hide infobox in case the user has touched an intersection point
                                    // and drags the underlying line now.

                if (!isNaN(o.targets[0].Xprev + o.targets[0].Yprev)) {
                    drag.setPositionDirectly(Const.COORDS_BY_SCREEN,
                        [newPos.scrCoords[1], newPos.scrCoords[2]],
                        [o.targets[0].Xprev, o.targets[0].Yprev]
                        );
                }
                // Remember the actual position for the next move event. Then we are able to
                // compute the difference vector.
                o.targets[0].Xprev = newPos.scrCoords[1];
                o.targets[0].Yprev = newPos.scrCoords[2];
            }
            // This may be necessary for some gliders and labels
            if (Type.exists(drag.coords)) {
                drag.prepareUpdate().update(false).updateRenderer();
                this.updateInfobox(drag);
                drag.prepareUpdate().update(true).updateRenderer();
            }

            if (drag.coords) {
                newDragScrCoords = drag.coords.scrCoords;
            }
            // No updates for very small movements of coordsElements
            if (!drag.coords ||
                dragScrCoords[1] !== newDragScrCoords[1] ||
                dragScrCoords[2] !== newDragScrCoords[2]) {

                drag.triggerEventHandlers([type + 'drag', 'drag'], [evt]);

                this.update();
            }
            drag.highlight(true);
            this.triggerEventHandlers(['mousehit', 'hit'], [evt, drag]);

            drag.lastDragTime = new Date();
        },

        /**
         * Moves elements in multitouch mode.
         * @param {Array} p1 x,y coordinates of first touch
         * @param {Array} p2 x,y coordinates of second touch
         * @param {Object} o The touch object that is dragged: {JXG.Board#touches}.
         * @param {Object} evt The event object that lead to this movement.
         */
        twoFingerMove: function (o, id, evt) {
            var drag;

            if (Type.exists(o) && Type.exists(o.obj)) {
                drag = o.obj;
            } else {
                return;
            }

            if (drag.elementClass === Const.OBJECT_CLASS_LINE ||
                drag.type === Const.OBJECT_TYPE_POLYGON) {
                this.twoFingerTouchObject(o.targets, drag, id);
            } else if (drag.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                this.twoFingerTouchCircle(o.targets, drag, id);
            }

            if (evt) {
                drag.triggerEventHandlers(['touchdrag', 'drag'], [evt]);
            }
        },

        /**
         * Moves, rotates and scales a line or polygon with two fingers.
         * @param {Array} tar Array conatining touch event objects: {JXG.Board#touches.targets}.
         * @param {object} drag The object that is dragged:
         * @param {Number} id pointerId of the event. In case of old touch event this is emulated.
         */
        twoFingerTouchObject: function (tar, drag, id) {
            var np, op, nd, od,
                d, alpha,
                S, t1, t3, t4, t5,
                ar, i, len,
                fixEl, moveEl, fix;

            if (Type.exists(tar[0]) && Type.exists(tar[1]) &&
                !isNaN(tar[0].Xprev + tar[0].Yprev + tar[1].Xprev + tar[1].Yprev)) {

                if (id === tar[0].num) {
                    fixEl  = tar[1];
                    moveEl = tar[0];
                } else {
                    fixEl  = tar[0];
                    moveEl = tar[1];
                }

                fix = (new Coords(Const.COORDS_BY_SCREEN, [fixEl.Xprev, fixEl.Yprev], this)).usrCoords;
                // Previous finger position
                op = (new Coords(Const.COORDS_BY_SCREEN, [moveEl.Xprev, moveEl.Yprev], this)).usrCoords;
                // New finger position
                np = (new Coords(Const.COORDS_BY_SCREEN, [moveEl.X, moveEl.Y], this)).usrCoords;

                // Old and new directions
                od = Mat.crossProduct(fix, op);
                nd = Mat.crossProduct(fix, np);

                // Intersection between the two directions
                S = Mat.crossProduct(od, nd);

                // If parallel translate, otherwise rotate
                if (Math.abs(S[0]) < Mat.eps) {
                    return;
                }

                alpha = Geometry.rad(op.slice(1), fix.slice(1), np.slice(1));

                t1 = this.create('transform', [alpha, [fix[1], fix[2]]], {type: 'rotate'});
                t1.update();

                if (Type.evaluate(drag.visProp.scalable)) {
                    // Scale
                    d = Geometry.distance(np, fix) / Geometry.distance(op, fix);

                    t3 = this.create('transform', [-fix[1], -fix[2]], {type: 'translate'});
                    t4 = this.create('transform', [d, d], {type: 'scale'});
                    t5 = this.create('transform', [fix[1], fix[2]], {type: 'translate'});
                    t1.melt(t3).melt(t4).melt(t5);
                }

                if (drag.elementClass === Const.OBJECT_CLASS_LINE) {
                    ar = [];
                    if (drag.point1.draggable()) {
                        ar.push(drag.point1);
                    }
                    if (drag.point2.draggable()) {
                        ar.push(drag.point2);
                    }
                    t1.applyOnce(ar);
                } else if (drag.type === Const.OBJECT_TYPE_POLYGON) {
                    ar = [];
                    len = drag.vertices.length - 1;
                    for (i = 0; i < len; ++i) {
                        if (drag.vertices[i].draggable()) {
                            ar.push(drag.vertices[i]);
                        }
                    }
                    t1.applyOnce(ar);
                }

                this.update();
                drag.highlight(true);
            }
        },

        /*
         * Moves, rotates and scales a circle with two fingers.
         * @param {Array} tar Array conatining touch event objects: {JXG.Board#touches.targets}.
         * @param {object} drag The object that is dragged:
         * @param {Number} id pointerId of the event. In case of old touch event this is emulated.
         */
        twoFingerTouchCircle: function (tar, drag, id) {
            var fixEl, moveEl, np, op, fix,
                d, alpha, t1, t2, t3, t4;

            if (drag.method === 'pointCircle' || drag.method === 'pointLine') {
                return;
            }

            if (Type.exists(tar[0]) && Type.exists(tar[1]) &&
                !isNaN(tar[0].Xprev + tar[0].Yprev + tar[1].Xprev + tar[1].Yprev)) {

                if (id === tar[0].num) {
                    fixEl  = tar[1];
                    moveEl = tar[0];
                } else {
                    fixEl  = tar[0];
                    moveEl = tar[1];
                }

                fix = (new Coords(Const.COORDS_BY_SCREEN, [fixEl.Xprev, fixEl.Yprev], this)).usrCoords;
                // Previous finger position
                op = (new Coords(Const.COORDS_BY_SCREEN, [moveEl.Xprev, moveEl.Yprev], this)).usrCoords;
                // New finger position
                np = (new Coords(Const.COORDS_BY_SCREEN, [moveEl.X, moveEl.Y], this)).usrCoords;

                alpha = Geometry.rad(op.slice(1), fix.slice(1), np.slice(1));

                // Rotate and scale by the movement of the second finger
                t1 = this.create('transform', [-fix[1], -fix[2]], {type: 'translate'});
                t2 = this.create('transform', [alpha], {type: 'rotate'});
                t1.melt(t2);
                if (Type.evaluate(drag.visProp.scalable)) {
                    d = Geometry.distance(fix, np) / Geometry.distance(fix, op);
                    t3 = this.create('transform', [d, d], {type: 'scale'});
                    t1.melt(t3);
                }
                t4 = this.create('transform', [fix[1], fix[2]], {type: 'translate'});
                t1.melt(t4);

                if (drag.center.draggable()) {
                    t1.applyOnce([drag.center]);
                }

                if (drag.method === 'twoPoints') {
                    if (drag.point2.draggable()) {
                        t1.applyOnce([drag.point2]);
                    }
                } else if (drag.method === 'pointRadius') {
                    if (Type.isNumber(drag.updateRadius.origin)) {
                        drag.setRadius(drag.radius * d);
                    }
                }

                this.update(drag.center);
                drag.highlight(true);
            }
        },

        highlightElements: function (x, y, evt, target) {
            var el, pEl, pId,
                overObjects = {},
                len = this.objectsList.length;

            // Elements  below the mouse pointer which are not highlighted yet will be highlighted.
            for (el = 0; el < len; el++) {
                pEl = this.objectsList[el];
                pId = pEl.id;
                if (Type.exists(pEl.hasPoint) && pEl.visPropCalc.visible && pEl.hasPoint(x, y)) {
                    // this is required in any case because otherwise the box won't be shown until the point is dragged
                    this.updateInfobox(pEl);

                    if (!Type.exists(this.highlightedObjects[pId])) { // highlight only if not highlighted
                        overObjects[pId] = pEl;
                        pEl.highlight();
                        // triggers board event.
                        this.triggerEventHandlers(['mousehit', 'hit'], [evt, pEl, target]);
                    }

                    if (pEl.mouseover) {
                        pEl.triggerEventHandlers(['mousemove', 'move'], [evt]);
                    } else {
                        pEl.triggerEventHandlers(['mouseover', 'over'], [evt]);
                        pEl.mouseover = true;
                    }
                }
            }

            for (el = 0; el < len; el++) {
                pEl = this.objectsList[el];
                pId = pEl.id;
                if (pEl.mouseover) {
                    if (!overObjects[pId]) {
                        pEl.triggerEventHandlers(['mouseout', 'out'], [evt]);
                        pEl.mouseover = false;
                    }
                }
            }
        },

        /**
         * Helper function which returns a reasonable starting point for the object being dragged.
         * Formerly known as initXYstart().
         * @private
         * @param {JXG.GeometryElement} obj The object to be dragged
         * @param {Array} targets Array of targets. It is changed by this function.
         */
        saveStartPos: function (obj, targets) {
            var xy = [], i, len;

            if (obj.type === Const.OBJECT_TYPE_TICKS) {
                xy.push([1, NaN, NaN]);
            } else if (obj.elementClass === Const.OBJECT_CLASS_LINE) {
                xy.push(obj.point1.coords.usrCoords);
                xy.push(obj.point2.coords.usrCoords);
            } else if (obj.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                xy.push(obj.center.coords.usrCoords);
                if (obj.method === 'twoPoints') {
                    xy.push(obj.point2.coords.usrCoords);
                }
            } else if (obj.type === Const.OBJECT_TYPE_POLYGON) {
                len = obj.vertices.length - 1;
                for (i = 0; i < len; i++) {
                    xy.push(obj.vertices[i].coords.usrCoords);
                }
            } else if (obj.type === Const.OBJECT_TYPE_SECTOR) {
                xy.push(obj.point1.coords.usrCoords);
                xy.push(obj.point2.coords.usrCoords);
                xy.push(obj.point3.coords.usrCoords);
            } else if (Type.isPoint(obj) || obj.type === Const.OBJECT_TYPE_GLIDER) {
                xy.push(obj.coords.usrCoords);
            } else if (obj.elementClass === Const.OBJECT_CLASS_CURVE) {
                // if (Type.exists(obj.parents)) {
                //     len = obj.parents.length;
                //     if (len > 0) {
                //         for (i = 0; i < len; i++) {
                //             xy.push(this.select(obj.parents[i]).coords.usrCoords);
                //         }
                //     } else
                // }
                if (obj.points.length > 0) {
                    xy.push(obj.points[0].usrCoords);
                }
            } else {
                try {
                    xy.push(obj.coords.usrCoords);
                } catch (e) {
                    JXG.debug('JSXGraph+ saveStartPos: obj.coords.usrCoords not available: ' + e);
                }
            }

            len = xy.length;
            for (i = 0; i < len; i++) {
                targets.Zstart.push(xy[i][0]);
                targets.Xstart.push(xy[i][1]);
                targets.Ystart.push(xy[i][2]);
            }
        },

        mouseOriginMoveStart: function (evt) {
            var r, pos;

            r = this._isRequiredKeyPressed(evt, 'pan');
            if (r) {
                pos = this.getMousePosition(evt);
                this.initMoveOrigin(pos[0], pos[1]);
            }

            return r;
        },

        mouseOriginMove: function (evt) {
            var r = (this.mode === this.BOARD_MODE_MOVE_ORIGIN),
                pos;

            if (r) {
                pos = this.getMousePosition(evt);
                this.moveOrigin(pos[0], pos[1], true);
            }

            return r;
        },

        /**
         * Start moving the origin with one finger.
         * @private
         * @param  {Object} evt Event from touchStartListener
         * @return {Boolean}   returns if the origin is moved.
         */
        touchStartMoveOriginOneFinger: function (evt) {
            var touches = evt[JXG.touchProperty],
                conditions, pos;

            conditions = this.attr.pan.enabled &&
                !this.attr.pan.needtwofingers &&
                touches.length === 1;

            if (conditions) {
                pos = this.getMousePosition(evt, 0);
                this.initMoveOrigin(pos[0], pos[1]);
            }

            return conditions;
        },

        /**
         * Move the origin with one finger
         * @private
         * @param  {Object} evt Event from touchMoveListener
         * @return {Boolean}     returns if the origin is moved.
         */
        touchOriginMove: function (evt) {
            var r = (this.mode === this.BOARD_MODE_MOVE_ORIGIN),
                pos;

            if (r) {
                pos = this.getMousePosition(evt, 0);
                this.moveOrigin(pos[0], pos[1], true);
            }

            return r;
        },

        /**
         * Stop moving the origin with one finger
         * @return {null} null
         * @private
         */
        originMoveEnd: function () {
            this.updateQuality = this.BOARD_QUALITY_HIGH;
            this.mode = this.BOARD_MODE_NONE;
        },

        /**********************************************************
         *
         * Event Handler
         *
         **********************************************************/

        /**
         *  Add all possible event handlers to the board object
         */
        addEventHandlers: function () {
            if (Env.supportsPointerEvents()) {
                this.addPointerEventHandlers();
            } else {
                this.addMouseEventHandlers();
                this.addTouchEventHandlers();
            }

            // This one produces errors on IE
            //Env.addEvent(this.containerObj, 'contextmenu', function (e) { e.preventDefault(); return false;}, this);
            // This one works on IE, Firefox and Chromium with default configurations. On some Safari
            // or Opera versions the user must explicitly allow the deactivation of the context menu.
            if (this.containerObj !== null) {
                this.containerObj.oncontextmenu = function (e) {
                    if (Type.exists(e)) {
                        e.preventDefault();
                    }
                    return false;
                };
            }

            this.addFullscreenEventHandlers();
            this.addKeyboardEventHandlers();

            if (Env.isBrowser) {
                try {
                    // resizeObserver: triggered if size of the JSXGraph div changes.
                    this.startResizeObserver();
                } catch (err) {
                    // resize event: triggered if size of window changes
                    Env.addEvent(window, 'resize', this.resizeListener, this);
                    // intersectionObserver: triggered if JSXGraph becomes visible.
                    this.startIntersectionObserver();
                }
                // Scroll event: needs to be captured since on mobile devices
                // sometimes a header bar is displayed / hidden, which triggers a
                // resize event.
                Env.addEvent(window, 'scroll', this.scrollListener, this);
            }
        },

        /**
         * Remove all event handlers from the board object
         */
        removeEventHandlers: function () {
            this.removeMouseEventHandlers();
            this.removeTouchEventHandlers();
            this.removePointerEventHandlers();

            this.removeFullscreenEventHandlers();
            this.removeKeyboardEventHandlers();
            if (Env.isBrowser) {
                if (Type.exists(this.resizeObserver)) {
                    this.stopResizeObserver();
                } else {
                    Env.removeEvent(window, 'resize', this.resizeListener, this);
                    this.stopIntersectionObserver();
                }
                Env.removeEvent(window, 'scroll', this.scrollListener, this);
            }
        },

        /**
         * Registers the MSPointer* event handlers.
         */
        addPointerEventHandlers: function () {
            if (!this.hasPointerHandlers && Env.isBrowser) {
                var moveTarget = this.attr.movetarget || this.containerObj;

                if (window.navigator.msPointerEnabled) {  // IE10-
                    Env.addEvent(this.containerObj, 'MSPointerDown', this.pointerDownListener, this);
                    Env.addEvent(moveTarget, 'MSPointerMove', this.pointerMoveListener, this);
                } else {
                    Env.addEvent(this.containerObj, 'pointerdown', this.pointerDownListener, this);
                    Env.addEvent(moveTarget, 'pointermove', this.pointerMoveListener, this);
                }
                Env.addEvent(this.containerObj, 'mousewheel', this.mouseWheelListener, this);
                Env.addEvent(this.containerObj, 'DOMMouseScroll', this.mouseWheelListener, this);

                if (this.containerObj !== null) {
                    // This is needed for capturing touch events.
                    // It is also in jsxgraph.css, but one never knows...
                    this.containerObj.style.touchAction = 'none';
                }

                this.hasPointerHandlers = true;
            }
        },

        /**
         * Registers mouse move, down and wheel event handlers.
         */
        addMouseEventHandlers: function () {
            if (!this.hasMouseHandlers && Env.isBrowser) {
                var moveTarget = this.attr.movetarget || this.containerObj;

                Env.addEvent(this.containerObj, 'mousedown', this.mouseDownListener, this);
                Env.addEvent(moveTarget, 'mousemove', this.mouseMoveListener, this);

                Env.addEvent(this.containerObj, 'mousewheel', this.mouseWheelListener, this);
                Env.addEvent(this.containerObj, 'DOMMouseScroll', this.mouseWheelListener, this);

                this.hasMouseHandlers = true;
            }
        },

        /**
         * Register touch start and move and gesture start and change event handlers.
         * @param {Boolean} appleGestures If set to false the gesturestart and gesturechange event handlers
         * will not be registered.
         *
         * Since iOS 13, touch events were abandoned in favour of pointer events
         */
        addTouchEventHandlers: function (appleGestures) {
            if (!this.hasTouchHandlers && Env.isBrowser) {
                var moveTarget = this.attr.movetarget || this.containerObj;

                Env.addEvent(this.containerObj, 'touchstart', this.touchStartListener, this);
                Env.addEvent(moveTarget, 'touchmove', this.touchMoveListener, this);

                /*
                if (!Type.exists(appleGestures) || appleGestures) {
                    // Gesture listener are called in touchStart and touchMove.
                    //Env.addEvent(this.containerObj, 'gesturestart', this.gestureStartListener, this);
                    //Env.addEvent(this.containerObj, 'gesturechange', this.gestureChangeListener, this);
                }
                */

                this.hasTouchHandlers = true;
            }
        },

        /**
         * Add fullscreen events which update the CSS transformation matrix to correct
         * the mouse/touch/pointer positions in case of CSS transformations.
         */
        addFullscreenEventHandlers: function() {
            var i,
                // standard/Edge, firefox, chrome/safari, IE11
                events = ['fullscreenchange', 'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange'],
                le = events.length;

            if (!this.hasFullsceenEventHandlers && Env.isBrowser) {
                for (i = 0; i < le; i++) {
                    Env.addEvent(this.document, events[i], this.fullscreenListener, this);
                }
                this.hasFullsceenEventHandlers = true;
            }
        },

        addKeyboardEventHandlers: function() {
            if (this.attr.keyboard.enabled && !this.hasKeyboardHandlers && Env.isBrowser) {
                Env.addEvent(this.containerObj, 'keydown', this.keyDownListener, this);
                Env.addEvent(this.containerObj, 'focusin', this.keyFocusInListener, this);
                Env.addEvent(this.containerObj, 'focusout', this.keyFocusOutListener, this);
                this.hasKeyboardHandlers = true;
            }
        },

        /**
         * Remove all registered touch event handlers.
         */
        removeKeyboardEventHandlers: function () {
            if (this.hasKeyboardHandlers && Env.isBrowser) {
                Env.removeEvent(this.containerObj, 'keydown', this.keyDownListener, this);
                Env.removeEvent(this.containerObj, 'focusin', this.keyFocusInListener, this);
                Env.removeEvent(this.containerObj, 'focusout', this.keyFocusOutListener, this);
                this.hasKeyboardHandlers = false;
            }
        },

        /**
         * Remove all registered event handlers regarding fullscreen mode.
         */
        removeFullscreenEventHandlers: function() {
            var i,
                // standard/Edge, firefox, chrome/safari, IE11
                events = ['fullscreenchange', 'mozfullscreenchange', 'webkitfullscreenchange', 'msfullscreenchange'],
                le = events.length;

            if (this.hasFullsceenEventHandlers && Env.isBrowser) {
                for (i = 0; i < le; i++) {
                    Env.removeEvent(this.document, events[i], this.fullscreenListener, this);
                }
                this.hasFullsceenEventHandlers = false;
            }
        },

        /**
         * Remove MSPointer* Event handlers.
         */
        removePointerEventHandlers: function () {
            if (this.hasPointerHandlers && Env.isBrowser) {
                var moveTarget = this.attr.movetarget || this.containerObj;

                if (window.navigator.msPointerEnabled) {  // IE10-
                    Env.removeEvent(this.containerObj, 'MSPointerDown', this.pointerDownListener, this);
                    Env.removeEvent(moveTarget, 'MSPointerMove', this.pointerMoveListener, this);
                } else {
                    Env.removeEvent(this.containerObj, 'pointerdown', this.pointerDownListener, this);
                    Env.removeEvent(moveTarget, 'pointermove', this.pointerMoveListener, this);
                }

                Env.removeEvent(this.containerObj, 'mousewheel', this.mouseWheelListener, this);
                Env.removeEvent(this.containerObj, 'DOMMouseScroll', this.mouseWheelListener, this);

                if (this.hasPointerUp) {
                    if (window.navigator.msPointerEnabled) {  // IE10-
                        Env.removeEvent(this.document, 'MSPointerUp',   this.pointerUpListener, this);
                    } else {
                        Env.removeEvent(this.document, 'pointerup',     this.pointerUpListener, this);
                        Env.removeEvent(this.document, 'pointercancel', this.pointerUpListener, this);
                    }
                    this.hasPointerUp = false;
                }

                this.hasPointerHandlers = false;
            }
        },

        /**
         * De-register mouse event handlers.
         */
        removeMouseEventHandlers: function () {
            if (this.hasMouseHandlers && Env.isBrowser) {
                var moveTarget = this.attr.movetarget || this.containerObj;

                Env.removeEvent(this.containerObj, 'mousedown', this.mouseDownListener, this);
                Env.removeEvent(moveTarget, 'mousemove', this.mouseMoveListener, this);

                if (this.hasMouseUp) {
                    Env.removeEvent(this.document, 'mouseup', this.mouseUpListener, this);
                    this.hasMouseUp = false;
                }

                Env.removeEvent(this.containerObj, 'mousewheel', this.mouseWheelListener, this);
                Env.removeEvent(this.containerObj, 'DOMMouseScroll', this.mouseWheelListener, this);

                this.hasMouseHandlers = false;
            }
        },

        /**
         * Remove all registered touch event handlers.
         */
        removeTouchEventHandlers: function () {
            if (this.hasTouchHandlers && Env.isBrowser) {
                var moveTarget = this.attr.movetarget || this.containerObj;

                Env.removeEvent(this.containerObj, 'touchstart', this.touchStartListener, this);
                Env.removeEvent(moveTarget, 'touchmove', this.touchMoveListener, this);

                if (this.hasTouchEnd) {
                    Env.removeEvent(this.document, 'touchend', this.touchEndListener, this);
                    this.hasTouchEnd = false;
                }

                this.hasTouchHandlers = false;
            }
        },

        /**
         * Handler for click on left arrow in the navigation bar
         * @returns {JXG.Board} Reference to the board
         */
        clickLeftArrow: function () {
            this.moveOrigin(this.origin.scrCoords[1] + this.canvasWidth * 0.1, this.origin.scrCoords[2]);
            return this;
        },

        /**
         * Handler for click on right arrow in the navigation bar
         * @returns {JXG.Board} Reference to the board
         */
        clickRightArrow: function () {
            this.moveOrigin(this.origin.scrCoords[1] - this.canvasWidth * 0.1, this.origin.scrCoords[2]);
            return this;
        },

        /**
         * Handler for click on up arrow in the navigation bar
         * @returns {JXG.Board} Reference to the board
         */
        clickUpArrow: function () {
            this.moveOrigin(this.origin.scrCoords[1], this.origin.scrCoords[2] - this.canvasHeight * 0.1);
            return this;
        },

        /**
         * Handler for click on down arrow in the navigation bar
         * @returns {JXG.Board} Reference to the board
         */
        clickDownArrow: function () {
            this.moveOrigin(this.origin.scrCoords[1], this.origin.scrCoords[2] + this.canvasHeight * 0.1);
            return this;
        },

        /**
         * Triggered on iOS/Safari while the user inputs a gesture (e.g. pinch) and is used to zoom into the board.
         * Works on iOS/Safari and Android.
         * @param {Event} evt Browser event object
         * @returns {Boolean}
         */
        gestureChangeListener: function (evt) {
            var c,
                dir1 = [],
                dir2 = [],
                angle,
                mi = 10,
                isPinch = false,
                // Save zoomFactors
                zx = this.attr.zoom.factorx,
                zy = this.attr.zoom.factory,
                factor,
                dist,
                dx, dy, theta, cx, cy, bound;

            if (this.mode !== this.BOARD_MODE_ZOOM) {
                return true;
            }
            evt.preventDefault();

            dist = Geometry.distance([evt.touches[0].clientX, evt.touches[0].clientY],
                [evt.touches[1].clientX, evt.touches[1].clientY], 2);

            // Android pinch to zoom
            // evt.scale was available in iOS touch events (pre iOS 13)
            // evt.scale is undefined in Android
            if (evt.scale === undefined) {
                evt.scale = dist / this.prevDist;
            }

            if (!Type.exists(this.prevCoords)) {
                return false;
            }
            // Compute the angle of the two finger directions
            dir1 = [evt.touches[0].clientX - this.prevCoords[0][0],
                    evt.touches[0].clientY - this.prevCoords[0][1]];
            dir2 = [evt.touches[1].clientX - this.prevCoords[1][0],
                    evt.touches[1].clientY - this.prevCoords[1][1]];

            if ((dir1[0] * dir1[0] + dir1[1] * dir1[1] < mi * mi) &&
                (dir2[0] * dir2[0] + dir2[1] * dir2[1] < mi * mi)) {
                    return false;
            }

            angle = Geometry.rad(dir1, [0,0], dir2);
            if (this.isPreviousGesture !== 'pan' &&
                Math.abs(angle) > Math.PI * 0.2 &&
                Math.abs(angle) < Math.PI * 1.8) {
                isPinch = true;
            }

            if (this.isPreviousGesture !== 'pan' && !isPinch) {
                if (Math.abs(evt.scale) < 0.77 || Math.abs(evt.scale) > 1.3) {
                    isPinch = true;
                }
            }

            factor = evt.scale / this.prevScale;
            this.prevScale = evt.scale;
            this.prevCoords = [[evt.touches[0].clientX, evt.touches[0].clientY],
                               [evt.touches[1].clientX, evt.touches[1].clientY]];

            c = new Coords(Const.COORDS_BY_SCREEN, this.getMousePosition(evt, 0), this);

            if (this.attr.pan.enabled &&
                this.attr.pan.needtwofingers &&
                !isPinch) {
                // Pan detected

                this.isPreviousGesture = 'pan';

                this.moveOrigin(c.scrCoords[1], c.scrCoords[2], true);
            } else if (this.attr.zoom.enabled &&
                        Math.abs(factor - 1.0) < 0.5) {
                // Pinch detected

                if (this.attr.zoom.pinchhorizontal || this.attr.zoom.pinchvertical) {
                    dx = Math.abs(evt.touches[0].clientX - evt.touches[1].clientX);
                    dy = Math.abs(evt.touches[0].clientY - evt.touches[1].clientY);
                    theta = Math.abs(Math.atan2(dy, dx));
                    bound = Math.PI * this.attr.zoom.pinchsensitivity / 90.0;
                }

                if (this.attr.zoom.pinchhorizontal && theta < bound) {
                    this.attr.zoom.factorx = factor;
                    this.attr.zoom.factory = 1.0;
                    cx = 0;
                    cy = 0;
                } else if (this.attr.zoom.pinchvertical && Math.abs(theta - Math.PI * 0.5) < bound) {
                    this.attr.zoom.factorx = 1.0;
                    this.attr.zoom.factory = factor;
                    cx = 0;
                    cy = 0;
                } else {
                    this.attr.zoom.factorx = factor;
                    this.attr.zoom.factory = factor;
                    cx = c.usrCoords[1];
                    cy = c.usrCoords[2];
                }

                this.zoomIn(cx, cy);

                // Restore zoomFactors
                this.attr.zoom.factorx = zx;
                this.attr.zoom.factory = zy;
            }

            return false;
        },

        /**
         * Called by iOS/Safari as soon as the user starts a gesture. Works natively on iOS/Safari,
         * on Android we emulate it.
         * @param {Event} evt
         * @returns {Boolean}
         */
        gestureStartListener: function (evt) {
            var pos;

            evt.preventDefault();
            this.prevScale = 1.0;
            // Android pinch to zoom
            this.prevDist = Geometry.distance([evt.touches[0].clientX, evt.touches[0].clientY],
                            [evt.touches[1].clientX, evt.touches[1].clientY], 2);
            this.prevCoords = [[evt.touches[0].clientX, evt.touches[0].clientY],
                               [evt.touches[1].clientX, evt.touches[1].clientY]];
            this.isPreviousGesture = 'none';

            // If pinch-to-zoom is interpreted as panning
            // we have to prepare move origin
            pos = this.getMousePosition(evt, 0);
            this.initMoveOrigin(pos[0], pos[1]);

            this.mode = this.BOARD_MODE_ZOOM;
            return false;
        },

        /**
         * Test if the required key combination is pressed for wheel zoom, move origin and
         * selection
         * @private
         * @param  {Object}  evt    Mouse or pen event
         * @param  {String}  action String containing the action: 'zoom', 'pan', 'selection'.
         * Corresponds to the attribute subobject.
         * @return {Boolean}        true or false.
         */
        _isRequiredKeyPressed: function (evt, action) {
            var obj = this.attr[action];
            if (!obj.enabled) {
                return false;
            }

            if (((obj.needshift && evt.shiftKey) || (!obj.needshift && !evt.shiftKey)) &&
                ((obj.needctrl && evt.ctrlKey) || (!obj.needctrl && !evt.ctrlKey))
            )  {
                return true;
            }

            return false;
        },

        /*
         * Pointer events
         */

        /**
         *
         * Check if pointer event is already registered in {@link JXG.Board#_board_touches}.
         *
         * @param  {Object} evt Event object
         * @return {Boolean} true if down event has already been sent.
         * @private
         */
         _isPointerRegistered: function(evt) {
            var i, len = this._board_touches.length;

            for (i = 0; i < len; i++) {
                if (this._board_touches[i].pointerId === evt.pointerId) {
                    return true;
                }
            }
            return false;
        },

        /**
         *
         * Store the position of a pointer event.
         * If not yet done, registers a pointer event in {@link JXG.Board#_board_touches}.
         * Allows to follow the path of that finger on the screen.
         * Only two simultaneous touches are supported.
         *
         * @param {Object} evt Event object
         * @returns {JXG.Board} Reference to the board
         * @private
         */
         _pointerStorePosition: function (evt) {
            var i, found;

            for (i = 0, found = false; i < this._board_touches.length; i++) {
                if (this._board_touches[i].pointerId === evt.pointerId) {
                    this._board_touches[i].clientX = evt.clientX;
                    this._board_touches[i].clientY = evt.clientY;
                    found = true;
                    break;
                }
            }

            // Restrict the number of simultaneous touches to 2
            if (!found && this._board_touches.length < 2) {
                this._board_touches.push({
                    pointerId: evt.pointerId,
                    clientX: evt.clientX,
                    clientY: evt.clientY
                });
            }

            return this;
        },

        /**
         * Deregisters a pointer event in {@link JXG.Board#_board_touches}.
         * It happens if a finger has been lifted from the screen.
         *
         * @param {Object} evt Event object
         * @returns {JXG.Board} Reference to the board
         * @private
         */
        _pointerRemoveTouches: function (evt) {
            var i;
            for (i = 0; i < this._board_touches.length; i++) {
                if (this._board_touches[i].pointerId === evt.pointerId) {
                    this._board_touches.splice(i, 1);
                    break;
                }
            }

            return this;
        },

        /**
         * Remove all registered fingers from {@link JXG.Board#_board_touches}.
         * This might be necessary if too many fingers have been registered.
         * @returns {JXG.Board} Reference to the board
         * @private
         */
        _pointerClearTouches: function() {
            if (this._board_touches.length > 0) {
                this.dehighlightAll();
            }
            this.updateQuality = this.BOARD_QUALITY_HIGH;
            this.mode = this.BOARD_MODE_NONE;
            this._board_touches = [];
            this.touches = [];
        },

        /**
         * Determine which input device is used for this action.
         * Possible devices are 'touch', 'pen' and 'mouse'.
         * This affects the precision and certain events.
         * In case of no browser, 'mouse' is used.
         *
         * @see JXG.Board#pointerDownListener
         * @see JXG.Board#pointerMoveListener
         * @see JXG.Board#initMoveObject
         * @see JXG.Board#moveObject
         *
         * @param {Event} evt The browsers event object.
         * @returns {String} 'mouse', 'pen', or 'touch'
         * @private
         */
        _getPointerInputDevice: function(evt) {
            if (Env.isBrowser) {
                if (evt.pointerType === 'touch' ||        // New
                    (window.navigator.msMaxTouchPoints && // Old
                        window.navigator.msMaxTouchPoints > 1)) {
                    return 'touch';
                }
                if (evt.pointerType === 'mouse') {
                    return 'mouse';
                }
                if (evt.pointerType === 'pen') {
                    return 'pen';
                }
            }
            return 'mouse';
        },

        /**
         * This method is called by the browser when a pointing device is pressed on the screen.
         * @param {Event} evt The browsers event object.
         * @param {Object} object If the object to be dragged is already known, it can be submitted via this parameter
         * @returns {Boolean} ...
         */
        pointerDownListener: function (evt, object) {
            var i, j, k, pos, elements, sel,
                target_obj,
                type = 'mouse', // Used in case of no browser
                found, target;

            // Fix for Firefox browser: When using a second finger, the
            // touch event for the first finger is sent again.
            if (!object && this._isPointerRegistered(evt)) {
                return false;
            }

            if (!object && evt.isPrimary) {
                // First finger down. To be on the safe side this._board_touches is cleared.
                this._pointerClearTouches();
            }

            if (!this.hasPointerUp) {
                if (window.navigator.msPointerEnabled) {  // IE10-
                    Env.addEvent(this.document, 'MSPointerUp',   this.pointerUpListener, this);
                } else {
                    // 'pointercancel' is fired e.g. if the finger leaves the browser and drags down the system menu on Android
                    Env.addEvent(this.document, 'pointerup',     this.pointerUpListener, this);
                    Env.addEvent(this.document, 'pointercancel', this.pointerUpListener, this);
                }
                this.hasPointerUp = true;
            }

            if (this.hasMouseHandlers) {
                this.removeMouseEventHandlers();
            }

            if (this.hasTouchHandlers) {
                this.removeTouchEventHandlers();
            }

            // Prevent accidental selection of text
            if (this.document.selection && Type.isFunction(this.document.selection.empty)) {
                this.document.selection.empty();
            } else if (window.getSelection) {
                sel = window.getSelection();
                if (sel.removeAllRanges) {
                    try {
                        sel.removeAllRanges();
                    } catch (e) {}
                }
            }

            // Mouse, touch or pen device
            this._inputDevice = this._getPointerInputDevice(evt);
            type = this._inputDevice;
            this.options.precision.hasPoint = this.options.precision[type];

            // Handling of multi touch with pointer events should be easier than the touch events.
            // Every pointer device has its own pointerId, e.g. the mouse
            // always has id 1 or 0, fingers and pens get unique ids every time a pointerDown event is fired and they will
            // keep this id until a pointerUp event is fired. What we have to do here is:
            //  1. collect all elements under the current pointer
            //  2. run through the touches control structure
            //    a. look for the object collected in step 1.
            //    b. if an object is found, check the number of pointers. If appropriate, add the pointer.
            pos = this.getMousePosition(evt);

            // selection
            this._testForSelection(evt);
            if (this.selectingMode) {
                this._startSelecting(pos);
                this.triggerEventHandlers(['touchstartselecting', 'pointerstartselecting', 'startselecting'], [evt]);
                return;     // don't continue as a normal click
            }

            if (this.attr.drag.enabled && object) {
                elements = [ object ];
                this.mode = this.BOARD_MODE_DRAG;
            } else {
                elements = this.initMoveObject(pos[0], pos[1], evt, type);
            }

            target_obj = {
                num: evt.pointerId,
                X: pos[0],
                Y: pos[1],
                Xprev: NaN,
                Yprev: NaN,
                Xstart: [],
                Ystart: [],
                Zstart: []
            };

            // If no draggable object can be found, get out here immediately
            if (elements.length > 0) {
                // check touches structure
                target = elements[elements.length - 1];
                found = false;

                // Reminder: this.touches is the list of elements which
                // currently "possess" a pointer (mouse, pen, finger)
                for (i = 0; i < this.touches.length; i++) {
                    // An element receives a further touch, i.e.
                    // the target is already in our touches array, add the pointer to the existing touch
                    if (this.touches[i].obj === target) {
                        j = i;
                        k = this.touches[i].targets.push(target_obj) - 1;
                        found = true;
                        break;
                    }
                }
                if (!found) {
                    // An new element hae been touched.
                    k = 0;
                    j = this.touches.push({
                        obj: target,
                        targets: [target_obj]
                    }) - 1;
                }

                this.dehighlightAll();
                target.highlight(true);

                this.saveStartPos(target, this.touches[j].targets[k]);

                // Prevent accidental text selection
                // this could get us new trouble: input fields, links and drop down boxes placed as text
                // on the board don't work anymore.
                if (evt && evt.preventDefault) {
                    evt.preventDefault();
                } else if (window.event) {
                    window.event.returnValue = false;
                }
            }

            if (this.touches.length > 0) {
                evt.preventDefault();
                evt.stopPropagation();
            }

            if (!Env.isBrowser) {
                return false;
            }
            if (this._getPointerInputDevice(evt) !== 'touch') {
                if (this.mode === this.BOARD_MODE_NONE) {
                    this.mouseOriginMoveStart(evt);
                }
            } else {
                this._pointerStorePosition(evt);
                evt.touches = this._board_touches;

                // Touch events on empty areas of the board are handled here, see also touchStartListener
                // 1. case: one finger. If allowed, this triggers pan with one finger
                if (evt.touches.length === 1 &&
                    this.mode === this.BOARD_MODE_NONE &&
                    this.touchStartMoveOriginOneFinger(evt)) {
                        // Empty by purpose
                } else if (evt.touches.length === 2 &&
                            (this.mode === this.BOARD_MODE_NONE || this.mode === this.BOARD_MODE_MOVE_ORIGIN)
                        ) {
                    // 2. case: two fingers: pinch to zoom or pan with two fingers needed.
                    // This happens when the second finger hits the device. First, the
                    // "one finger pan mode" has to be cancelled.
                    if (this.mode === this.BOARD_MODE_MOVE_ORIGIN) {
                        this.originMoveEnd();
                    }

                    this.gestureStartListener(evt);
                }
            }

            this.triggerEventHandlers(['touchstart', 'down', 'pointerdown', 'MSPointerDown'], [evt]);
            return false;
        },

        // /**
        //  * Called if pointer leaves an HTML tag. It is called by the inner-most tag.
        //  * That means, if a JSXGraph text, i.e. an HTML div, is placed close
        //  * to the border of the board, this pointerout event will be ignored.
        //  * @param  {Event} evt
        //  * @return {Boolean}
        //  */
        // pointerOutListener: function (evt) {
        //     if (evt.target === this.containerObj ||
        //         (this.renderer.type === 'svg' && evt.target === this.renderer.foreignObjLayer)) {
        //         this.pointerUpListener(evt);
        //     }
        //     return this.mode === this.BOARD_MODE_NONE;
        // },

        /**
         * Called periodically by the browser while the user moves a pointing device across the screen.
         * @param {Event} evt
         * @returns {Boolean}
         */
        pointerMoveListener: function (evt) {
            var i, j, pos, touchTargets,
                type = 'mouse'; // in case of no browser

            if (this._getPointerInputDevice(evt) === 'touch' && !this._isPointerRegistered(evt)) {
                // Test, if there was a previous down event of this _getPointerId
                // (in case it is a touch event).
                // Otherwise this move event is ignored. This is necessary e.g. for sketchometry.
                return this.BOARD_MODE_NONE;
            }

            if (!this.checkFrameRate(evt)) {
                return false;
            }

            if (this.mode !== this.BOARD_MODE_DRAG) {
                this.dehighlightAll();
                this.displayInfobox(false);
            }

            if (this.mode !== this.BOARD_MODE_NONE) {
                evt.preventDefault();
                evt.stopPropagation();
            }

            this.updateQuality = this.BOARD_QUALITY_LOW;
            // Mouse, touch or pen device
            this._inputDevice = this._getPointerInputDevice(evt);
            type = this._inputDevice;
            this.options.precision.hasPoint = this.options.precision[type];

            // selection
            if (this.selectingMode) {
                pos = this.getMousePosition(evt);
                this._moveSelecting(pos);
                this.triggerEventHandlers(['touchmoveselecting', 'moveselecting', 'pointermoveselecting'], [evt, this.mode]);
            } else if (!this.mouseOriginMove(evt)) {
                if (this.mode === this.BOARD_MODE_DRAG) {
                    // Run through all jsxgraph elements which are touched by at least one finger.
                    for (i = 0; i < this.touches.length; i++) {
                        touchTargets = this.touches[i].targets;
                        // Run through all touch events which have been started on this jsxgraph element.
                        for (j = 0; j < touchTargets.length; j++) {
                            if (touchTargets[j].num === evt.pointerId) {

                                pos = this.getMousePosition(evt);
                                touchTargets[j].X = pos[0];
                                touchTargets[j].Y = pos[1];

                                if (touchTargets.length === 1) {
                                    // Touch by one finger: this is possible for all elements that can be dragged
                                    this.moveObject(pos[0], pos[1], this.touches[i], evt, type);
                                } else if (touchTargets.length === 2) {
                                    // Touch by two fingers: e.g. moving lines
                                    this.twoFingerMove(this.touches[i], evt.pointerId, evt);

                                    touchTargets[j].Xprev = pos[0];
                                    touchTargets[j].Yprev = pos[1];
                                }

                                // There is only one pointer in the evt object, so there's no point in looking further
                                break;
                            }
                        }
                    }
                } else {
                    if (this._getPointerInputDevice(evt) === 'touch') {
                        this._pointerStorePosition(evt);

                        if (this._board_touches.length === 2) {
                            evt.touches = this._board_touches;
                            this.gestureChangeListener(evt);
                        }
                    }

                    // Move event without dragging an element
                    pos = this.getMousePosition(evt);
                    this.highlightElements(pos[0], pos[1], evt, -1);
                }
            }

            // Hiding the infobox is commented out, since it prevents showing the infobox
            // on IE 11+ on 'over'
            //if (this.mode !== this.BOARD_MODE_DRAG) {
                //this.displayInfobox(false);
            //}
            this.triggerEventHandlers(['touchmove', 'move', 'pointermove', 'MSPointerMove'], [evt, this.mode]);
            this.updateQuality = this.BOARD_QUALITY_HIGH;

            return this.mode === this.BOARD_MODE_NONE;
        },

        /**
         * Triggered as soon as the user stops touching the device with at least one finger.
         * @param {Event} evt
         * @returns {Boolean}
         */
        pointerUpListener: function (evt) {
            var i, j, found, touchTargets;

            this.triggerEventHandlers(['touchend', 'up', 'pointerup', 'MSPointerUp'], [evt]);
            this.displayInfobox(false);

            if (evt) {
                for (i = 0; i < this.touches.length; i++) {
                    touchTargets = this.touches[i].targets;
                    for (j = 0; j < touchTargets.length; j++) {
                        if (touchTargets[j].num === evt.pointerId) {
                            touchTargets.splice(j, 1);
                            if (touchTargets.length === 0) {
                                this.touches.splice(i, 1);
                            }
                            break;
                        }
                    }
                }
            }

            // selection
            if (this.selectingMode) {
                this._stopSelecting(evt);
                this.triggerEventHandlers(['touchstopselecting', 'pointerstopselecting', 'stopselecting'], [evt]);
            } else {
                for (i = this.downObjects.length - 1; i > -1; i--) {
                    found = false;
                    for (j = 0; j < this.touches.length; j++) {
                        if (this.touches[j].obj.id === this.downObjects[i].id) {
                            found = true;
                        }
                    }
                    if (!found) {
                        this.downObjects[i].triggerEventHandlers(['touchend', 'up', 'pointerup', 'MSPointerUp'], [evt]);
                        this.downObjects[i].snapToGrid();
                        this.downObjects[i].snapToPoints();
                        this.downObjects.splice(i, 1);
                    }
                }
            }

            // this._pointerRemoveTouches(evt);
            // if (this._board_touches.length === 0) {
                if (this.hasPointerUp) {
                    if (window.navigator.msPointerEnabled) {  // IE10-
                        Env.removeEvent(this.document, 'MSPointerUp',   this.pointerUpListener, this);
                    } else {
                        Env.removeEvent(this.document, 'pointerup',     this.pointerUpListener, this);
                        Env.removeEvent(this.document, 'pointercancel', this.pointerUpListener, this);
                    }
                    this.hasPointerUp = false;
                }

                // this.dehighlightAll();
                // this.updateQuality = this.BOARD_QUALITY_HIGH;
                // this.mode = this.BOARD_MODE_NONE;

                this.originMoveEnd();
                this.update();
            // }
            // After one finger leaves the screen the gesture is stopped.
            this._pointerClearTouches();
            return true;
        },

        /**
         * Touch-Events
         */

        /**
         * This method is called by the browser when a finger touches the surface of the touch-device.
         * @param {Event} evt The browsers event object.
         * @returns {Boolean} ...
         */
        touchStartListener: function (evt) {
            var i, pos, elements, j, k,
                eps = this.options.precision.touch,
                obj, found, targets,
                evtTouches = evt[JXG.touchProperty],
                target, touchTargets;

            if (!this.hasTouchEnd) {
                Env.addEvent(this.document, 'touchend', this.touchEndListener, this);
                this.hasTouchEnd = true;
            }

            // Do not remove mouseHandlers, since Chrome on win tablets sends mouseevents if used with pen.
            //if (this.hasMouseHandlers) { this.removeMouseEventHandlers(); }

            // prevent accidental selection of text
            if (this.document.selection && Type.isFunction(this.document.selection.empty)) {
                this.document.selection.empty();
            } else if (window.getSelection) {
                window.getSelection().removeAllRanges();
            }

            // multitouch
            this._inputDevice = 'touch';
            this.options.precision.hasPoint = this.options.precision.touch;

            // This is the most critical part. first we should run through the existing touches and collect all targettouches that don't belong to our
            // previous touches. once this is done we run through the existing touches again and watch out for free touches that can be attached to our existing
            // touches, e.g. we translate (parallel translation) a line with one finger, now a second finger is over this line. this should change the operation to
            // a rotational translation. or one finger moves a circle, a second finger can be attached to the circle: this now changes the operation from translation to
            // stretching. as a last step we're going through the rest of the targettouches and initiate new move operations:
            //  * points have higher priority over other elements.
            //  * if we find a targettouch over an element that could be transformed with more than one finger, we search the rest of the targettouches, if they are over
            //    this element and add them.
            // ADDENDUM 11/10/11:
            //  (1) run through the touches control object,
            //  (2) try to find the targetTouches for every touch. on touchstart only new touches are added, hence we can find a targettouch
            //      for every target in our touches objects
            //  (3) if one of the targettouches was bound to a touches targets array, mark it
            //  (4) run through the targettouches. if the targettouch is marked, continue. otherwise check for elements below the targettouch:
            //      (a) if no element could be found: mark the target touches and continue
            //      --- in the following cases, "init" means:
            //           (i) check if the element is already used in another touches element, if so, mark the targettouch and continue
            //          (ii) if not, init a new touches element, add the targettouch to the touches property and mark it
            //      (b) if the element is a point, init
            //      (c) if the element is a line, init and try to find a second targettouch on that line. if a second one is found, add and mark it
            //      (d) if the element is a circle, init and try to find TWO other targettouches on that circle. if only one is found, mark it and continue. otherwise
            //          add both to the touches array and mark them.
            for (i = 0; i < evtTouches.length; i++) {
                evtTouches[i].jxg_isused = false;
            }

            for (i = 0; i < this.touches.length; i++) {
                touchTargets = this.touches[i].targets;
                for (j = 0; j < touchTargets.length; j++) {
                    touchTargets[j].num = -1;
                    eps = this.options.precision.touch;

                    do {
                        for (k = 0; k < evtTouches.length; k++) {
                            // find the new targettouches
                            if (Math.abs(Math.pow(evtTouches[k].screenX - touchTargets[j].X, 2) +
                                    Math.pow(evtTouches[k].screenY - touchTargets[j].Y, 2)) < eps * eps) {
                                touchTargets[j].num = k;
                                touchTargets[j].X = evtTouches[k].screenX;
                                touchTargets[j].Y = evtTouches[k].screenY;
                                evtTouches[k].jxg_isused = true;
                                break;
                            }
                        }

                        eps *= 2;

                    } while (touchTargets[j].num === -1 &&
                             eps < this.options.precision.touchMax);

                    if (touchTargets[j].num === -1) {
                        JXG.debug('i couldn\'t find a targettouches for target no ' + j + ' on ' + this.touches[i].obj.name + ' (' + this.touches[i].obj.id + '). Removed the target.');
                        JXG.debug('eps = ' + eps + ', touchMax = ' + Options.precision.touchMax);
                        touchTargets.splice(i, 1);
                    }

                }
            }

            // we just re-mapped the targettouches to our existing touches list.
            // now we have to initialize some touches from additional targettouches
            for (i = 0; i < evtTouches.length; i++) {
                if (!evtTouches[i].jxg_isused) {

                    pos = this.getMousePosition(evt, i);
                    // selection
                    // this._testForSelection(evt); // we do not have shift or ctrl keys yet.
                    if (this.selectingMode) {
                        this._startSelecting(pos);
                        this.triggerEventHandlers(['touchstartselecting', 'startselecting'], [evt]);
                        evt.preventDefault();
                        evt.stopPropagation();
                        this.options.precision.hasPoint = this.options.precision.mouse;
                        return this.touches.length > 0; // don't continue as a normal click
                    }

                    elements = this.initMoveObject(pos[0], pos[1], evt, 'touch');
                    if (elements.length !== 0) {
                        obj = elements[elements.length - 1];
                        target = {num: i,
                            X: evtTouches[i].screenX,
                            Y: evtTouches[i].screenY,
                            Xprev: NaN,
                            Yprev: NaN,
                            Xstart: [],
                            Ystart: [],
                            Zstart: []
                        };

                        if (Type.isPoint(obj) ||
                                obj.elementClass === Const.OBJECT_CLASS_TEXT ||
                                obj.type === Const.OBJECT_TYPE_TICKS ||
                                obj.type === Const.OBJECT_TYPE_IMAGE) {
                            // It's a point, so it's single touch, so we just push it to our touches
                            targets = [target];

                            // For the UNDO/REDO of object moves
                            this.saveStartPos(obj, targets[0]);

                            this.touches.push({ obj: obj, targets: targets });
                            obj.highlight(true);

                        } else if (obj.elementClass === Const.OBJECT_CLASS_LINE ||
                                obj.elementClass === Const.OBJECT_CLASS_CIRCLE ||
                                obj.elementClass === Const.OBJECT_CLASS_CURVE ||
                                obj.type === Const.OBJECT_TYPE_POLYGON) {
                            found = false;

                            // first check if this geometric object is already captured in this.touches
                            for (j = 0; j < this.touches.length; j++) {
                                if (obj.id === this.touches[j].obj.id) {
                                    found = true;
                                    // only add it, if we don't have two targets in there already
                                    if (this.touches[j].targets.length === 1) {
                                        // For the UNDO/REDO of object moves
                                        this.saveStartPos(obj, target);
                                        this.touches[j].targets.push(target);
                                    }

                                    evtTouches[i].jxg_isused = true;
                                }
                            }

                            // we couldn't find it in touches, so we just init a new touches
                            // IF there is a second touch targetting this line, we will find it later on, and then add it to
                            // the touches control object.
                            if (!found) {
                                targets = [target];

                                // For the UNDO/REDO of object moves
                                this.saveStartPos(obj, targets[0]);
                                this.touches.push({ obj: obj, targets: targets });
                                obj.highlight(true);
                            }
                        }
                    }

                    evtTouches[i].jxg_isused = true;
                }
            }

            if (this.touches.length > 0) {
                evt.preventDefault();
                evt.stopPropagation();
            }

            // Touch events on empty areas of the board are handled here:
            // 1. case: one finger. If allowed, this triggers pan with one finger
            if (evtTouches.length === 1 && this.mode === this.BOARD_MODE_NONE && this.touchStartMoveOriginOneFinger(evt)) {
            } else if (evtTouches.length === 2 &&
                        (this.mode === this.BOARD_MODE_NONE || this.mode === this.BOARD_MODE_MOVE_ORIGIN)
                    ) {
                // 2. case: two fingers: pinch to zoom or pan with two fingers needed.
                // This happens when the second finger hits the device. First, the
                // "one finger pan mode" has to be cancelled.
                if (this.mode === this.BOARD_MODE_MOVE_ORIGIN) {
                    this.originMoveEnd();
                }
                this.gestureStartListener(evt);
            }

            this.options.precision.hasPoint = this.options.precision.mouse;
            this.triggerEventHandlers(['touchstart', 'down'], [evt]);

            return false;
            //return this.touches.length > 0;
        },

        /**
         * Called periodically by the browser while the user moves his fingers across the device.
         * @param {Event} evt
         * @returns {Boolean}
         */
        touchMoveListener: function (evt) {
            var i, pos1, pos2,
                touchTargets,
                evtTouches = evt[JXG.touchProperty];

            if (!this.checkFrameRate(evt)) {
                return false;
            }

            if (this.mode !== this.BOARD_MODE_NONE) {
                evt.preventDefault();
                evt.stopPropagation();
            }

            if (this.mode !== this.BOARD_MODE_DRAG) {
                this.dehighlightAll();
                this.displayInfobox(false);
            }

            this._inputDevice = 'touch';
            this.options.precision.hasPoint = this.options.precision.touch;
            this.updateQuality = this.BOARD_QUALITY_LOW;

            // selection
            if (this.selectingMode) {
                for (i = 0; i < evtTouches.length; i++) {
                    if (!evtTouches[i].jxg_isused) {
                        pos1 = this.getMousePosition(evt, i);
                        this._moveSelecting(pos1);
                        this.triggerEventHandlers(['touchmoves', 'moveselecting'], [evt, this.mode]);
                        break;
                    }
                }
            } else {
                if (!this.touchOriginMove(evt)) {
                    if (this.mode === this.BOARD_MODE_DRAG) {
                        // Runs over through all elements which are touched
                        // by at least one finger.
                        for (i = 0; i < this.touches.length; i++) {
                            touchTargets = this.touches[i].targets;
                            if (touchTargets.length === 1) {


                                // Touch by one finger:  this is possible for all elements that can be dragged
                                if (evtTouches[touchTargets[0].num]) {
                                    pos1 = this.getMousePosition(evt, touchTargets[0].num);
                                    if (pos1[0] < 0 || pos1[0] > this.canvasWidth ||
                                        pos1[1] < 0 || pos1[1] > this.canvasHeight) {
                                        return;
                                    }
                                    touchTargets[0].X = pos1[0];
                                    touchTargets[0].Y = pos1[1];
                                    this.moveObject(pos1[0], pos1[1], this.touches[i], evt, 'touch');
                                }

                            } else if (touchTargets.length === 2 &&
                                touchTargets[0].num > -1 &&
                                touchTargets[1].num > -1) {

                                // Touch by two fingers: moving lines, ...
                                if (evtTouches[touchTargets[0].num] &&
                                    evtTouches[touchTargets[1].num]) {

                                    // Get coordinates of the two touches
                                    pos1 = this.getMousePosition(evt, touchTargets[0].num);
                                    pos2 = this.getMousePosition(evt, touchTargets[1].num);
                                    if (pos1[0] < 0 || pos1[0] > this.canvasWidth ||
                                        pos1[1] < 0 || pos1[1] > this.canvasHeight ||
                                        pos2[0] < 0 || pos2[0] > this.canvasWidth ||
                                        pos2[1] < 0 || pos2[1] > this.canvasHeight) {
                                        return;
                                    }

                                    touchTargets[0].X = pos1[0];
                                    touchTargets[0].Y = pos1[1];
                                    touchTargets[1].X = pos2[0];
                                    touchTargets[1].Y = pos2[1];

                                    this.twoFingerMove(this.touches[i], touchTargets[0].num, evt);
                                    this.twoFingerMove(this.touches[i], touchTargets[1].num);

                                    touchTargets[0].Xprev = pos1[0];
                                    touchTargets[0].Yprev = pos1[1];
                                    touchTargets[1].Xprev = pos2[0];
                                    touchTargets[1].Yprev = pos2[1];
                                }
                            }
                        }
                    } else {
                        if (evtTouches.length === 2) {
                            this.gestureChangeListener(evt);
                        }
                        // Move event without dragging an element
                        pos1 = this.getMousePosition(evt, 0);
                        this.highlightElements(pos1[0], pos1[1], evt, -1);
                    }
                }
            }

            if (this.mode !== this.BOARD_MODE_DRAG) {
                this.displayInfobox(false);
            }

            this.triggerEventHandlers(['touchmove', 'move'], [evt, this.mode]);
            this.options.precision.hasPoint = this.options.precision.mouse;
            this.updateQuality = this.BOARD_QUALITY_HIGH;

            return this.mode === this.BOARD_MODE_NONE;
        },

        /**
         * Triggered as soon as the user stops touching the device with at least one finger.
         * @param {Event} evt
         * @returns {Boolean}
         */
        touchEndListener: function (evt) {
            var i, j, k,
                eps = this.options.precision.touch,
                tmpTouches = [], found, foundNumber,
                evtTouches = evt && evt[JXG.touchProperty],
                touchTargets;

            this.triggerEventHandlers(['touchend', 'up'], [evt]);
            this.displayInfobox(false);

            // selection
            if (this.selectingMode) {
                this._stopSelecting(evt);
                this.triggerEventHandlers(['touchstopselecting', 'stopselecting'], [evt]);
            } else if (evtTouches && evtTouches.length > 0) {
                for (i = 0; i < this.touches.length; i++) {
                    tmpTouches[i] = this.touches[i];
                }
                this.touches.length = 0;

                // try to convert the operation, e.g. if a lines is rotated and translated with two fingers and one finger is lifted,
                // convert the operation to a simple one-finger-translation.
                // ADDENDUM 11/10/11:
                // see addendum to touchStartListener from 11/10/11
                // (1) run through the tmptouches
                // (2) check the touches.obj, if it is a
                //     (a) point, try to find the targettouch, if found keep it and mark the targettouch, else drop the touch.
                //     (b) line with
                //          (i) one target: try to find it, if found keep it mark the targettouch, else drop the touch.
                //         (ii) two targets: if none can be found, drop the touch. if one can be found, remove the other target. mark all found targettouches
                //     (c) circle with [proceed like in line]

                // init the targettouches marker
                for (i = 0; i < evtTouches.length; i++) {
                    evtTouches[i].jxg_isused = false;
                }

                for (i = 0; i < tmpTouches.length; i++) {
                    // could all targets of the current this.touches.obj be assigned to targettouches?
                    found = false;
                    foundNumber = 0;
                    touchTargets = tmpTouches[i].targets;

                    for (j = 0; j < touchTargets.length; j++) {
                        touchTargets[j].found = false;
                        for (k = 0; k < evtTouches.length; k++) {
                            if (Math.abs(Math.pow(evtTouches[k].screenX - touchTargets[j].X, 2) + Math.pow(evtTouches[k].screenY - touchTargets[j].Y, 2)) < eps * eps) {
                                touchTargets[j].found = true;
                                touchTargets[j].num = k;
                                touchTargets[j].X = evtTouches[k].screenX;
                                touchTargets[j].Y = evtTouches[k].screenY;
                                foundNumber += 1;
                                break;
                            }
                        }
                    }

                    if (Type.isPoint(tmpTouches[i].obj)) {
                        found = (touchTargets[0] && touchTargets[0].found);
                    } else if (tmpTouches[i].obj.elementClass === Const.OBJECT_CLASS_LINE) {
                        found = (touchTargets[0] && touchTargets[0].found) || (touchTargets[1] && touchTargets[1].found);
                    } else if (tmpTouches[i].obj.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                        found = foundNumber === 1 || foundNumber === 3;
                    }

                    // if we found this object to be still dragged by the user, add it back to this.touches
                    if (found) {
                        this.touches.push({
                            obj: tmpTouches[i].obj,
                            targets: []
                        });

                        for (j = 0; j < touchTargets.length; j++) {
                            if (touchTargets[j].found) {
                                this.touches[this.touches.length - 1].targets.push({
                                    num: touchTargets[j].num,
                                    X: touchTargets[j].screenX,
                                    Y: touchTargets[j].screenY,
                                    Xprev: NaN,
                                    Yprev: NaN,
                                    Xstart: touchTargets[j].Xstart,
                                    Ystart: touchTargets[j].Ystart,
                                    Zstart: touchTargets[j].Zstart
                                });
                            }
                        }

                    } else {
                        tmpTouches[i].obj.noHighlight();
                    }
                }

            } else {
                this.touches.length = 0;
            }

            for (i = this.downObjects.length - 1; i > -1; i--) {
                found = false;
                for (j = 0; j < this.touches.length; j++) {
                    if (this.touches[j].obj.id === this.downObjects[i].id) {
                        found = true;
                    }
                }
                if (!found) {
                    this.downObjects[i].triggerEventHandlers(['touchup', 'up'], [evt]);
                    this.downObjects[i].snapToGrid();
                    this.downObjects[i].snapToPoints();
                    this.downObjects.splice(i, 1);
                }
            }

            if (!evtTouches || evtTouches.length === 0) {

                if (this.hasTouchEnd) {
                    Env.removeEvent(this.document, 'touchend', this.touchEndListener, this);
                    this.hasTouchEnd = false;
                }

                this.dehighlightAll();
                this.updateQuality = this.BOARD_QUALITY_HIGH;

                this.originMoveEnd();
                this.update();
            }

            return true;
        },

        /**
         * This method is called by the browser when the mouse button is clicked.
         * @param {Event} evt The browsers event object.
         * @returns {Boolean} True if no element is found under the current mouse pointer, false otherwise.
         */
        mouseDownListener: function (evt) {
            var pos, elements, result;

            // prevent accidental selection of text
            if (this.document.selection && Type.isFunction(this.document.selection.empty)) {
                this.document.selection.empty();
            } else if (window.getSelection) {
                window.getSelection().removeAllRanges();
            }

            if (!this.hasMouseUp) {
                Env.addEvent(this.document, 'mouseup', this.mouseUpListener, this);
                this.hasMouseUp = true;
            } else {
                // In case this.hasMouseUp==true, it may be that there was a
                // mousedown event before which was not followed by an mouseup event.
                // This seems to happen with interactive whiteboard pens sometimes.
                return;
            }

            this._inputDevice = 'mouse';
            this.options.precision.hasPoint = this.options.precision.mouse;
            pos = this.getMousePosition(evt);

            // selection
            this._testForSelection(evt);
            if (this.selectingMode) {
                this._startSelecting(pos);
                this.triggerEventHandlers(['mousestartselecting', 'startselecting'], [evt]);
                return;     // don't continue as a normal click
            }

            elements = this.initMoveObject(pos[0], pos[1], evt, 'mouse');

            // if no draggable object can be found, get out here immediately
            if (elements.length === 0) {
                this.mode = this.BOARD_MODE_NONE;
                result = true;
            } else {
                /** @ignore */
                this.mouse = {
                    obj: null,
                    targets: [{
                        X: pos[0],
                        Y: pos[1],
                        Xprev: NaN,
                        Yprev: NaN
                    }]
                };
                this.mouse.obj = elements[elements.length - 1];

                this.dehighlightAll();
                this.mouse.obj.highlight(true);

                this.mouse.targets[0].Xstart = [];
                this.mouse.targets[0].Ystart = [];
                this.mouse.targets[0].Zstart = [];

                this.saveStartPos(this.mouse.obj, this.mouse.targets[0]);

                // prevent accidental text selection
                // this could get us new trouble: input fields, links and drop down boxes placed as text
                // on the board don't work anymore.
                if (evt && evt.preventDefault) {
                    evt.preventDefault();
                } else if (window.event) {
                    window.event.returnValue = false;
                }
            }

            if (this.mode === this.BOARD_MODE_NONE) {
                result = this.mouseOriginMoveStart(evt);
            }

            this.triggerEventHandlers(['mousedown', 'down'], [evt]);

            return result;
        },

        /**
         * This method is called by the browser when the mouse is moved.
         * @param {Event} evt The browsers event object.
         */
        mouseMoveListener: function (evt) {
            var pos;

            if (!this.checkFrameRate(evt)) {
                return false;
            }

            pos = this.getMousePosition(evt);

            this.updateQuality = this.BOARD_QUALITY_LOW;

            if (this.mode !== this.BOARD_MODE_DRAG) {
                this.dehighlightAll();
                this.displayInfobox(false);
            }

            // we have to check for four cases:
            //   * user moves origin
            //   * user drags an object
            //   * user just moves the mouse, here highlight all elements at
            //     the current mouse position
            //   * the user is selecting

            // selection
            if (this.selectingMode) {
                this._moveSelecting(pos);
                this.triggerEventHandlers(['mousemoveselecting', 'moveselecting'], [evt, this.mode]);
            } else if (!this.mouseOriginMove(evt)) {
                if (this.mode === this.BOARD_MODE_DRAG) {
                    this.moveObject(pos[0], pos[1], this.mouse, evt, 'mouse');
                } else { // BOARD_MODE_NONE
                    // Move event without dragging an element
                    this.highlightElements(pos[0], pos[1], evt, -1);
                }
                this.triggerEventHandlers(['mousemove', 'move'], [evt, this.mode]);
            }
            this.updateQuality = this.BOARD_QUALITY_HIGH;
        },

        /**
         * This method is called by the browser when the mouse button is released.
         * @param {Event} evt
         */
        mouseUpListener: function (evt) {
            var i;

            if (this.selectingMode === false) {
                this.triggerEventHandlers(['mouseup', 'up'], [evt]);
            }

            // redraw with high precision
            this.updateQuality = this.BOARD_QUALITY_HIGH;

            if (this.mouse && this.mouse.obj) {
                // The parameter is needed for lines with snapToGrid enabled
                this.mouse.obj.snapToGrid(this.mouse.targets[0]);
                this.mouse.obj.snapToPoints();
            }

            this.originMoveEnd();
            this.dehighlightAll();
            this.update();

            // selection
            if (this.selectingMode) {
                this._stopSelecting(evt);
                this.triggerEventHandlers(['mousestopselecting', 'stopselecting'], [evt]);
            } else {
                for (i = 0; i < this.downObjects.length; i++) {
                    this.downObjects[i].triggerEventHandlers(['mouseup', 'up'], [evt]);
                }
            }

            this.downObjects.length = 0;

            if (this.hasMouseUp) {
                Env.removeEvent(this.document, 'mouseup', this.mouseUpListener, this);
                this.hasMouseUp = false;
            }

            // release dragged mouse object
            /** @ignore */
            this.mouse = null;
        },

        /**
         * Handler for mouse wheel events. Used to zoom in and out of the board.
         * @param {Event} evt
         * @returns {Boolean}
         */
        mouseWheelListener: function (evt) {
            if (!this.attr.zoom.wheel || !this._isRequiredKeyPressed(evt, 'zoom')) {
                return true;
            }

            evt = evt || window.event;
            var wd = evt.detail ? -evt.detail : evt.wheelDelta / 40,
                pos = new Coords(Const.COORDS_BY_SCREEN, this.getMousePosition(evt), this);

            if (wd > 0) {
                this.zoomIn(pos.usrCoords[1], pos.usrCoords[2]);
            } else {
                this.zoomOut(pos.usrCoords[1], pos.usrCoords[2]);
            }

            this.triggerEventHandlers(['mousewheel'], [evt]);

            evt.preventDefault();
            return false;
        },

        /**
         * Allow moving of JSXGraph elements with arrow keys
         * and zooming of the construction with + / -.
         * Panning of the construction is done with arrow keys
         * if the pan key (shift or ctrl) is pressed.
         * The selection of the element is done with the tab key.
         *
         * @param  {Event} evt The browser's event object
         *
         * @see JXG.Board#keyboard
         * @see JXG.Board#keyFocusInListener
         * @see JXG.Board#keyFocusOutListener
         *
         */
        keyDownListener: function (evt) {
            var id_node = evt.target.id,
                id, el, res,
                sX = 0,
                sY = 0,
                // dx, dy are provided in screen units and
                // are converted to user coordinates
                dx = Type.evaluate(this.attr.keyboard.dx) / this.unitX,
                dy = Type.evaluate(this.attr.keyboard.dy) / this.unitY,
                doZoom = false,
                done = true,
                dir, actPos;

            if (!this.attr.keyboard.enabled || id_node === '') {
                return false;
            }

            // Get the JSXGraph id from the id of the SVG node.
            id = id_node.replace(this.containerObj.id + '_', '');
            el = this.select(id);

            if (Type.exists(el.coords)) {
                actPos = el.coords.usrCoords.slice(1);
            }

            if (Type.evaluate(this.attr.keyboard.panshift) || Type.evaluate(this.attr.keyboard.panctrl)) {
                doZoom = true;
            }

            if ((Type.evaluate(this.attr.keyboard.panshift) && evt.shiftKey) ||
                (Type.evaluate(this.attr.keyboard.panctrl) && evt.ctrlKey)) {
                if (evt.keyCode === 38) {           // up
                    this.clickUpArrow();
                } else if (evt.keyCode === 40) {    // down
                    this.clickDownArrow();
                } else if (evt.keyCode === 37) {    // left
                    this.clickLeftArrow();
                } else if (evt.keyCode === 39) {    // right
                    this.clickRightArrow();
                } else {
                    done = false;
                }
            } else {
                // Adapt dx, dy to snapToGrid and attractToGrid
                // snapToGrid has priority.
                if (Type.exists(el.visProp)) {
                    if (Type.exists(el.visProp.snaptogrid) &&
                        el.visProp.snaptogrid &&
                        Type.evaluate(el.visProp.snapsizex) &&
                        Type.evaluate(el.visProp.snapsizey)) {

                        // Adapt dx, dy such that snapToGrid is possible
                        res = el.getSnapSizes();
                        sX = res[0];
                        sY = res[1];
                        dx = Math.max(sX, dx);
                        dy = Math.max(sY, dy);

                    } else if (Type.exists(el.visProp.attracttogrid) &&
                        el.visProp.attracttogrid &&
                        Type.evaluate(el.visProp.attractordistance) &&
                        Type.evaluate(el.visProp.attractorunit)) {

                        // Adapt dx, dy such that attractToGrid is possible
                        sX = 1.1 * Type.evaluate(el.visProp.attractordistance);
                        sY = sX;

                        if (Type.evaluate(el.visProp.attractorunit) === 'screen') {
                            sX /= this.unitX;
                            sY /= this.unitX;
                        }
                        dx = Math.max(sX, dx);
                        dy = Math.max(sY, dy);
                    }

                }

                if (evt.keyCode === 38) {           // up
                    dir = [0, dy];
                } else if (evt.keyCode === 40) {    // down
                    dir = [0, -dy];
                } else if (evt.keyCode === 37) {    // left
                    dir = [-dx, 0];
                } else if (evt.keyCode === 39) {    // right
                    dir = [dx, 0];
                // } else if (evt.keyCode === 9) {  // tab

                } else if (doZoom && evt.key === '+') {   // +
                    this.zoomIn();
                } else if (doZoom && evt.key === '-') {   // -
                    this.zoomOut();
                } else if (doZoom && evt.key === 'o') {   // o
                    this.zoom100();
                } else {
                    done = false;
                }

                if (dir && el.isDraggable &&
                        el.visPropCalc.visible &&
                        ((this.geonextCompatibilityMode &&
                            (Type.isPoint(el) ||
                            el.elementClass === Const.OBJECT_CLASS_TEXT)
                        ) || !this.geonextCompatibilityMode) &&
                        !Type.evaluate(el.visProp.fixed)
                    ) {

                    if (Type.exists(el.coords)) {
                        dir[0] += actPos[0];
                        dir[1] += actPos[1];
                    }
                    // For coordsElement setPosition has to call setPositionDirectly.
                    // Otherwise the position is set by a translation.
                    el.setPosition(JXG.COORDS_BY_USER, dir);
                    if (Type.exists(el.coords)) {
                        this.updateInfobox(el);
                    }
                    this.triggerEventHandlers(['hit'], [evt, el]);
                }
            }

            this.update();

            if (done) {
                evt.preventDefault();
            }
            return true;
        },

        /**
         * Event listener for SVG elements getting focus.
         * This is needed for highlighting when using keyboard control.
         *
         * @see JXG.Board#keyFocusOutListener
         * @see JXG.Board#keyDownListener
         * @see JXG.Board#keyboard
         *
         * @param  {Event} evt The browser's event object
         */
        keyFocusInListener: function (evt) {
            var id_node = evt.target.id,
                id, el;

            if (!this.attr.keyboard.enabled || id_node === '') {
                return false;
            }

            id = id_node.replace(this.containerObj.id + '_', '');
            el = this.select(id);
            if (Type.exists(el.highlight)) {
                el.highlight(true);
            }
            if (Type.exists(el.coords)) {
                this.updateInfobox(el);
            }
            this.triggerEventHandlers(['hit'], [evt, el]);
        },

        /**
         * Event listener for SVG elements losing focus.
         * This is needed for dehighlighting when using keyboard control.
         *
         * @see JXG.Board#keyFocusInListener
         * @see JXG.Board#keyDownListener
         * @see JXG.Board#keyboard
         *
         * @param  {Event} evt The browser's event object
         */
        keyFocusOutListener: function (evt) {
            if (!this.attr.keyboard.enabled) {
                return false;
            }
            // var id_node = evt.target.id,
            //     id, el;

            // id = id_node.replace(this.containerObj.id + '_', '');
            // el = this.select(id);
            this.dehighlightAll();
            this.displayInfobox(false);
        },

        /**
         * Update the width and height of the JSXGraph container div element.
         * Read actual values with getBoundingClientRect(),
         * and call board.resizeContainer() with this values.
         * <p>
         * If necessary, also call setBoundingBox().
         *
         * @see JXG.Board#startResizeObserver
         * @see JXG.Board#resizeListener
         * @see JXG.Board#resizeContainer
         * @see JXG.Board#setBoundingBox
         *
         */
        updateContainerDims: function() {
            var w, h,
                bb, css;

            // Get size of the board's container div
            bb = this.containerObj.getBoundingClientRect();
            w = bb.width;
            h = bb.height;

            // Subtract the border size
            if (window && window.getComputedStyle) {
                css = window.getComputedStyle(this.containerObj, null);
                w -= parseFloat(css.getPropertyValue('border-left-width')) + parseFloat(css.getPropertyValue('border-right-width'));
                h -= parseFloat(css.getPropertyValue('border-top-width'))  + parseFloat(css.getPropertyValue('border-bottom-width'));
            }

            // If div is invisible - do nothing
            if (w <= 0 || h <= 0) {
                return;
            }

            // If bounding box is not yet initialized, do it now.
            if (isNaN(this.getBoundingBox()[0])) {
                this.setBoundingBox(this.attr.boundingbox, this.keepaspectratio, 'keep');
            }

            // Do nothing if the dimension did not change since being visible
            // the last time. Note that if the div had display:none in the mean time,
            // we did not store this._prevDim.
            if (Type.exists(this._prevDim) &&
                this._prevDim.w === w && this._prevDim.h === h) {
                    return;
            }

            // Set the size of the SVG or canvas element
            this.resizeContainer(w, h, true);
            this._prevDim = {
                w: w,
                h: h
            };
        },

        /**
         * Start observer which reacts to size changes of the JSXGraph
         * container div element. Calls updateContainerDims().
         * If not available, an event listener for the window-resize event is started.
         * On mobile devices also scrolling might trigger resizes.
         * However, resize events triggered by scrolling events should be ignored.
         * Therefore, also a scrollListener is started.
         * Resize can be controlled with the board attribute resize.
         *
         * @see JXG.Board#updateContainerDims
         * @see JXG.Board#resizeListener
         * @see JXG.Board#scrollListener
         * @see JXG.Board#resize
         *
         */
        startResizeObserver: function() {
            var that = this;

            if (!Env.isBrowser || !this.attr.resize || !this.attr.resize.enabled) {
                return;
            }

            this.resizeObserver = new ResizeObserver(function(entries) {
                if (!that._isResizing) {
                    that._isResizing = true;
                    window.setTimeout(function() {
                        try {
                            that.updateContainerDims();
                        } catch (err) {
                            that.stopResizeObserver();
                        } finally {
                            that._isResizing = false;
                        }
                    }, that.attr.resize.throttle);
                }
            });
            this.resizeObserver.observe(this.containerObj);
        },

        /**
         * Stops the resize observer.
         * @see JXG.Board#startResizeObserver
         *
         */
        stopResizeObserver: function() {
            if (!Env.isBrowser || !this.attr.resize || !this.attr.resize.enabled) {
                return;
            }

            if (Type.exists(this.resizeObserver)) {
                this.resizeObserver.unobserve(this.containerObj);
            }
        },

        /**
         * Fallback solutions if there is no resizeObserver available in the browser.
         * Reacts to resize events of the window (only). Otherwise similar to
         * startResizeObserver(). To handle changes of the visibility
         * of the JSXGraph container element, additionally an intersection observer is used.
         * which watches changes in the visibility of the JSXGraph container element.
         * This is necessary e.g. for register tabs or dia shows.
         *
         * @see JXG.Board#startResizeObserver
         * @see JXG.Board#startIntersectionObserver
         */
        resizeListener: function() {
            var that = this;

            if (!Env.isBrowser || !this.attr.resize || !this.attr.resize.enabled) {
                return;
            }
            if (!this._isScrolling && !this._isResizing) {
                this._isResizing = true;
                window.setTimeout(function() {
                    that.updateContainerDims();
                    that._isResizing = false;
                }, this.attr.resize.throttle);
            }
        },

        /**
         * Listener to watch for scroll events. Sets board._isScrolling = true
         * @param  {Event} evt The browser's event object
         *
         * @see JXG.Board#startResizeObserver
         * @see JXG.Board#resizeListener
         *
         */
        scrollListener: function(evt) {
            var that = this;

            if (!Env.isBrowser) {
                return;
            }
            if (!this._isScrolling) {
                this._isScrolling = true;
                window.setTimeout(function() {
                    that._isScrolling = false;
                }, 66);
            }
        },

        /**
         * Watch for changes of the visibility of the JSXGraph container element.
         *
         * @see JXG.Board#startResizeObserver
         * @see JXG.Board#resizeListener
         *
         */
        startIntersectionObserver: function() {
            var that = this,
                options = {
                    root: null,
                    rootMargin: '0px',
                    threshold: 0.8
                };

            try {
                this.intersectionObserver = new IntersectionObserver(function(entries) {
                    // If bounding box is not yet initialized, do it now.
                    if (isNaN(that.getBoundingBox()[0])) {
                        that.updateContainerDims();
                    }
                }, options);
                this.intersectionObserver.observe(that.containerObj);
            } catch (err) {
                console.log('JSXGraph: IntersectionObserver not available in this browser.');
            }
        },

        /**
         * Stop the intersection observer
         *
         * @see JXG.Board#startIntersectionObserver
         *
         */
        stopIntersectionObserver: function() {
            if (Type.exists(this.intersectionObserver)) {
                this.intersectionObserver.unobserve(this.containerObj);
            }
        },

        /**********************************************************
         *
         * End of Event Handlers
         *
         **********************************************************/

        /**
         * Initialize the info box object which is used to display
         * the coordinates of points near the mouse pointer,
         * @returns {JXG.Board} Reference to the board
        */
        initInfobox: function () {
            var  attr = Type.copyAttributes({}, this.options, 'infobox');

            attr.id = this.id + '_infobox';
            /**
             * Infobox close to points in which the points' coordinates are displayed.
             * This is simply a JXG.Text element. Access through board.infobox.
             * Uses CSS class .JXGinfobox.
             * @type JXG.Text
             *
             */
            this.infobox = this.create('text', [0, 0, '0,0'], attr);

            this.infobox.distanceX = -20;
            this.infobox.distanceY = 25;
            // this.infobox.needsUpdateSize = false;  // That is not true, but it speeds drawing up.

            this.infobox.dump = false;

            this.displayInfobox(false);
            return this;
        },

        /**
         * Updates and displays a little info box to show coordinates of current selected points.
         * @param {JXG.GeometryElement} el A GeometryElement
         * @returns {JXG.Board} Reference to the board
         * @see JXG.Board#displayInfobox
         * @see JXG.Board#showInfobox
         * @see Point#showInfobox
         *
         */
        updateInfobox: function (el) {
            var x, y, xc, yc,
            vpinfoboxdigits,
            vpsi = Type.evaluate(el.visProp.showinfobox);

            if ((!Type.evaluate(this.attr.showinfobox) &&  vpsi === 'inherit') ||
                !vpsi) {
                return this;
            }

            if (Type.isPoint(el)) {
                xc = el.coords.usrCoords[1];
                yc = el.coords.usrCoords[2];

                vpinfoboxdigits = Type.evaluate(el.visProp.infoboxdigits);
                this.infobox.setCoords(xc + this.infobox.distanceX / this.unitX,
                                       yc + this.infobox.distanceY / this.unitY);

                if (typeof el.infoboxText !== 'string') {
                    if (vpinfoboxdigits === 'auto') {
                        x = Type.autoDigits(xc);
                        y = Type.autoDigits(yc);
                    } else if (Type.isNumber(vpinfoboxdigits)) {
                        x = Type.toFixed(xc, vpinfoboxdigits);
                        y = Type.toFixed(yc, vpinfoboxdigits);
                    } else {
                        x = xc;
                        y = yc;
                    }

                    this.highlightInfobox(x, y, el);
                } else {
                    this.highlightCustomInfobox(el.infoboxText, el);
                }

                this.displayInfobox(true);
            }
            return this;
        },

        /**
         * Set infobox visible / invisible.
         *
         * It uses its property hiddenByParent to memorize its status.
         * In this way, many DOM access can be avoided.
         *
         * @param  {Boolean} val true for visible, false for invisible
         * @returns {JXG.Board} Reference to the board.
         * @see JXG.Board#updateInfobox
         *
         */
        displayInfobox: function(val) {
            if (this.infobox.hiddenByParent === val) {
                this.infobox.hiddenByParent = !val;
                this.infobox.prepareUpdate().updateVisibility(val).updateRenderer();
            }
            return this;
        },

        // Alias for displayInfobox to be backwards compatible.
        // The method showInfobox clashes with the board attribute showInfobox
        showInfobox: function(val) {
            return this.displayInfobox(val);
        },

        /**
         * Changes the text of the info box to show the given coordinates.
         * @param {Number} x
         * @param {Number} y
         * @param {JXG.GeometryElement} [el] The element the mouse is pointing at
         * @returns {JXG.Board} Reference to the board.
         */
        highlightInfobox: function (x, y, el) {
            this.highlightCustomInfobox('(' + x + ', ' + y + ')', el);
            return this;
        },

        /**
         * Changes the text of the info box to what is provided via text.
         * @param {String} text
         * @param {JXG.GeometryElement} [el]
         * @returns {JXG.Board} Reference to the board.
         */
        highlightCustomInfobox: function (text, el) {
            this.infobox.setText(text);
            return this;
        },

        /**
         * Remove highlighting of all elements.
         * @returns {JXG.Board} Reference to the board.
         */
        dehighlightAll: function () {
            var el, pEl, needsDehighlight = false;

            for (el in this.highlightedObjects) {
                if (this.highlightedObjects.hasOwnProperty(el)) {
                    pEl = this.highlightedObjects[el];

                    if (this.hasMouseHandlers || this.hasPointerHandlers) {
                        pEl.noHighlight();
                    }

                    needsDehighlight = true;

                    // In highlightedObjects should only be objects which fulfill all these conditions
                    // And in case of complex elements, like a turtle based fractal, it should be faster to
                    // just de-highlight the element instead of checking hasPoint...
                    // if ((!Type.exists(pEl.hasPoint)) || !pEl.hasPoint(x, y) || !pEl.visPropCalc.visible)
                }
            }

            this.highlightedObjects = {};

            // We do not need to redraw during dehighlighting in CanvasRenderer
            // because we are redrawing anyhow
            //  -- We do need to redraw during dehighlighting. Otherwise objects won't be dehighlighted until
            // another object is highlighted.
            if (this.renderer.type === 'canvas' && needsDehighlight) {
                this.prepareUpdate();
                this.renderer.suspendRedraw(this);
                this.updateRenderer();
                this.renderer.unsuspendRedraw();
            }

            return this;
        },

        /**
         * Returns the input parameters in an array. This method looks pointless and it really is, but it had a purpose
         * once.
         * @private
         * @param {Number} x X coordinate in screen coordinates
         * @param {Number} y Y coordinate in screen coordinates
         * @returns {Array} Coordinates [x, y] of the mouse in screen coordinates.
         * @see JXG.Board#getUsrCoordsOfMouse
         */
        getScrCoordsOfMouse: function (x, y) {
            return [x, y];
        },

        /**
         * This method calculates the user coords of the current mouse coordinates.
         * @param {Event} evt Event object containing the mouse coordinates.
         * @returns {Array} Coordinates [x, y] of the mouse in user coordinates.
         * @example
         * board.on('up', function (evt) {
         *         var a = board.getUsrCoordsOfMouse(evt),
         *             x = a[0],
         *             y = a[1],
         *             somePoint = board.create('point', [x,y], {name:'SomePoint',size:4});
         *             // Shorter version:
         *             //somePoint = board.create('point', a, {name:'SomePoint',size:4});
         *         });
         *
         * </pre><div id="JXG48d5066b-16ba-4920-b8ea-a4f8eff6b746" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG48d5066b-16ba-4920-b8ea-a4f8eff6b746',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     board.on('up', function (evt) {
         *             var a = board.getUsrCoordsOfMouse(evt),
         *                 x = a[0],
         *                 y = a[1],
         *                 somePoint = board.create('point', [x,y], {name:'SomePoint',size:4});
         *                 // Shorter version:
         *                 //somePoint = board.create('point', a, {name:'SomePoint',size:4});
         *             });
         *
         *     })();
         *
         * </script><pre>
         *
         * @see JXG.Board#getScrCoordsOfMouse
         * @see JXG.Board#getAllUnderMouse
         */
        getUsrCoordsOfMouse: function (evt) {
            var cPos = this.getCoordsTopLeftCorner(),
                absPos = Env.getPosition(evt, null, this.document),
                x = absPos[0] - cPos[0],
                y = absPos[1] - cPos[1],
                newCoords = new Coords(Const.COORDS_BY_SCREEN, [x, y], this);

            return newCoords.usrCoords.slice(1);
        },

        /**
         * Collects all elements under current mouse position plus current user coordinates of mouse cursor.
         * @param {Event} evt Event object containing the mouse coordinates.
         * @returns {Array} Array of elements at the current mouse position plus current user coordinates of mouse.
         * @see JXG.Board#getUsrCoordsOfMouse
         * @see JXG.Board#getAllObjectsUnderMouse
         */
        getAllUnderMouse: function (evt) {
            var elList = this.getAllObjectsUnderMouse(evt);
            elList.push(this.getUsrCoordsOfMouse(evt));

            return elList;
        },

        /**
         * Collects all elements under current mouse position.
         * @param {Event} evt Event object containing the mouse coordinates.
         * @returns {Array} Array of elements at the current mouse position.
         * @see JXG.Board#getAllUnderMouse
         */
        getAllObjectsUnderMouse: function (evt) {
            var cPos = this.getCoordsTopLeftCorner(),
                absPos = Env.getPosition(evt, null, this.document),
                dx = absPos[0] - cPos[0],
                dy = absPos[1] - cPos[1],
                elList = [],
                el,
                pEl,
                len = this.objectsList.length;

            for (el = 0; el < len; el++) {
                pEl = this.objectsList[el];
                if (pEl.visPropCalc.visible && pEl.hasPoint && pEl.hasPoint(dx, dy)) {
                    elList[elList.length] = pEl;
                }
            }

            return elList;
        },

        /**
         * Update the coords object of all elements which possess this
         * property. This is necessary after changing the viewport.
         * @returns {JXG.Board} Reference to this board.
         **/
        updateCoords: function () {
            var el, ob, len = this.objectsList.length;

            for (ob = 0; ob < len; ob++) {
                el = this.objectsList[ob];

                if (Type.exists(el.coords)) {
                    if (Type.evaluate(el.visProp.frozen)) {
                        el.coords.screen2usr();
                    } else {
                        el.coords.usr2screen();
                    }
                }
            }
            return this;
        },

        /**
         * Moves the origin and initializes an update of all elements.
         * @param {Number} x
         * @param {Number} y
         * @param {Boolean} [diff=false]
         * @returns {JXG.Board} Reference to this board.
         */
        moveOrigin: function (x, y, diff) {
            var ox, oy, ul, lr;
            if (Type.exists(x) && Type.exists(y)) {
                ox = this.origin.scrCoords[1];
                oy = this.origin.scrCoords[2];

                this.origin.scrCoords[1] = x;
                this.origin.scrCoords[2] = y;

                if (diff) {
                    this.origin.scrCoords[1] -= this.drag_dx;
                    this.origin.scrCoords[2] -= this.drag_dy;
                }

                ul = (new Coords(Const.COORDS_BY_SCREEN, [0, 0], this)).usrCoords;
                lr = (new Coords(Const.COORDS_BY_SCREEN, [this.canvasWidth, this.canvasHeight], this)).usrCoords;
                if (ul[1] < this.maxboundingbox[0] ||
                    ul[2] > this.maxboundingbox[1] ||
                    lr[1] > this.maxboundingbox[2] ||
                    lr[2] < this.maxboundingbox[3]) {

                    this.origin.scrCoords[1] = ox;
                    this.origin.scrCoords[2] = oy;
                }
            }

            this.updateCoords().clearTraces().fullUpdate();
            this.triggerEventHandlers(['boundingbox']);

            return this;
        },

        /**
         * Add conditional updates to the elements.
         * @param {String} str String containing coniditional update in geonext syntax
         */
        addConditions: function (str) {
            var term, m, left, right, name, el, property,
                functions = [],
                // plaintext = 'var el, x, y, c, rgbo;\n',
                i = str.indexOf('<data>'),
                j = str.indexOf('<' + '/data>'),

                xyFun = function (board, el, f, what) {
                    return function () {
                        var e, t;

                        e = board.select(el.id);
                        t = e.coords.usrCoords[what];

                        if (what === 2) {
                            e.setPositionDirectly(Const.COORDS_BY_USER, [f(), t]);
                        } else {
                            e.setPositionDirectly(Const.COORDS_BY_USER, [t, f()]);
                        }
                        e.prepareUpdate().update();
                    };
                },

                visFun = function (board, el, f) {
                    return function () {
                        var e, v;

                        e = board.select(el.id);
                        v = f();

                        e.setAttribute({visible: v});
                    };
                },

                colFun = function (board, el, f, what) {
                    return function () {
                        var e, v;

                        e = board.select(el.id);
                        v = f();

                        if (what === 'strokewidth') {
                            e.visProp.strokewidth = v;
                        } else {
                            v = Color.rgba2rgbo(v);
                            e.visProp[what + 'color'] = v[0];
                            e.visProp[what + 'opacity'] = v[1];
                        }
                    };
                },

                posFun = function (board, el, f) {
                    return function () {
                        var e = board.select(el.id);

                        e.position = f();
                    };
                },

                styleFun = function (board, el, f) {
                    return function () {
                        var e = board.select(el.id);

                        e.setStyle(f());
                    };
                };

            if (i < 0) {
                return;
            }

            while (i >= 0) {
                term = str.slice(i + 6, j);   // throw away <data>
                m = term.indexOf('=');
                left = term.slice(0, m);
                right = term.slice(m + 1);
                m = left.indexOf('.');     // Dies erzeugt Probleme bei Variablennamen der Form " Steuern akt."
                name = left.slice(0, m);    //.replace(/\s+$/,''); // do NOT cut out name (with whitespace)
                el = this.elementsByName[Type.unescapeHTML(name)];

                property = left.slice(m + 1).replace(/\s+/g, '').toLowerCase(); // remove whitespace in property
                right = Type.createFunction (right, this, '', true);

                // Debug
                if (!Type.exists(this.elementsByName[name])) {
                    JXG.debug("debug conditions: |" + name + "| undefined");
                } else {
                    // plaintext += "el = this.objects[\"" + el.id + "\"];\n";

                    switch (property) {
                    case 'x':
                        functions.push(xyFun(this, el, right, 2));
                        break;
                    case 'y':
                        functions.push(xyFun(this, el, right, 1));
                        break;
                    case 'visible':
                        functions.push(visFun(this, el, right));
                        break;
                    case 'position':
                        functions.push(posFun(this, el, right));
                        break;
                    case 'stroke':
                        functions.push(colFun(this, el, right, 'stroke'));
                        break;
                    case 'style':
                        functions.push(styleFun(this, el, right));
                        break;
                    case 'strokewidth':
                        functions.push(colFun(this, el, right, 'strokewidth'));
                        break;
                    case 'fill':
                        functions.push(colFun(this, el, right, 'fill'));
                        break;
                    case 'label':
                        break;
                    default:
                        JXG.debug("property '" + property + "' in conditions not yet implemented:" + right);
                        break;
                    }
                }
                str = str.slice(j + 7); // cut off "</data>"
                i = str.indexOf('<data>');
                j = str.indexOf('<' + '/data>');
            }

            this.updateConditions = function () {
                var i;

                for (i = 0; i < functions.length; i++) {
                    functions[i]();
                }

                this.prepareUpdate().updateElements();
                return true;
            };
            this.updateConditions();
        },

        /**
         * Computes the commands in the conditions-section of the gxt file.
         * It is evaluated after an update, before the unsuspendRedraw.
         * The function is generated in
         * @see JXG.Board#addConditions
         * @private
         */
        updateConditions: function () {
            return false;
        },

        /**
         * Calculates adequate snap sizes.
         * @returns {JXG.Board} Reference to the board.
         */
        calculateSnapSizes: function () {
            var p1 = new Coords(Const.COORDS_BY_USER, [0, 0], this),
                p2 = new Coords(Const.COORDS_BY_USER, [this.options.grid.gridX, this.options.grid.gridY], this),
                x = p1.scrCoords[1] - p2.scrCoords[1],
                y = p1.scrCoords[2] - p2.scrCoords[2];

            this.options.grid.snapSizeX = this.options.grid.gridX;
            while (Math.abs(x) > 25) {
                this.options.grid.snapSizeX *= 2;
                x /= 2;
            }

            this.options.grid.snapSizeY = this.options.grid.gridY;
            while (Math.abs(y) > 25) {
                this.options.grid.snapSizeY *= 2;
                y /= 2;
            }

            return this;
        },

        /**
         * Apply update on all objects with the new zoom-factors. Clears all traces.
         * @returns {JXG.Board} Reference to the board.
         */
        applyZoom: function () {
            this.updateCoords().calculateSnapSizes().clearTraces().fullUpdate();

            return this;
        },

        /**
         * Zooms into the board by the factors board.attr.zoom.factorX and board.attr.zoom.factorY and applies the zoom.
         * The zoom operation is centered at x, y.
         * @param {Number} [x]
         * @param {Number} [y]
         * @returns {JXG.Board} Reference to the board
         */
        zoomIn: function (x, y) {
            var bb = this.getBoundingBox(),
                zX = this.attr.zoom.factorx,
                zY = this.attr.zoom.factory,
                dX = (bb[2] - bb[0]) * (1.0 - 1.0 / zX),
                dY = (bb[1] - bb[3]) * (1.0 - 1.0 / zY),
                lr = 0.5,
                tr = 0.5,
                mi = this.attr.zoom.eps || this.attr.zoom.min || 0.001;  // this.attr.zoom.eps is deprecated

            if ((this.zoomX > this.attr.zoom.max && zX > 1.0) ||
                (this.zoomY > this.attr.zoom.max && zY > 1.0) ||
                (this.zoomX < mi && zX < 1.0) ||  // zoomIn is used for all zooms on touch devices
                (this.zoomY < mi && zY < 1.0)) {
                return this;
            }

            if (Type.isNumber(x) && Type.isNumber(y)) {
                lr = (x - bb[0]) / (bb[2] - bb[0]);
                tr = (bb[1] - y) / (bb[1] - bb[3]);
            }

            this.setBoundingBox([bb[0] + dX * lr, bb[1] - dY * tr, bb[2] - dX * (1 - lr), bb[3] + dY * (1 - tr)], this.keepaspectratio, 'update');
            return this.applyZoom();
        },

        /**
         * Zooms out of the board by the factors board.attr.zoom.factorX and board.attr.zoom.factorY and applies the zoom.
         * The zoom operation is centered at x, y.
         *
         * @param {Number} [x]
         * @param {Number} [y]
         * @returns {JXG.Board} Reference to the board
         */
        zoomOut: function (x, y) {
            var bb = this.getBoundingBox(),
                zX = this.attr.zoom.factorx,
                zY = this.attr.zoom.factory,
                dX = (bb[2] - bb[0]) * (1.0 - zX),
                dY = (bb[1] - bb[3]) * (1.0 - zY),
                lr = 0.5,
                tr = 0.5,
                mi = this.attr.zoom.eps || this.attr.zoom.min || 0.001;  // this.attr.zoom.eps is deprecated

            if (this.zoomX < mi || this.zoomY < mi) {
                return this;
            }

            if (Type.isNumber(x) && Type.isNumber(y)) {
                lr = (x - bb[0]) / (bb[2] - bb[0]);
                tr = (bb[1] - y) / (bb[1] - bb[3]);
            }

            this.setBoundingBox([bb[0] + dX * lr, bb[1] - dY * tr, bb[2] - dX * (1 - lr), bb[3] + dY * (1 - tr)], this.keepaspectratio, 'update');

            return this.applyZoom();
        },

        /**
         * Reset the zoom level to the original zoom level from initBoard();
         * Additionally, if the board as been initialized with a boundingBox (which is the default),
         * restore the viewport to the original viewport during initialization. Otherwise,
         * (i.e. if the board as been initialized with unitX/Y and originX/Y),
         * just set the zoom level to 100%.
         *
         * @returns {JXG.Board} Reference to the board
         */
        zoom100: function () {
            var bb, dX, dY;

            if (Type.exists(this.attr.boundingbox)) {
                this.setBoundingBox(this.attr.boundingbox, this.keepaspectratio, 'reset');
            } else {
                // Board has been set up with unitX/Y and originX/Y
                bb = this.getBoundingBox();
                dX = (bb[2] - bb[0]) * (1.0 - this.zoomX) * 0.5;
                dY = (bb[1] - bb[3]) * (1.0 - this.zoomY) * 0.5;
                this.setBoundingBox([bb[0] + dX, bb[1] - dY, bb[2] - dX, bb[3] + dY], this.keepaspectratio, 'reset');
            }
            return this.applyZoom();
        },

        /**
         * Zooms the board so every visible point is shown. Keeps aspect ratio.
         * @returns {JXG.Board} Reference to the board
         */
        zoomAllPoints: function () {
            var el, border, borderX, borderY, pEl,
                minX = 0,
                maxX = 0,
                minY = 0,
                maxY = 0,
                len = this.objectsList.length;

            for (el = 0; el < len; el++) {
                pEl = this.objectsList[el];

                if (Type.isPoint(pEl) && pEl.visPropCalc.visible) {
                    if (pEl.coords.usrCoords[1] < minX) {
                        minX = pEl.coords.usrCoords[1];
                    } else if (pEl.coords.usrCoords[1] > maxX) {
                        maxX = pEl.coords.usrCoords[1];
                    }
                    if (pEl.coords.usrCoords[2] > maxY) {
                        maxY = pEl.coords.usrCoords[2];
                    } else if (pEl.coords.usrCoords[2] < minY) {
                        minY = pEl.coords.usrCoords[2];
                    }
                }
            }

            border = 50;
            borderX = border / this.unitX;
            borderY = border / this.unitY;

            this.setBoundingBox([minX - borderX, maxY + borderY, maxX + borderX, minY - borderY], this.keepaspectratio, 'update');

            return this.applyZoom();
        },

        /**
         * Reset the bounding box and the zoom level to 100% such that a given set of elements is
         * within the board's viewport.
         * @param {Array} elements A set of elements given by id, reference, or name.
         * @returns {JXG.Board} Reference to the board.
         */
        zoomElements: function (elements) {
            var i, e, box,
                newBBox = [Infinity, -Infinity, -Infinity, Infinity],
                cx, cy, dx, dy, d;

            if (!Type.isArray(elements) || elements.length === 0) {
                return this;
            }

            for (i = 0; i < elements.length; i++) {
                e = this.select(elements[i]);

                box = e.bounds();
                if (Type.isArray(box)) {
                    if (box[0] < newBBox[0]) { newBBox[0] = box[0]; }
                    if (box[1] > newBBox[1]) { newBBox[1] = box[1]; }
                    if (box[2] > newBBox[2]) { newBBox[2] = box[2]; }
                    if (box[3] < newBBox[3]) { newBBox[3] = box[3]; }
                }
            }

            if (Type.isArray(newBBox)) {
                cx = 0.5 * (newBBox[0] + newBBox[2]);
                cy = 0.5 * (newBBox[1] + newBBox[3]);
                dx = 1.5 * (newBBox[2] - newBBox[0]) * 0.5;
                dy = 1.5 * (newBBox[1] - newBBox[3]) * 0.5;
                d = Math.max(dx, dy);
                this.setBoundingBox([cx - d, cy + d, cx + d, cy - d], this.keepaspectratio, 'update');
            }

            return this;
        },

        /**
         * Sets the zoom level to <tt>fX</tt> resp <tt>fY</tt>.
         * @param {Number} fX
         * @param {Number} fY
         * @returns {JXG.Board} Reference to the board.
         */
        setZoom: function (fX, fY) {
            var oX = this.attr.zoom.factorx,
                oY = this.attr.zoom.factory;

            this.attr.zoom.factorx = fX / this.zoomX;
            this.attr.zoom.factory = fY / this.zoomY;

            this.zoomIn();

            this.attr.zoom.factorx = oX;
            this.attr.zoom.factory = oY;

            return this;
        },

        /**
         * Removes object from board and renderer.
         * <p>
         * <b>Performance hints:</b> It is recommended to use the object's id.
         * If many elements are removed, it is best to call <tt>board.suspendUpdate()</tt>
         * before looping through the elements to be removed and call
         * <tt>board.unsuspendUpdate()</tt> after the loop. Further, it is advisable to loop
         * in reverse order, i.e. remove the object in reverse order of their creation time.
         *
         * @param {JXG.GeometryElement|Array} object The object to remove or array of objects to be removed.
         * The element(s) is/are given by name, id or a reference.
         * @param {Boolean} saveMethod If true, the algorithm runs through all elements
         * and tests if the element to be deleted is a child element. If yes, it will be
         * removed from the list of child elements. If false (default), the element
         * is removed from the lists of child elements of all its ancestors.
         * This should be much faster.
         * @returns {JXG.Board} Reference to the board
         */
        removeObject: function (object, saveMethod) {
            var el, i;

            if (Type.isArray(object)) {
                for (i = 0; i < object.length; i++) {
                    this.removeObject(object[i]);
                }

                return this;
            }

            object = this.select(object);

            // If the object which is about to be removed unknown or a string, do nothing.
            // it is a string if a string was given and could not be resolved to an element.
            if (!Type.exists(object) || Type.isString(object)) {
                return this;
            }

            try {
                // remove all children.
                for (el in object.childElements) {
                    if (object.childElements.hasOwnProperty(el)) {
                        object.childElements[el].board.removeObject(object.childElements[el]);
                    }
                }

                // Remove all children in elements like turtle
                for (el in object.objects) {
                    if (object.objects.hasOwnProperty(el)) {
                        object.objects[el].board.removeObject(object.objects[el]);
                    }
                }

                // Remove the element from the childElement list and the descendant list of all elements.
                if (saveMethod) {
                    // Running through all objects has quadratic complexity if many objects are deleted.
                    for (el in this.objects) {
                        if (this.objects.hasOwnProperty(el)) {
                            if (Type.exists(this.objects[el].childElements) &&
                                Type.exists(this.objects[el].childElements.hasOwnProperty(object.id))
                            ) {
                                delete this.objects[el].childElements[object.id];
                                delete this.objects[el].descendants[object.id];
                            }
                        }
                    }
                } else if (Type.exists(object.ancestors)) {
                    // Running through the ancestors should be much more efficient.
                    for (el in object.ancestors) {
                        if (object.ancestors.hasOwnProperty(el)) {
                            if (Type.exists(object.ancestors[el].childElements) &&
                                Type.exists(object.ancestors[el].childElements.hasOwnProperty(object.id))
                            ) {
                                delete object.ancestors[el].childElements[object.id];
                                delete object.ancestors[el].descendants[object.id];
                            }
                        }
                    }
                }

                // remove the object itself from our control structures
                if (object._pos > -1) {
                    this.objectsList.splice(object._pos, 1);
                    for (el = object._pos; el < this.objectsList.length; el++) {
                        this.objectsList[el]._pos--;
                    }
                } else if (object.type !== Const.OBJECT_TYPE_TURTLE) {
                    JXG.debug('Board.removeObject: object ' + object.id + ' not found in list.');
                }

                delete this.objects[object.id];
                delete this.elementsByName[object.name];

                if (object.visProp && Type.evaluate(object.visProp.trace)) {
                    object.clearTrace();
                }

                // the object deletion itself is handled by the object.
                if (Type.exists(object.remove)) {
                    object.remove();
                }
            } catch (e) {
                JXG.debug(object.id + ': Could not be removed: ' + e);
            }

            this.update();

            return this;
        },

        /**
         * Removes the ancestors of an object an the object itself from board and renderer.
         * @param {JXG.GeometryElement} object The object to remove.
         * @returns {JXG.Board} Reference to the board
         */
        removeAncestors: function (object) {
            var anc;

            for (anc in object.ancestors) {
                if (object.ancestors.hasOwnProperty(anc)) {
                    this.removeAncestors(object.ancestors[anc]);
                }
            }

            this.removeObject(object);

            return this;
        },

        /**
         * Initialize some objects which are contained in every GEONExT construction by default,
         * but are not contained in the gxt files.
         * @returns {JXG.Board} Reference to the board
         */
        initGeonextBoard: function () {
            var p1, p2, p3;

            p1 = this.create('point', [0, 0], {
                id: this.id + 'g00e0',
                name: 'Ursprung',
                withLabel: false,
                visible: false,
                fixed: true
            });

            p2 = this.create('point', [1, 0], {
                id: this.id + 'gX0e0',
                name: 'Punkt_1_0',
                withLabel: false,
                visible: false,
                fixed: true
            });

            p3 = this.create('point', [0, 1], {
                id: this.id + 'gY0e0',
                name: 'Punkt_0_1',
                withLabel: false,
                visible: false,
                fixed: true
            });

            this.create('line', [p1, p2], {
                id: this.id + 'gXLe0',
                name: 'X-Achse',
                withLabel: false,
                visible: false
            });

            this.create('line', [p1, p3], {
                id: this.id + 'gYLe0',
                name: 'Y-Achse',
                withLabel: false,
                visible: false
            });

            return this;
        },

        /**
         * Change the height and width of the board's container.
         * After doing so, {@link JXG.JSXGraph.setBoundingBox} is called using
         * the actual size of the bounding box and the actual value of keepaspectratio.
         * If setBoundingbox() should not be called automatically,
         * call resizeContainer with dontSetBoundingBox == true.
         * @param {Number} canvasWidth New width of the container.
         * @param {Number} canvasHeight New height of the container.
         * @param {Boolean} [dontset=false] If true do not set the CSS width and height of the DOM element.
         * @param {Boolean} [dontSetBoundingBox=false] If true do not call setBoundingBox().
         * @returns {JXG.Board} Reference to the board
         */
        resizeContainer: function (canvasWidth, canvasHeight, dontset, dontSetBoundingBox) {
            var box;
                // w, h, cx, cy;
                // box_act,
                // shift_x = 0,
                // shift_y = 0;

            if (!dontSetBoundingBox) {
                // box_act = this.getBoundingBox();    // This is the actual bounding box.
                box = this.getBoundingBox();    // This is the actual bounding box.
            }

            this.canvasWidth = parseFloat(canvasWidth);
            this.canvasHeight = parseFloat(canvasHeight);

            // if (!dontSetBoundingBox) {
            //     box     = this.attr.boundingbox;    // This is the intended bounding box.

            //     // The shift values compensate the follow-up correction
            //     // in setBoundingBox in case of "this.keepaspectratio==true"
            //     // Otherwise, shift_x and shift_y will be zero.
            //     // Obsolet since setBoundingBox centers in case of "this.keepaspectratio==true".
            //     // shift_x = box_act[0] - box[0] / this.zoomX;
            //     // shift_y = box_act[1] - box[1] / this.zoomY;

            //     cx = (box[2] + box[0]) * 0.5; // + shift_x;
            //     cy = (box[3] + box[1]) * 0.5; // + shift_y;

            //     w = (box[2] - box[0]) * 0.5 / this.zoomX;
            //     h = (box[1] - box[3]) * 0.5 / this.zoomY;

            //     box = [cx - w, cy + h, cx + w, cy - h];
            // }

            if (!dontset) {
                this.containerObj.style.width = (this.canvasWidth) + 'px';
                this.containerObj.style.height = (this.canvasHeight) + 'px';
            }
            this.renderer.resize(this.canvasWidth, this.canvasHeight);

            if (!dontSetBoundingBox) {
                this.setBoundingBox(box, this.keepaspectratio, 'keep');
            }

            return this;
        },

        /**
         * Lists the dependencies graph in a new HTML-window.
         * @returns {JXG.Board} Reference to the board
         */
        showDependencies: function () {
            var el, t, c, f, i;

            t = '<p>\n';
            for (el in this.objects) {
                if (this.objects.hasOwnProperty(el)) {
                    i = 0;
                    for (c in this.objects[el].childElements) {
                        if (this.objects[el].childElements.hasOwnProperty(c)) {
                            i += 1;
                        }
                    }
                    if (i >= 0) {
                        t += '<strong>' + this.objects[el].id + ':<' + '/strong> ';
                    }

                    for (c in this.objects[el].childElements) {
                        if (this.objects[el].childElements.hasOwnProperty(c)) {
                            t += this.objects[el].childElements[c].id + '(' + this.objects[el].childElements[c].name + ')' + ', ';
                        }
                    }
                    t += '<p>\n';
                }
            }
            t += '<' + '/p>\n';
            f = window.open();
            f.document.open();
            f.document.write(t);
            f.document.close();
            return this;
        },

        /**
         * Lists the XML code of the construction in a new HTML-window.
         * @returns {JXG.Board} Reference to the board
         */
        showXML: function () {
            var f = window.open('');
            f.document.open();
            f.document.write('<pre>' + Type.escapeHTML(this.xmlString) + '<' + '/pre>');
            f.document.close();
            return this;
        },

        /**
         * Sets for all objects the needsUpdate flag to "true".
         * @returns {JXG.Board} Reference to the board
         */
        prepareUpdate: function () {
            var el, pEl, len = this.objectsList.length;

            /*
            if (this.attr.updatetype === 'hierarchical') {
                return this;
            }
            */

            for (el = 0; el < len; el++) {
                pEl = this.objectsList[el];
                pEl.needsUpdate = pEl.needsRegularUpdate || this.needsFullUpdate;
            }

            for (el in this.groups) {
                if (this.groups.hasOwnProperty(el)) {
                    pEl = this.groups[el];
                    pEl.needsUpdate = pEl.needsRegularUpdate || this.needsFullUpdate;
                }
            }

            return this;
        },

        /**
         * Runs through all elements and calls their update() method.
         * @param {JXG.GeometryElement} drag Element that caused the update.
         * @returns {JXG.Board} Reference to the board
         */
        updateElements: function (drag) {
            var el, pEl;
            //var childId, i = 0;

            drag = this.select(drag);

            /*
            if (Type.exists(drag)) {
                for (el = 0; el < this.objectsList.length; el++) {
                    pEl = this.objectsList[el];
                    if (pEl.id === drag.id) {
                        i = el;
                        break;
                    }
                }
            }
            */

            for (el = 0; el < this.objectsList.length; el++) {
                pEl = this.objectsList[el];
                if (this.needsFullUpdate && pEl.elementClass === Const.OBJECT_CLASS_TEXT) {
                    pEl.updateSize();
                }

                // For updates of an element we distinguish if the dragged element is updated or
                // other elements are updated.
                // The difference lies in the treatment of gliders and points based on transformations.
                pEl.update(!Type.exists(drag) || pEl.id !== drag.id)
                   .updateVisibility();
            }

            // update groups last
            for (el in this.groups) {
                if (this.groups.hasOwnProperty(el)) {
                    this.groups[el].update(drag);
                }
            }

            return this;
        },

        /**
         * Runs through all elements and calls their update() method.
         * @returns {JXG.Board} Reference to the board
         */
        updateRenderer: function () {
            var el,
                len = this.objectsList.length;

            /*
            objs = this.objectsList.slice(0);
            objs.sort(function (a, b) {
                if (a.visProp.layer < b.visProp.layer) {
                    return -1;
                } else if (a.visProp.layer === b.visProp.layer) {
                    return b.lastDragTime.getTime() - a.lastDragTime.getTime();
                } else {
                    return 1;
                }
            });
            */

            if (this.renderer.type === 'canvas') {
                this.updateRendererCanvas();
            } else {
                for (el = 0; el < len; el++) {
                    this.objectsList[el].updateRenderer();
                }
            }
            return this;
        },

        /**
         * Runs through all elements and calls their update() method.
         * This is a special version for the CanvasRenderer.
         * Here, we have to do our own layer handling.
         * @returns {JXG.Board} Reference to the board
         */
        updateRendererCanvas: function () {
            var el, pEl, i, mini, la,
                olen = this.objectsList.length,
                layers = this.options.layer,
                len = this.options.layer.numlayers,
                last = Number.NEGATIVE_INFINITY;

            for (i = 0; i < len; i++) {
                mini = Number.POSITIVE_INFINITY;

                for (la in layers) {
                    if (layers.hasOwnProperty(la)) {
                        if (layers[la] > last && layers[la] < mini) {
                            mini = layers[la];
                        }
                    }
                }

                last = mini;

                for (el = 0; el < olen; el++) {
                    pEl = this.objectsList[el];

                    if (pEl.visProp.layer === mini) {
                        pEl.prepareUpdate().updateRenderer();
                    }
                }
            }
            return this;
        },

        /**
         * Please use {@link JXG.Board.on} instead.
         * @param {Function} hook A function to be called by the board after an update occurred.
         * @param {String} [m='update'] When the hook is to be called. Possible values are <i>mouseup</i>, <i>mousedown</i> and <i>update</i>.
         * @param {Object} [context=board] Determines the execution context the hook is called. This parameter is optional, default is the
         * board object the hook is attached to.
         * @returns {Number} Id of the hook, required to remove the hook from the board.
         * @deprecated
         */
        addHook: function (hook, m, context) {
            JXG.deprecated('Board.addHook()', 'Board.on()');
            m = Type.def(m, 'update');

            context = Type.def(context, this);

            this.hooks.push([m, hook]);
            this.on(m, hook, context);

            return this.hooks.length - 1;
        },

        /**
         * Alias of {@link JXG.Board.on}.
         */
        addEvent: JXG.shortcut(JXG.Board.prototype, 'on'),

        /**
         * Please use {@link JXG.Board.off} instead.
         * @param {Number|function} id The number you got when you added the hook or a reference to the event handler.
         * @returns {JXG.Board} Reference to the board
         * @deprecated
         */
        removeHook: function (id) {
            JXG.deprecated('Board.removeHook()', 'Board.off()');
            if (this.hooks[id]) {
                this.off(this.hooks[id][0], this.hooks[id][1]);
                this.hooks[id] = null;
            }

            return this;
        },

        /**
         * Alias of {@link JXG.Board.off}.
         */
        removeEvent: JXG.shortcut(JXG.Board.prototype, 'off'),

        /**
         * Runs through all hooked functions and calls them.
         * @returns {JXG.Board} Reference to the board
         * @deprecated
         */
        updateHooks: function (m) {
            var arg = Array.prototype.slice.call(arguments, 0);

            JXG.deprecated('Board.updateHooks()', 'Board.triggerEventHandlers()');

            arg[0] = Type.def(arg[0], 'update');
            this.triggerEventHandlers([arg[0]], arguments);

            return this;
        },

        /**
         * Adds a dependent board to this board.
         * @param {JXG.Board} board A reference to board which will be updated after an update of this board occurred.
         * @returns {JXG.Board} Reference to the board
         */
        addChild: function (board) {
            if (Type.exists(board) && Type.exists(board.containerObj)) {
                this.dependentBoards.push(board);
                this.update();
            }
            return this;
        },

        /**
         * Deletes a board from the list of dependent boards.
         * @param {JXG.Board} board Reference to the board which will be removed.
         * @returns {JXG.Board} Reference to the board
         */
        removeChild: function (board) {
            var i;

            for (i = this.dependentBoards.length - 1; i >= 0; i--) {
                if (this.dependentBoards[i] === board) {
                    this.dependentBoards.splice(i, 1);
                }
            }
            return this;
        },

        /**
         * Runs through most elements and calls their update() method and update the conditions.
         * @param {JXG.GeometryElement} [drag] Element that caused the update.
         * @returns {JXG.Board} Reference to the board
         */
        update: function (drag) {
            var i, len, b, insert,
                storeActiveEl;

            if (this.inUpdate || this.isSuspendedUpdate) {
                return this;
            }
            this.inUpdate = true;

            if (this.attr.minimizereflow === 'all' && this.containerObj && this.renderer.type !== 'vml') {
                storeActiveEl = document.activeElement; // Store focus element
                insert = this.renderer.removeToInsertLater(this.containerObj);
            }

            if (this.attr.minimizereflow === 'svg' && this.renderer.type === 'svg') {
                storeActiveEl = document.activeElement;
                insert = this.renderer.removeToInsertLater(this.renderer.svgRoot);
            }

            this.prepareUpdate().updateElements(drag).updateConditions();
            this.renderer.suspendRedraw(this);
            this.updateRenderer();
            this.renderer.unsuspendRedraw();
            this.triggerEventHandlers(['update'], []);

            if (insert) {
                insert();
                storeActiveEl.focus();     // Restore focus element
            }

            // To resolve dependencies between boards
            // for (var board in JXG.boards) {
            len = this.dependentBoards.length;
            for (i = 0; i < len; i++) {
                b = this.dependentBoards[i];
                if (Type.exists(b) && b !== this) {
                    b.updateQuality = this.updateQuality;
                    b.prepareUpdate().updateElements().updateConditions();
                    b.renderer.suspendRedraw();
                    b.updateRenderer();
                    b.renderer.unsuspendRedraw();
                    b.triggerEventHandlers(['update'], []);
                }

            }

            this.inUpdate = false;
            return this;
        },

        /**
         * Runs through all elements and calls their update() method and update the conditions.
         * This is necessary after zooming and changing the bounding box.
         * @returns {JXG.Board} Reference to the board
         */
        fullUpdate: function () {
            this.needsFullUpdate = true;
            this.update();
            this.needsFullUpdate = false;
            return this;
        },

        /**
         * Adds a grid to the board according to the settings given in board.options.
         * @returns {JXG.Board} Reference to the board.
         */
        addGrid: function () {
            this.create('grid', []);

            return this;
        },

        /**
         * Removes all grids assigned to this board. Warning: This method also removes all objects depending on one or
         * more of the grids.
         * @returns {JXG.Board} Reference to the board object.
         */
        removeGrids: function () {
            var i;

            for (i = 0; i < this.grids.length; i++) {
                this.removeObject(this.grids[i]);
            }

            this.grids.length = 0;
            this.update(); // required for canvas renderer

            return this;
        },

        /**
         * Creates a new geometric element of type elementType.
         * @param {String} elementType Type of the element to be constructed given as a string e.g. 'point' or 'circle'.
         * @param {Array} parents Array of parent elements needed to construct the element e.g. coordinates for a point or two
         * points to construct a line. This highly depends on the elementType that is constructed. See the corresponding JXG.create*
         * methods for a list of possible parameters.
         * @param {Object} [attributes] An object containing the attributes to be set. This also depends on the elementType.
         * Common attributes are name, visible, strokeColor.
         * @returns {Object} Reference to the created element. This is usually a GeometryElement, but can be an array containing
         * two or more elements.
         */
        create: function (elementType, parents, attributes) {
            var el, i;

            elementType = elementType.toLowerCase();

            if (!Type.exists(parents)) {
                parents = [];
            }

            if (!Type.exists(attributes)) {
                attributes = {};
            }

            for (i = 0; i < parents.length; i++) {
                if (Type.isString(parents[i]) &&
                    !(elementType === 'text' && i === 2) &&
                    !(elementType === 'solidofrevolution3d' && i === 2) &&
                    !((elementType === 'input' || elementType === 'checkbox' || elementType === 'button') &&
                      (i === 2 || i === 3)) &&
                    !(elementType === 'curve' && i > 0) // Allow curve plots with jessiecode
                ) {
                    parents[i] = this.select(parents[i]);
                }
            }

            if (Type.isFunction(JXG.elements[elementType])) {
                el = JXG.elements[elementType](this, parents, attributes);
            } else {
                throw new Error("JSXGraph: create: Unknown element type given: " + elementType);
            }

            if (!Type.exists(el)) {
                JXG.debug("JSXGraph: create: failure creating " + elementType);
                return el;
            }

            if (el.prepareUpdate && el.update && el.updateRenderer) {
                el.fullUpdate();
            }
            return el;
        },

        /**
         * Deprecated name for {@link JXG.Board.create}.
         * @deprecated
         */
        createElement: function () {
            JXG.deprecated('Board.createElement()', 'Board.create()');
            return this.create.apply(this, arguments);
        },

        /**
         * Delete the elements drawn as part of a trace of an element.
         * @returns {JXG.Board} Reference to the board
         */
        clearTraces: function () {
            var el;

            for (el = 0; el < this.objectsList.length; el++) {
                this.objectsList[el].clearTrace();
            }

            this.numTraces = 0;
            return this;
        },

        /**
         * Stop updates of the board.
         * @returns {JXG.Board} Reference to the board
         */
        suspendUpdate: function () {
            if (!this.inUpdate) {
                this.isSuspendedUpdate = true;
            }
            return this;
        },

        /**
         * Enable updates of the board.
         * @returns {JXG.Board} Reference to the board
         */
        unsuspendUpdate: function () {
            if (this.isSuspendedUpdate) {
                this.isSuspendedUpdate = false;
                this.fullUpdate();
            }
            return this;
        },

        /**
         * Set the bounding box of the board.
         * @param {Array} bbox New bounding box [x1,y1,x2,y2]
         * @param {Boolean} [keepaspectratio=false] If set to true, the aspect ratio will be 1:1, but
         * the resulting viewport may be larger.
         * @param {String} [setZoom='reset'] Reset, keep or update the zoom level of the board. 'reset'
         * sets {@link JXG.Board#zoomX} and {@link JXG.Board#zoomY} to the start values (or 1.0).
         * 'update' adapts these values accoring to the new bounding box and 'keep' does nothing.
         * @returns {JXG.Board} Reference to the board
         */
        setBoundingBox: function (bbox, keepaspectratio, setZoom) {
            var h, w, ux, uy,
                offX = 0,
                offY = 0,
                dim = Env.getDimensions(this.container, this.document);

            if (!Type.isArray(bbox)) {
                return this;
            }

            if (bbox[0] < this.maxboundingbox[0] ||
                bbox[1] > this.maxboundingbox[1] ||
                bbox[2] > this.maxboundingbox[2] ||
                bbox[3] < this.maxboundingbox[3]) {
                return this;
            }

            if (!Type.exists(setZoom)) {
                setZoom = 'reset';
            }

            ux = this.unitX;
            uy = this.unitY;

            this.canvasWidth = parseInt(dim.width, 10);
            this.canvasHeight = parseInt(dim.height, 10);
            w = this.canvasWidth;
            h = this.canvasHeight;
            if (keepaspectratio) {
                this.unitX = w / (bbox[2] - bbox[0]);
                this.unitY = h / (bbox[1] - bbox[3]);
                if (Math.abs(this.unitX) < Math.abs(this.unitY)) {
                    this.unitY = Math.abs(this.unitX) * this.unitY / Math.abs(this.unitY);
                    // Add the additional units in equal portions above and below
                    offY = (h / this.unitY - (bbox[1] - bbox[3])) * 0.5;
                } else {
                    this.unitX = Math.abs(this.unitY) * this.unitX / Math.abs(this.unitX);
                    // Add the additional units in equal portions left and right
                    offX = (w / this.unitX - (bbox[2] - bbox[0])) * 0.5;
                }
                this.keepaspectratio = true;
            } else {
                this.unitX = w / (bbox[2] - bbox[0]);
                this.unitY = h / (bbox[1] - bbox[3]);
                this.keepaspectratio = false;
            }

            this.moveOrigin(-this.unitX * (bbox[0] - offX), this.unitY * (bbox[1] + offY));

            if (setZoom === 'update') {
                this.zoomX *= this.unitX / ux;
                this.zoomY *= this.unitY / uy;
            } else if (setZoom === 'reset') {
                this.zoomX = Type.exists(this.attr.zoomx) ? this.attr.zoomx : 1.0;
                this.zoomY = Type.exists(this.attr.zoomy) ? this.attr.zoomy : 1.0;
            }

            return this;
        },

        /**
         * Get the bounding box of the board.
         * @returns {Array} bounding box [x1,y1,x2,y2] upper left corner, lower right corner
         */
        getBoundingBox: function () {
            var ul = (new Coords(Const.COORDS_BY_SCREEN, [0, 0], this)).usrCoords,
                lr = (new Coords(Const.COORDS_BY_SCREEN, [this.canvasWidth, this.canvasHeight], this)).usrCoords;

            return [ul[1], ul[2], lr[1], lr[2]];
        },

        /**
         * Adds an animation. Animations are controlled by the boards, so the boards need to be aware of the
         * animated elements. This function tells the board about new elements to animate.
         * @param {JXG.GeometryElement} element The element which is to be animated.
         * @returns {JXG.Board} Reference to the board
         */
        addAnimation: function (element) {
            var that = this;

            this.animationObjects[element.id] = element;

            if (!this.animationIntervalCode) {
                this.animationIntervalCode = window.setInterval(function () {
                    that.animate();
                }, element.board.attr.animationdelay);
            }

            return this;
        },

        /**
         * Cancels all running animations.
         * @returns {JXG.Board} Reference to the board
         */
        stopAllAnimation: function () {
            var el;

            for (el in this.animationObjects) {
                if (this.animationObjects.hasOwnProperty(el) && Type.exists(this.animationObjects[el])) {
                    this.animationObjects[el] = null;
                    delete this.animationObjects[el];
                }
            }

            window.clearInterval(this.animationIntervalCode);
            delete this.animationIntervalCode;

            return this;
        },

        /**
         * General purpose animation function. This currently only supports moving points from one place to another. This
         * is faster than managing the animation per point, especially if there is more than one animated point at the same time.
         * @returns {JXG.Board} Reference to the board
         */
        animate: function () {
            var props, el, o, newCoords, r, p, c, cbtmp,
                count = 0,
                obj = null;

            for (el in this.animationObjects) {
                if (this.animationObjects.hasOwnProperty(el) && Type.exists(this.animationObjects[el])) {
                    count += 1;
                    o = this.animationObjects[el];

                    if (o.animationPath) {
                        if (Type.isFunction(o.animationPath)) {
                            newCoords = o.animationPath(new Date().getTime() - o.animationStart);
                        } else {
                            newCoords = o.animationPath.pop();
                        }

                        if ((!Type.exists(newCoords)) || (!Type.isArray(newCoords) && isNaN(newCoords))) {
                            delete o.animationPath;
                        } else {
                            o.setPositionDirectly(Const.COORDS_BY_USER, newCoords);
                            o.fullUpdate();
                            obj = o;
                        }
                    }
                    if (o.animationData) {
                        c = 0;

                        for (r in o.animationData) {
                            if (o.animationData.hasOwnProperty(r)) {
                                p = o.animationData[r].pop();

                                if (!Type.exists(p)) {
                                    delete o.animationData[p];
                                } else {
                                    c += 1;
                                    props = {};
                                    props[r] = p;
                                    o.setAttribute(props);
                                }
                            }
                        }

                        if (c === 0) {
                            delete o.animationData;
                        }
                    }

                    if (!Type.exists(o.animationData) && !Type.exists(o.animationPath)) {
                        this.animationObjects[el] = null;
                        delete this.animationObjects[el];

                        if (Type.exists(o.animationCallback)) {
                            cbtmp = o.animationCallback;
                            o.animationCallback = null;
                            cbtmp();
                        }
                    }
                }
            }

            if (count === 0) {
                window.clearInterval(this.animationIntervalCode);
                delete this.animationIntervalCode;
            } else {
                this.update(obj);
            }

            return this;
        },

        /**
         * Migrate the dependency properties of the point src
         * to the point dest and  delete the point src.
         * For example, a circle around the point src
         * receives the new center dest. The old center src
         * will be deleted.
         * @param {JXG.Point} src Original point which will be deleted
         * @param {JXG.Point} dest New point with the dependencies of src.
         * @param {Boolean} copyName Flag which decides if the name of the src element is copied to the
         *  dest element.
         * @returns {JXG.Board} Reference to the board
         */
        migratePoint: function (src, dest, copyName) {
            var child, childId, prop, found, i, srcLabelId, srcHasLabel = false;

            src = this.select(src);
            dest = this.select(dest);

            if (Type.exists(src.label)) {
                srcLabelId = src.label.id;
                srcHasLabel = true;
                this.removeObject(src.label);
            }

            for (childId in src.childElements) {
                if (src.childElements.hasOwnProperty(childId)) {
                    child = src.childElements[childId];
                    found = false;

                    for (prop in child) {
                        if (child.hasOwnProperty(prop)) {
                            if (child[prop] ===  src) {
                                child[prop] = dest;
                                found = true;
                            }
                        }
                    }

                    if (found) {
                        delete src.childElements[childId];
                    }

                    for (i = 0; i < child.parents.length; i++) {
                        if (child.parents[i] === src.id) {
                            child.parents[i] = dest.id;
                        }
                    }

                    dest.addChild(child);
                }
            }

            // The destination object should receive the name
            // and the label of the originating (src) object
            if (copyName) {
                if (srcHasLabel) {
                    delete dest.childElements[srcLabelId];
                    delete dest.descendants[srcLabelId];
                }

                if (dest.label) {
                    this.removeObject(dest.label);
                }

                delete this.elementsByName[dest.name];
                dest.name = src.name;
                if (srcHasLabel) {
                    dest.createLabel();
                }
            }

            this.removeObject(src);

            if (Type.exists(dest.name) && dest.name !== '') {
                this.elementsByName[dest.name] = dest;
            }

            this.fullUpdate();

            return this;
        },

        /**
         * Initializes color blindness simulation.
         * @param {String} deficiency Describes the color blindness deficiency which is simulated. Accepted values are 'protanopia', 'deuteranopia', and 'tritanopia'.
         * @returns {JXG.Board} Reference to the board
         */
        emulateColorblindness: function (deficiency) {
            var e, o;

            if (!Type.exists(deficiency)) {
                deficiency = 'none';
            }

            if (this.currentCBDef === deficiency) {
                return this;
            }

            for (e in this.objects) {
                if (this.objects.hasOwnProperty(e)) {
                    o = this.objects[e];

                    if (deficiency !== 'none') {
                        if (this.currentCBDef === 'none') {
                            // this could be accomplished by JXG.extend, too. But do not use
                            // JXG.deepCopy as this could result in an infinite loop because in
                            // visProp there could be geometry elements which contain the board which
                            // contains all objects which contain board etc.
                            o.visPropOriginal = {
                                strokecolor: o.visProp.strokecolor,
                                fillcolor: o.visProp.fillcolor,
                                highlightstrokecolor: o.visProp.highlightstrokecolor,
                                highlightfillcolor: o.visProp.highlightfillcolor
                            };
                        }
                        o.setAttribute({
                            strokecolor: Color.rgb2cb(Type.evaluate(o.visPropOriginal.strokecolor), deficiency),
                            fillcolor: Color.rgb2cb(Type.evaluate(o.visPropOriginal.fillcolor), deficiency),
                            highlightstrokecolor: Color.rgb2cb(Type.evaluate(o.visPropOriginal.highlightstrokecolor), deficiency),
                            highlightfillcolor: Color.rgb2cb(Type.evaluate(o.visPropOriginal.highlightfillcolor), deficiency)
                        });
                    } else if (Type.exists(o.visPropOriginal)) {
                        JXG.extend(o.visProp, o.visPropOriginal);
                    }
                }
            }
            this.currentCBDef = deficiency;
            this.update();

            return this;
        },

        /**
         * Select a single or multiple elements at once.
         * @param {String|Object|function} str The name, id or a reference to a JSXGraph element on this board. An object will
         * be used as a filter to return multiple elements at once filtered by the properties of the object.
         * @param {Boolean} onlyByIdOrName If true (default:false) elements are only filtered by their id, name or groupId.
         * The advanced filters consisting of objects or functions are ignored.
         * @returns {JXG.GeometryElement|JXG.Composition}
         * @example
         * // select the element with name A
         * board.select('A');
         *
         * // select all elements with strokecolor set to 'red' (but not '#ff0000')
         * board.select({
         *   strokeColor: 'red'
         * });
         *
         * // select all points on or below the x axis and make them black.
         * board.select({
         *   elementClass: JXG.OBJECT_CLASS_POINT,
         *   Y: function (v) {
         *     return v <= 0;
         *   }
         * }).setAttribute({color: 'black'});
         *
         * // select all elements
         * board.select(function (el) {
         *   return true;
         * });
         */
        select: function (str, onlyByIdOrName) {
            var flist, olist, i, l,
                s = str;

            if (s === null) {
                return s;
            }

            // it's a string, most likely an id or a name.
            if (Type.isString(s) && s !== '') {
                // Search by ID
                if (Type.exists(this.objects[s])) {
                    s = this.objects[s];
                // Search by name
                } else if (Type.exists(this.elementsByName[s])) {
                    s = this.elementsByName[s];
                // Search by group ID
                } else if (Type.exists(this.groups[s])) {
                    s = this.groups[s];
                }
            // it's a function or an object, but not an element
            } else if (!onlyByIdOrName &&
                (Type.isFunction(s) ||
                 (Type.isObject(s) && !Type.isFunction(s.setAttribute))
                )) {
                flist = Type.filterElements(this.objectsList, s);

                olist = {};
                l = flist.length;
                for (i = 0; i < l; i++) {
                    olist[flist[i].id] = flist[i];
                }
                s = new Composition(olist);
            // it's an element which has been deleted (and still hangs around, e.g. in an attractor list
            } else if (Type.isObject(s) && Type.exists(s.id) && !Type.exists(this.objects[s.id])) {
                s = null;
            }

            return s;
        },

        /**
         * Checks if the given point is inside the boundingbox.
         * @param {Number|JXG.Coords} x User coordinate or {@link JXG.Coords} object.
         * @param {Number} [y] User coordinate. May be omitted in case <tt>x</tt> is a {@link JXG.Coords} object.
         * @returns {Boolean}
         */
        hasPoint: function (x, y) {
            var px = x,
                py = y,
                bbox = this.getBoundingBox();

            if (Type.exists(x) && Type.isArray(x.usrCoords)) {
                px = x.usrCoords[1];
                py = x.usrCoords[2];
            }

            return !!(Type.isNumber(px) && Type.isNumber(py) &&
                bbox[0] < px && px < bbox[2] && bbox[1] > py && py > bbox[3]);
        },

        /**
         * Update CSS transformations of type scaling. It is used to correct the mouse position
         * in {@link JXG.Board.getMousePosition}.
         * The inverse transformation matrix is updated on each mouseDown and touchStart event.
         *
         * It is up to the user to call this method after an update of the CSS transformation
         * in the DOM.
         */
        updateCSSTransforms: function () {
            var obj = this.containerObj,
                o = obj,
                o2 = obj;

            this.cssTransMat = Env.getCSSTransformMatrix(o);

            /*
             * In Mozilla and Webkit: offsetParent seems to jump at least to the next iframe,
             * if not to the body. In IE and if we are in an position:absolute environment
             * offsetParent walks up the DOM hierarchy.
             * In order to walk up the DOM hierarchy also in Mozilla and Webkit
             * we need the parentNode steps.
             */
            o = o.offsetParent;
            while (o) {
                this.cssTransMat = Mat.matMatMult(Env.getCSSTransformMatrix(o), this.cssTransMat);

                o2 = o2.parentNode;
                while (o2 !== o) {
                    this.cssTransMat = Mat.matMatMult(Env.getCSSTransformMatrix(o), this.cssTransMat);
                    o2 = o2.parentNode;
                }

                o = o.offsetParent;
            }
            this.cssTransMat = Mat.inverse(this.cssTransMat);

            return this;
        },

        /**
         * Start selection mode. This function can either be triggered from outside or by
         * a down event together with correct key pressing. The default keys are
         * shift+ctrl. But this can be changed in the options.
         *
         * Starting from out side can be realized for example with a button like this:
         * <pre>
         * 	&lt;button onclick="board.startSelectionMode()"&gt;Start&lt;/button&gt;
         * </pre>
         * @example
         * //
         * // Set a new bounding box from the selection rectangle
         * //
         * var board = JXG.JSXGraph.initBoard('jxgbox', {
         *         boundingBox:[-3,2,3,-2],
         *         keepAspectRatio: false,
         *         axis:true,
         *         selection: {
         *             enabled: true,
         *             needShift: false,
         *             needCtrl: true,
         *             withLines: false,
         *             vertices: {
         *                 visible: false
         *             },
         *             fillColor: '#ffff00',
         *         }
         *      });
         *
         * var f = function f(x) { return Math.cos(x); },
         *     curve = board.create('functiongraph', [f]);
         *
         * board.on('stopselecting', function(){
         *     var box = board.stopSelectionMode(),
         *
         *         // bbox has the coordinates of the selection rectangle.
         *         // Attention: box[i].usrCoords have the form [1, x, y], i.e.
         *         // are homogeneous coordinates.
         *         bbox = box[0].usrCoords.slice(1).concat(box[1].usrCoords.slice(1));
         *
         *         // Set a new bounding box
         *         board.setBoundingBox(bbox, false);
         *  });
         *
         *
         * </pre><div class="jxgbox" id="JXG11eff3a6-8c50-11e5-b01d-901b0e1b8723" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *     //
         *     // Set a new bounding box from the selection rectangle
         *     //
         *     var board = JXG.JSXGraph.initBoard('JXG11eff3a6-8c50-11e5-b01d-901b0e1b8723', {
         *             boundingBox:[-3,2,3,-2],
         *             keepAspectRatio: false,
         *             axis:true,
         *             selection: {
         *                 enabled: true,
         *                 needShift: false,
         *                 needCtrl: true,
         *                 withLines: false,
         *                 vertices: {
         *                     visible: false
         *                 },
         *                 fillColor: '#ffff00',
         *             }
         *        });
         *
         *     var f = function f(x) { return Math.cos(x); },
         *         curve = board.create('functiongraph', [f]);
         *
         *     board.on('stopselecting', function(){
         *         var box = board.stopSelectionMode(),
         *
         *             // bbox has the coordinates of the selection rectangle.
         *             // Attention: box[i].usrCoords have the form [1, x, y], i.e.
         *             // are homogeneous coordinates.
         *             bbox = box[0].usrCoords.slice(1).concat(box[1].usrCoords.slice(1));
         *
         *             // Set a new bounding box
         *             board.setBoundingBox(bbox, false);
         *      });
         *     })();
         *
         * </script><pre>
         *
         */
        startSelectionMode: function () {
            this.selectingMode = true;
            this.selectionPolygon.setAttribute({visible: true});
            this.selectingBox = [[0, 0], [0, 0]];
            this._setSelectionPolygonFromBox();
            this.selectionPolygon.fullUpdate();
        },

        /**
         * Finalize the selection: disable selection mode and return the coordinates
         * of the selection rectangle.
         * @returns {Array} Coordinates of the selection rectangle. The array
         * contains two {@link JXG.Coords} objects. One the upper left corner and
         * the second for the lower right corner.
         */
        stopSelectionMode: function () {
            this.selectingMode = false;
            this.selectionPolygon.setAttribute({visible: false});
            return [this.selectionPolygon.vertices[0].coords, this.selectionPolygon.vertices[2].coords];
        },

        /**
         * Start the selection of a region.
         * @private
         * @param  {Array} pos Screen coordiates of the upper left corner of the
         * selection rectangle.
         */
        _startSelecting: function (pos) {
            this.isSelecting = true;
            this.selectingBox = [ [pos[0], pos[1]], [pos[0], pos[1]] ];
            this._setSelectionPolygonFromBox();
        },

        /**
         * Update the selection rectangle during a move event.
         * @private
         * @param  {Array} pos Screen coordiates of the move event
         */
        _moveSelecting: function (pos) {
            if (this.isSelecting) {
                this.selectingBox[1] = [pos[0], pos[1]];
                this._setSelectionPolygonFromBox();
                this.selectionPolygon.fullUpdate();
            }
        },

        /**
         * Update the selection rectangle during an up event. Stop selection.
         * @private
         * @param  {Object} evt Event object
         */
        _stopSelecting:  function (evt) {
            var pos = this.getMousePosition(evt);

            this.isSelecting = false;
            this.selectingBox[1] = [pos[0], pos[1]];
            this._setSelectionPolygonFromBox();
        },

        /**
         * Update the Selection rectangle.
         * @private
         */
        _setSelectionPolygonFromBox: function () {
               var A = this.selectingBox[0],
                B = this.selectingBox[1];

               this.selectionPolygon.vertices[0].setPositionDirectly(JXG.COORDS_BY_SCREEN, [A[0], A[1]]);
               this.selectionPolygon.vertices[1].setPositionDirectly(JXG.COORDS_BY_SCREEN, [A[0], B[1]]);
               this.selectionPolygon.vertices[2].setPositionDirectly(JXG.COORDS_BY_SCREEN, [B[0], B[1]]);
               this.selectionPolygon.vertices[3].setPositionDirectly(JXG.COORDS_BY_SCREEN, [B[0], A[1]]);
        },

        /**
         * Test if a down event should start a selection. Test if the
         * required keys are pressed. If yes, {@link JXG.Board.startSelectionMode} is called.
         * @param  {Object} evt Event object
         */
        _testForSelection: function (evt) {
            if (this._isRequiredKeyPressed(evt, 'selection')) {
                if (!Type.exists(this.selectionPolygon)) {
                    this._createSelectionPolygon(this.attr);
                }
                this.startSelectionMode();
            }
        },

        /**
         * Create the internal selection polygon, which will be available as board.selectionPolygon.
         * @private
         * @param  {Object} attr board attributes, e.g. the subobject board.attr.
         * @returns {Object} pointer to the board to enable chaining.
         */
        _createSelectionPolygon: function(attr) {
            var selectionattr;

            if (!Type.exists(this.selectionPolygon)) {
                selectionattr = Type.copyAttributes(attr, Options, 'board', 'selection');
                if (selectionattr.enabled === true) {
                    this.selectionPolygon = this.create('polygon', [[0, 0], [0, 0], [0, 0], [0, 0]], selectionattr);
                }
            }

            return this;
        },

        /* **************************
         *     EVENT DEFINITION
         * for documentation purposes
         * ************************** */

        //region Event handler documentation

        /**
         * @event
         * @description Whenever the user starts to touch or click the board.
         * @name JXG.Board#down
         * @param {Event} e The browser's event object.
         */
        __evt__down: function (e) { },

        /**
         * @event
         * @description Whenever the user starts to click on the board.
         * @name JXG.Board#mousedown
         * @param {Event} e The browser's event object.
         */
        __evt__mousedown: function (e) { },

        /**
         * @event
         * @description Whenever the user taps the pen on the board.
         * @name JXG.Board#pendown
         * @param {Event} e The browser's event object.
         */
        __evt__pendown: function (e) { },

        /**
         * @event
         * @description Whenever the user starts to click on the board with a
         * device sending pointer events.
         * @name JXG.Board#pointerdown
         * @param {Event} e The browser's event object.
         */
        __evt__pointerdown: function (e) { },

        /**
         * @event
         * @description Whenever the user starts to touch the board.
         * @name JXG.Board#touchstart
         * @param {Event} e The browser's event object.
         */
        __evt__touchstart: function (e) { },

        /**
         * @event
         * @description Whenever the user stops to touch or click the board.
         * @name JXG.Board#up
         * @param {Event} e The browser's event object.
         */
        __evt__up: function (e) { },

        /**
         * @event
         * @description Whenever the user releases the mousebutton over the board.
         * @name JXG.Board#mouseup
         * @param {Event} e The browser's event object.
         */
        __evt__mouseup: function (e) { },

        /**
         * @event
         * @description Whenever the user releases the mousebutton over the board with a
         * device sending pointer events.
         * @name JXG.Board#pointerup
         * @param {Event} e The browser's event object.
         */
        __evt__pointerup: function (e) { },

        /**
         * @event
         * @description Whenever the user stops touching the board.
         * @name JXG.Board#touchend
         * @param {Event} e The browser's event object.
         */
        __evt__touchend: function (e) { },

        /**
         * @event
         * @description This event is fired whenever the user is moving the finger or mouse pointer over the board.
         * @name JXG.Board#move
         * @param {Event} e The browser's event object.
         * @param {Number} mode The mode the board currently is in
         * @see JXG.Board#mode
         */
        __evt__move: function (e, mode) { },

        /**
         * @event
         * @description This event is fired whenever the user is moving the mouse over the board.
         * @name JXG.Board#mousemove
         * @param {Event} e The browser's event object.
         * @param {Number} mode The mode the board currently is in
         * @see JXG.Board#mode
         */
        __evt__mousemove: function (e, mode) { },

        /**
         * @event
         * @description This event is fired whenever the user is moving the pen over the board.
         * @name JXG.Board#penmove
         * @param {Event} e The browser's event object.
         * @param {Number} mode The mode the board currently is in
         * @see JXG.Board#mode
         */
        __evt__penmove: function (e, mode) { },

        /**
         * @event
         * @description This event is fired whenever the user is moving the mouse over the board  with a
         * device sending pointer events.
         * @name JXG.Board#pointermove
         * @param {Event} e The browser's event object.
         * @param {Number} mode The mode the board currently is in
         * @see JXG.Board#mode
         */
        __evt__pointermove: function (e, mode) { },

        /**
         * @event
         * @description This event is fired whenever the user is moving the finger over the board.
         * @name JXG.Board#touchmove
         * @param {Event} e The browser's event object.
         * @param {Number} mode The mode the board currently is in
         * @see JXG.Board#mode
         */
        __evt__touchmove: function (e, mode) { },

        /**
         * @event
         * @description Whenever an element is highlighted this event is fired.
         * @name JXG.Board#hit
         * @param {Event} e The browser's event object.
         * @param {JXG.GeometryElement} el The hit element.
         * @param target
         *
         * @example
         * var c = board.create('circle', [[1, 1], 2]);
         * board.on('hit', function(evt, el) {
         *     console.log("Hit element", el);
         * });
         *
         * </pre><div id="JXG19eb31ac-88e6-11e8-bcb5-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG19eb31ac-88e6-11e8-bcb5-901b0e1b8723',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var c = board.create('circle', [[1, 1], 2]);
         *     board.on('hit', function(evt, el) {
         *         console.log("Hit element", el);
         *     });
         *
         *     })();
         *
         * </script><pre>
         */
        __evt__hit: function (e, el, target) { },

        /**
         * @event
         * @description Whenever an element is highlighted this event is fired.
         * @name JXG.Board#mousehit
         * @see JXG.Board#hit
         * @param {Event} e The browser's event object.
         * @param {JXG.GeometryElement} el The hit element.
         * @param target
         */
        __evt__mousehit: function (e, el, target) { },

        /**
         * @event
         * @description This board is updated.
         * @name JXG.Board#update
         */
        __evt__update: function () { },

        /**
         * @event
         * @description The bounding box of the board has changed.
         * @name JXG.Board#boundingbox
         */
        __evt__boundingbox: function () { },

        /**
         * @event
         * @description Select a region is started during a down event or by calling
         * {@link JXG.Board.startSelectionMode}
         * @name JXG.Board#startselecting
         */
         __evt__startselecting: function () { },

         /**
         * @event
         * @description Select a region is started during a down event
         * from a device sending mouse events or by calling
         * {@link JXG.Board.startSelectionMode}.
         * @name JXG.Board#mousestartselecting
         */
         __evt__mousestartselecting: function () { },

         /**
         * @event
         * @description Select a region is started during a down event
         * from a device sending pointer events or by calling
         * {@link JXG.Board.startSelectionMode}.
         * @name JXG.Board#pointerstartselecting
         */
         __evt__pointerstartselecting: function () { },

         /**
         * @event
         * @description Select a region is started during a down event
         * from a device sending touch events or by calling
         * {@link JXG.Board.startSelectionMode}.
         * @name JXG.Board#touchstartselecting
         */
         __evt__touchstartselecting: function () { },

         /**
          * @event
          * @description Selection of a region is stopped during an up event.
          * @name JXG.Board#stopselecting
          */
         __evt__stopselecting: function () { },

         /**
         * @event
         * @description Selection of a region is stopped during an up event
         * from a device sending mouse events.
         * @name JXG.Board#mousestopselecting
         */
         __evt__mousestopselecting: function () { },

         /**
         * @event
         * @description Selection of a region is stopped during an up event
         * from a device sending pointer events.
         * @name JXG.Board#pointerstopselecting
         */
         __evt__pointerstopselecting: function () { },

         /**
         * @event
         * @description Selection of a region is stopped during an up event
         * from a device sending touch events.
         * @name JXG.Board#touchstopselecting
         */
         __evt__touchstopselecting: function () { },

         /**
         * @event
         * @description A move event while selecting of a region is active.
         * @name JXG.Board#moveselecting
         */
         __evt__moveselecting: function () { },

         /**
         * @event
         * @description A move event while selecting of a region is active
         * from a device sending mouse events.
         * @name JXG.Board#mousemoveselecting
         */
         __evt__mousemoveselecting: function () { },

         /**
         * @event
         * @description Select a region is started during a down event
         * from a device sending mouse events.
         * @name JXG.Board#pointermoveselecting
         */
         __evt__pointermoveselecting: function () { },

         /**
         * @event
         * @description Select a region is started during a down event
         * from a device sending touch events.
         * @name JXG.Board#touchmoveselecting
         */
         __evt__touchmoveselecting: function () { },

        /**
         * @ignore
         */
        __evt: function () {},

        //endregion

        /**
         * Expand the JSXGraph construction to fullscreen.
         * In order to preserve the proportions of the JSXGraph element,
         * a wrapper div is created which is set to fullscreen.
         * <p>
         * The wrapping div has the CSS class 'jxgbox_wrap_private' which is
         * defined in the file 'jsxgraph.css'
         * <p>
         * This feature is not available on iPhones (as of December 2021).
         *
         * @param {String} id (Optional) id of the div element which is brought to fullscreen.
         * If not provided, this defaults to the JSXGraph div. However, it may be necessary for the aspect ratio trick
         * which using padding-bottom/top and an out div element. Then, the id of the outer div has to be supplied.
         *
         * @return {JXG.Board} Reference to the board
         *
         * @example
         * &lt;div id='jxgbox' class='jxgbox' style='width:500px; height:200px;'&gt;&lt;/div&gt;
         * &lt;button onClick="board.toFullscreen()"&gt;Fullscreen&lt;/button&gt;
         *
         * &lt;script language="Javascript" type='text/javascript'&gt;
         * var board = JXG.JSXGraph.initBoard('jxgbox', {axis:true, boundingbox:[-5,5,5,-5]});
         * var p = board.create('point', [0, 1]);
         * &lt;/script&gt;
         *
         * </pre><div id="JXGd5bab8b6-fd40-11e8-ab14-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *      var board_d5bab8b6;
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXGd5bab8b6-fd40-11e8-ab14-901b0e1b8723',
         *             {boundingbox:[-5,5,5,-5], axis: true, showcopyright: false, shownavigation: false});
         *         var p = board.create('point', [0, 1]);
         *         board_d5bab8b6 = board;
         *     })();
         * </script>
         * <button onClick="board_d5bab8b6.toFullscreen()">Fullscreen</button>
         * <pre>
         *
         * @example
         * &lt;div id='outer' style='max-width: 500px; margin: 0 auto;'&gt;
         * &lt;div id='jxgbox' class='jxgbox' style='height: 0; padding-bottom: 100%'&gt;&lt;/div&gt;
         * &lt;/div&gt;
         * &lt;button onClick="board.toFullscreen('outer')"&gt;Fullscreen&lt;/button&gt;
         *
         * &lt;script language="Javascript" type='text/javascript'&gt;
         * var board = JXG.JSXGraph.initBoard('jxgbox', {
         *     axis:true,
         *     boundingbox:[-5,5,5,-5],
         *     fullscreen: { id: 'outer' },
         *     showFullscreen: true
         * });
         * var p = board.create('point', [-2, 3], {});
         * &lt;/script&gt;
         *
         * </pre><div id="JXG7103f6b_outer" style='max-width: 500px; margin: 0 auto;'>
         * <div id="JXG7103f6be-6993-4ff8-8133-c78e50a8afac" class="jxgbox" style="height: 0; padding-bottom: 100%;"></div>
         * </div>
         * <button onClick="board_JXG7103f6be.toFullscreen('JXG7103f6b_outer')">Fullscreen</button>
         * <script type="text/javascript">
         *     var board_JXG7103f6be;
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG7103f6be-6993-4ff8-8133-c78e50a8afac',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, fullscreen: { id: 'JXG7103f6b_outer' }, showFullscreen: true,
         *              showcopyright: false, shownavigation: false});
         *     var p = board.create('point', [-2, 3], {});
         *     board_JXG7103f6be = board;
         *     })();
         *
         * </script><pre>
         *
         *
         */
        toFullscreen: function (id) {
            var wrap_id, wrap_node, inner_node;

            id = id || this.container;
            this._fullscreen_inner_id = id;
            inner_node = document.getElementById(id);
            wrap_id = 'fullscreenwrap_' + id;

            // Wrap a div around the JSXGraph div.
            if (this.document.getElementById(wrap_id)) {
                wrap_node = this.document.getElementById(wrap_id);
            } else {
                wrap_node = document.createElement('div');
                wrap_node.classList.add('JXG_wrap_private');
                wrap_node.setAttribute('id', wrap_id);
                inner_node.parentNode.insertBefore(wrap_node, inner_node);
                wrap_node.appendChild(inner_node);
            }

            // Get the real width and height of the JSXGraph div
            // and determine the scaling and vertical shift amount
            this._fullscreen_res = Env._getScaleFactors(inner_node);

            // Trigger fullscreen mode
            wrap_node.requestFullscreen = wrap_node.requestFullscreen ||
                wrap_node.webkitRequestFullscreen ||
                wrap_node.mozRequestFullScreen ||
                wrap_node.msRequestFullscreen;

            if (wrap_node.requestFullscreen) {
                wrap_node.requestFullscreen();
            }

            return this;
        },

        /**
         * If fullscreen mode is toggled, the possible CSS transformations
         * which are applied to the JSXGraph canvas have to be reread.
         * Otherwise the position of upper left corner is wrongly interpreted.
         *
         * @param  {Object} evt fullscreen event object (unused)
         */
        fullscreenListener: function (evt) {
            var res, inner_id, inner_node;

            inner_id = this._fullscreen_inner_id;
            if (!Type.exists(inner_id)) {
                return;
            }

            document.fullscreenElement = document.fullscreenElement ||
                    document.webkitFullscreenElement ||
                    document.mozFullscreenElement ||
                    document.msFullscreenElement;

            inner_node = document.getElementById(inner_id);
            // If full screen mode is started we have to remove CSS margin around the JSXGraph div.
            // Otherwise, the positioning of the fullscreen div will be false.
            // When leaving the fullscreen mode, the margin is put back in.
            if (document.fullscreenElement) {
                // Just entered fullscreen mode

                // Get the data computed in board.toFullscreen()
                res = this._fullscreen_res;

                // Store the scaling data.
                // It is used in AbstractRenderer.updateText to restore the scaling matrix
                // which is removed by MathJax.
                // Further, the CSS margin has to be removed when in fullscreen mode,
                // and must be restored later.
                inner_node._cssFullscreenStore = {
                    id: document.fullscreenElement.id,
                    isFullscreen: true,
                    margin: inner_node.style.margin,
                    width: inner_node.style.width,
                    scale: res.scale,
                    vshift: res.vshift
                };

                inner_node.style.margin = '';
                inner_node.style.width = res.width + 'px';

                // Do the shifting and scaling via CSS pseudo rules
                // We do this after fullscreen mode has been established to get the correct size
                // of the JSXGraph div.
                Env.scaleJSXGraphDiv(document.fullscreenElement.id, inner_id, res.scale, res.vshift);

                // Clear document.fullscreenElement, because Safari doesn't to it and
                // when leaving full screen mode it is still set.
                document.fullscreenElement = null;

            } else if (Type.exists(inner_node._cssFullscreenStore)) {
                // Just left the fullscreen mode

                // Remove the CSS rules added in Env.scaleJSXGraphDiv
                try {
                    document.styleSheets[document.styleSheets.length - 1].deleteRule(0);
                } catch (err) {
                    console.log('JSXGraph: Could not remove CSS rules for full screen mode');
                }

                inner_node._cssFullscreenStore.isFullscreen = false;
                inner_node.style.margin = inner_node._cssFullscreenStore.margin;
                inner_node.style.width = inner_node._cssFullscreenStore.width;

            }

            this.updateCSSTransforms();
        },

        /**
         * Function to animate a curve rolling on another curve.
         * @param {Curve} c1 JSXGraph curve building the floor where c2 rolls
         * @param {Curve} c2 JSXGraph curve which rolls on c1.
         * @param {number} start_c1 The parameter t such that c1(t) touches c2. This is the start position of the
         *                          rolling process
         * @param {Number} stepsize Increase in t in each step for the curve c1
         * @param {Number} direction
         * @param {Number} time Delay time for setInterval()
         * @param {Array} pointlist Array of points which are rolled in each step. This list should contain
         *      all points which define c2 and gliders on c2.
         *
         * @example
         *
         * // Line which will be the floor to roll upon.
         * var line = brd.create('curve', [function (t) { return t;}, function (t){ return 1;}], {strokeWidth:6});
         * // Center of the rolling circle
         * var C = brd.create('point',[0,2],{name:'C'});
         * // Starting point of the rolling circle
         * var P = brd.create('point',[0,1],{name:'P', trace:true});
         * // Circle defined as a curve. The circle "starts" at P, i.e. circle(0) = P
         * var circle = brd.create('curve',[
         *           function (t){var d = P.Dist(C),
         *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
         *                       t += beta;
         *                       return C.X()+d*Math.cos(t);
         *           },
         *           function (t){var d = P.Dist(C),
         *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
         *                       t += beta;
         *                       return C.Y()+d*Math.sin(t);
         *           },
         *           0,2*Math.PI],
         *           {strokeWidth:6, strokeColor:'green'});
         *
         * // Point on circle
         * var B = brd.create('glider',[0,2,circle],{name:'B', color:'blue',trace:false});
         * var roll = brd.createRoulette(line, circle, 0, Math.PI/20, 1, 100, [C,P,B]);
         * roll.start() // Start the rolling, to be stopped by roll.stop()
         *
         * </pre><div class="jxgbox" id="JXGe5e1b53c-a036-4a46-9e35-190d196beca5" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         * var brd = JXG.JSXGraph.initBoard('JXGe5e1b53c-a036-4a46-9e35-190d196beca5', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright:false, shownavigation: false});
         * // Line which will be the floor to roll upon.
         * var line = brd.create('curve', [function (t) { return t;}, function (t){ return 1;}], {strokeWidth:6});
         * // Center of the rolling circle
         * var C = brd.create('point',[0,2],{name:'C'});
         * // Starting point of the rolling circle
         * var P = brd.create('point',[0,1],{name:'P', trace:true});
         * // Circle defined as a curve. The circle "starts" at P, i.e. circle(0) = P
         * var circle = brd.create('curve',[
         *           function (t){var d = P.Dist(C),
         *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
         *                       t += beta;
         *                       return C.X()+d*Math.cos(t);
         *           },
         *           function (t){var d = P.Dist(C),
         *                           beta = JXG.Math.Geometry.rad([C.X()+1,C.Y()],C,P);
         *                       t += beta;
         *                       return C.Y()+d*Math.sin(t);
         *           },
         *           0,2*Math.PI],
         *           {strokeWidth:6, strokeColor:'green'});
         *
         * // Point on circle
         * var B = brd.create('glider',[0,2,circle],{name:'B', color:'blue',trace:false});
         * var roll = brd.createRoulette(line, circle, 0, Math.PI/20, 1, 100, [C,P,B]);
         * roll.start() // Start the rolling, to be stopped by roll.stop()
         * </script><pre>
         */
        createRoulette: function (c1, c2, start_c1, stepsize, direction, time, pointlist) {
            var brd = this,
                Roulette = function () {
                    var alpha = 0, Tx = 0, Ty = 0,
                        t1 = start_c1,
                        t2 = Numerics.root(
                            function (t) {
                                var c1x = c1.X(t1),
                                    c1y = c1.Y(t1),
                                    c2x = c2.X(t),
                                    c2y = c2.Y(t);

                                return (c1x - c2x) * (c1x - c2x) + (c1y - c2y) * (c1y - c2y);
                            },
                            [0, Math.PI * 2]
                        ),
                        t1_new = 0.0, t2_new = 0.0,
                        c1dist,

                        rotation = brd.create('transform', [
                            function () {
                                return alpha;
                            }
                        ], {type: 'rotate'}),

                        rotationLocal = brd.create('transform', [
                            function () {
                                return alpha;
                            },
                            function () {
                                return c1.X(t1);
                            },
                            function () {
                                return c1.Y(t1);
                            }
                        ], {type: 'rotate'}),

                        translate = brd.create('transform', [
                            function () {
                                return Tx;
                            },
                            function () {
                                return Ty;
                            }
                        ], {type: 'translate'}),

                        // arc length via Simpson's rule.
                        arclen = function (c, a, b) {
                            var cpxa = Numerics.D(c.X)(a),
                                cpya = Numerics.D(c.Y)(a),
                                cpxb = Numerics.D(c.X)(b),
                                cpyb = Numerics.D(c.Y)(b),
                                cpxab = Numerics.D(c.X)((a + b) * 0.5),
                                cpyab = Numerics.D(c.Y)((a + b) * 0.5),

                                fa = Math.sqrt(cpxa * cpxa + cpya * cpya),
                                fb = Math.sqrt(cpxb * cpxb + cpyb * cpyb),
                                fab = Math.sqrt(cpxab * cpxab + cpyab * cpyab);

                            return (fa + 4 * fab + fb) * (b - a) / 6;
                        },

                        exactDist = function (t) {
                            return c1dist - arclen(c2, t2, t);
                        },

                        beta = Math.PI / 18,
                        beta9 = beta * 9,
                        interval = null;

                    this.rolling = function () {
                        var h, g, hp, gp, z;

                        t1_new = t1 + direction * stepsize;

                        // arc length between c1(t1) and c1(t1_new)
                        c1dist = arclen(c1, t1, t1_new);

                        // find t2_new such that arc length between c2(t2) and c1(t2_new) equals c1dist.
                        t2_new = Numerics.root(exactDist, t2);

                        // c1(t) as complex number
                        h = new Complex(c1.X(t1_new), c1.Y(t1_new));

                        // c2(t) as complex number
                        g = new Complex(c2.X(t2_new), c2.Y(t2_new));

                        hp = new Complex(Numerics.D(c1.X)(t1_new), Numerics.D(c1.Y)(t1_new));
                        gp = new Complex(Numerics.D(c2.X)(t2_new), Numerics.D(c2.Y)(t2_new));

                        // z is angle between the tangents of c1 at t1_new, and c2 at t2_new
                        z = Complex.C.div(hp, gp);

                        alpha = Math.atan2(z.imaginary, z.real);
                        // Normalizing the quotient
                        z.div(Complex.C.abs(z));
                        z.mult(g);
                        Tx = h.real - z.real;

                        // T = h(t1_new)-g(t2_new)*h'(t1_new)/g'(t2_new);
                        Ty = h.imaginary - z.imaginary;

                        // -(10-90) degrees: make corners roll smoothly
                        if (alpha < -beta && alpha > -beta9) {
                            alpha = -beta;
                            rotationLocal.applyOnce(pointlist);
                        } else if (alpha > beta && alpha < beta9) {
                            alpha = beta;
                            rotationLocal.applyOnce(pointlist);
                        } else {
                            rotation.applyOnce(pointlist);
                            translate.applyOnce(pointlist);
                            t1 = t1_new;
                            t2 = t2_new;
                        }
                        brd.update();
                    };

                    this.start = function () {
                        if (time > 0) {
                            interval = window.setInterval(this.rolling, time);
                        }
                        return this;
                    };

                    this.stop = function () {
                        window.clearInterval(interval);
                        return this;
                    };
                    return this;
                };
            return new Roulette();
        }
    });

    return JXG.Board;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */

/*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */
/*jslint nomen: true, plusplus: true, newcap:true*/

/* depends:
 jxg
 options
 renderer/abstract
 base/constants
 utils/type
 utils/env
 utils/color
 math/numerics
*/

define('renderer/svg',[
    'jxg', 'options', 'renderer/abstract', 'base/constants', 'utils/type', 'utils/color', 'utils/base64', 'math/numerics'
], function (JXG, Options, AbstractRenderer, Const, Type, Color, Base64, Numerics) {

    "use strict";

    /**
     * Uses SVG to implement the rendering methods defined in {@link JXG.AbstractRenderer}.
     * @class JXG.SVGRenderer
     * @augments JXG.AbstractRenderer
     * @param {Node} container Reference to a DOM node containing the board.
     * @param {Object} dim The dimensions of the board
     * @param {Number} dim.width
     * @param {Number} dim.height
     * @see JXG.AbstractRenderer
     */
    JXG.SVGRenderer = function (container, dim) {
        var i;

        // docstring in AbstractRenderer
        this.type = 'svg';

        this.isIE = navigator.appVersion.indexOf("MSIE") !== -1 || navigator.userAgent.match(/Trident\//);

        /**
         * SVG root node
         * @type Node
         */
        this.svgRoot = null;

        /**
         * The SVG Namespace used in JSXGraph.
         * @see http://www.w3.org/TR/SVG/
         * @type String
         * @default http://www.w3.org/2000/svg
         */
        this.svgNamespace = 'http://www.w3.org/2000/svg';

        /**
         * The xlink namespace. This is used for images.
         * @see http://www.w3.org/TR/xlink/
         * @type String
         * @default http://www.w3.org/1999/xlink
         */
        this.xlinkNamespace = 'http://www.w3.org/1999/xlink';

        // container is documented in AbstractRenderer
        this.container = container;

        // prepare the div container and the svg root node for use with JSXGraph
        this.container.style.MozUserSelect = 'none';
        this.container.style.userSelect = 'none';

        this.container.style.overflow = 'hidden';
        if (this.container.style.position === '') {
            this.container.style.position = 'relative';
        }

        this.svgRoot = this.container.ownerDocument.createElementNS(this.svgNamespace, "svg");
        this.svgRoot.style.overflow = 'hidden';
        this.svgRoot.style.display = 'block';

        this.resize(dim.width, dim.height);

        //this.svgRoot.setAttributeNS(null, 'shape-rendering', 'crispEdge'); //'optimizeQuality'); //geometricPrecision');

        this.container.appendChild(this.svgRoot);

        /**
         * The <tt>defs</tt> element is a container element to reference reusable SVG elements.
         * @type Node
         * @see http://www.w3.org/TR/SVG/struct.html#DefsElement
         */
        this.defs = this.container.ownerDocument.createElementNS(this.svgNamespace, 'defs');
        this.svgRoot.appendChild(this.defs);

        /**
         * Filters are used to apply shadows.
         * @type Node
         * @see http://www.w3.org/TR/SVG/filters.html#FilterElement
         */
        this.filter = this.container.ownerDocument.createElementNS(this.svgNamespace, 'filter');
        this.filter.setAttributeNS(null, 'id', this.container.id + '_' + 'f1');
        /*
        this.filter.setAttributeNS(null, 'x', '-100%');
        this.filter.setAttributeNS(null, 'y', '-100%');
        this.filter.setAttributeNS(null, 'width', '400%');
        this.filter.setAttributeNS(null, 'height', '400%');
        //this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse');
        */
        this.filter.setAttributeNS(null, 'width', '300%');
        this.filter.setAttributeNS(null, 'height', '300%');
        this.filter.setAttributeNS(null, 'filterUnits', 'userSpaceOnUse');

        this.feOffset = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feOffset');
        this.feOffset.setAttributeNS(null, 'result', 'offOut');
        this.feOffset.setAttributeNS(null, 'in', 'SourceAlpha');
        this.feOffset.setAttributeNS(null, 'dx', '5');
        this.feOffset.setAttributeNS(null, 'dy', '5');
        this.filter.appendChild(this.feOffset);

        this.feGaussianBlur = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feGaussianBlur');
        this.feGaussianBlur.setAttributeNS(null, 'result', 'blurOut');
        this.feGaussianBlur.setAttributeNS(null, 'in', 'offOut');
        this.feGaussianBlur.setAttributeNS(null, 'stdDeviation', '3');
        this.filter.appendChild(this.feGaussianBlur);

        this.feBlend = this.container.ownerDocument.createElementNS(this.svgNamespace, 'feBlend');
        this.feBlend.setAttributeNS(null, 'in', 'SourceGraphic');
        this.feBlend.setAttributeNS(null, 'in2', 'blurOut');
        this.feBlend.setAttributeNS(null, 'mode', 'normal');
        this.filter.appendChild(this.feBlend);

        this.defs.appendChild(this.filter);

        /**
         * JSXGraph uses a layer system to sort the elements on the board. This puts certain types of elements in front
         * of other types of elements. For the order used see {@link JXG.Options.layer}. The number of layers is documented
         * there, too. The higher the number, the "more on top" are the elements on this layer.
         * @type Array
         */
        this.layer = [];
        for (i = 0; i < Options.layer.numlayers; i++) {
            this.layer[i] = this.container.ownerDocument.createElementNS(this.svgNamespace, 'g');
            this.svgRoot.appendChild(this.layer[i]);
        }

        // Already documented in JXG.AbstractRenderer
        this.supportsForeignObject = document.implementation.hasFeature("http://w3.org/TR/SVG11/feature#Extensibility", "1.1");

        if (this.supportsForeignObject) {
            this.foreignObjLayer = this.container.ownerDocument.createElementNS(this.svgNamespace, 'foreignObject');
            this.foreignObjLayer.setAttribute("display", "none");
            this.foreignObjLayer.setAttribute("x", 0);
            this.foreignObjLayer.setAttribute("y", 0);
            this.foreignObjLayer.setAttribute("width", "100%");
            this.foreignObjLayer.setAttribute("height", "100%");
            this.foreignObjLayer.setAttribute('id', this.container.id + '_foreignObj');
            this.svgRoot.appendChild(this.foreignObjLayer);
        }

        /**
         * Defines dash patterns. Defined styles are: <ol>
         * <li value="-1"> 2px dash, 2px space</li>
         * <li> 5px dash, 5px space</li>
         * <li> 10px dash, 10px space</li>
         * <li> 20px dash, 20px space</li>
         * <li> 20px dash, 10px space, 10px dash, 10px dash</li>
         * <li> 20px dash, 5px space, 10px dash, 5px space</li></ol>
         * @type Array
         * @default ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5']
         * @see http://www.w3.org/TR/SVG/painting.html#StrokeProperties
         */
        this.dashArray = ['2, 2', '5, 5', '10, 10', '20, 20', '20, 10, 10, 10', '20, 5, 10, 5'];
    };

    JXG.SVGRenderer.prototype = new AbstractRenderer();

    JXG.extend(JXG.SVGRenderer.prototype, /** @lends JXG.SVGRenderer.prototype */ {

        /**
         * Creates an arrow DOM node. Arrows are displayed in SVG with a <em>marker</em> tag.
         * @private
         * @param {JXG.GeometryElement} el A JSXGraph element, preferably one that can have an arrow attached.
         * @param {String} [idAppendix=''] A string that is added to the node's id.
         * @returns {Node} Reference to the node added to the DOM.
         */
        _createArrowHead: function (el, idAppendix, type) {
            var node2, node3,
                id = el.id + 'Triangle',
                //type = null,
                v, h;

            if (Type.exists(idAppendix)) {
                id += idAppendix;
            }
            node2 = this.createPrim('marker', id);

            node2.setAttributeNS(null, 'stroke', Type.evaluate(el.visProp.strokecolor));
            node2.setAttributeNS(null, 'stroke-opacity', Type.evaluate(el.visProp.strokeopacity));
            node2.setAttributeNS(null, 'fill', Type.evaluate(el.visProp.strokecolor));
            node2.setAttributeNS(null, 'fill-opacity', Type.evaluate(el.visProp.strokeopacity));
            node2.setAttributeNS(null, 'stroke-width', 0);  // this is the stroke-width of the arrow head.
            // Should be zero to simplify the calculations

            node2.setAttributeNS(null, 'orient', 'auto');
            node2.setAttributeNS(null, 'markerUnits', 'strokeWidth'); // 'strokeWidth' 'userSpaceOnUse');

            /*
               Types 1, 2:
               The arrow head is an isosceles triangle with base length 10 and height 10.

               Type 3:
               A rectangle

               Types 4, 5, 6:
               Defined by Bezier curves from mp_arrowheads.html

               In any case but type 3 the arrow head is 10 units long,
               type 3 is 10 unitsb high.
               These 10 units are scaled to strokeWidth * arrowSize pixels, see
               this._setArrowWidth().

               See also abstractRenderer.updateLine() where the line path is shortened accordingly.

               Changes here are also necessary in setArrowWidth().

               So far, lines with arrow heads are shortenend to avoid overlapping of
               arrow head and line. This is not the case for curves, yet.
               Therefore, the offset refX has to be adapted to the path type.
            */
            node3 = this.container.ownerDocument.createElementNS(this.svgNamespace, 'path');
            h = 5;
            if (idAppendix === 'End') {
                // First arrow
                //type = a.typeFirst;
                // if (JXG.exists(ev_fa.type)) {
                //     type = Type.evaluate(ev_fa.type);
                // }

                v = 0;
                if (type === 2) {
                    node3.setAttributeNS(null, 'd', 'M 10,0 L 0,5 L 10,10 L 5,5 z');
                } else if (type === 3) {
                    node3.setAttributeNS(null, 'd', 'M 0,0 L 3.33,0 L 3.33,10 L 0,10 z');
                } else if (type === 4) {
                    // insetRatio:0.8 tipAngle:45 wingCurve:15 tailCurve:0
                    h = 3.31;
                    node3.setAttributeNS(null, 'd', 'M 0.00,3.31 C 3.53,3.84 7.13,4.50 10.00,6.63 C 9.33,5.52 8.67,4.42 8.00,3.31 C 8.67,2.21 9.33,1.10 10.00,0.00 C 7.13,2.13 3.53,2.79 0.00,3.31');
                } else if (type === 5) {
                    // insetRatio:0.9 tipAngle:40 wingCurve:5 tailCurve:15
                    h = 3.28;
                    node3.setAttributeNS(null, 'd', 'M 0.00,3.28 C 3.39,4.19 6.81,5.07 10.00,6.55 C 9.38,5.56 9.00,4.44 9.00,3.28 C 9.00,2.11 9.38,0.99 10.00,0.00 C 6.81,1.49 3.39,2.37 0.00,3.28');
                } else if (type === 6) {
                    // insetRatio:0.9 tipAngle:35 wingCurve:5 tailCurve:0
                    h = 2.84;
                    node3.setAttributeNS(null, 'd', 'M 0.00,2.84 C 3.39,3.59 6.79,4.35 10.00,5.68 C 9.67,4.73 9.33,3.78 9.00,2.84 C 9.33,1.89 9.67,0.95 10.00,0.00 C 6.79,1.33 3.39,2.09 0.00,2.84');
                } else if (type === 7) {
                    // insetRatio:0.9 tipAngle:60 wingCurve:30 tailCurve:0
                    h = 5.20;
                    node3.setAttributeNS(null, 'd', 'M 0.00,5.20 C 4.04,5.20 7.99,6.92 10.00,10.39 M 10.00,0.00 C 7.99,3.47 4.04,5.20 0.00,5.20');
                } else {
                    // type == 1 or > 6
                    node3.setAttributeNS(null, 'd', 'M 10,0 L 0,5 L 10,10 z');
                }
                if (/*!Type.exists(el.rendNode.getTotalLength) && */el.elementClass === Const.OBJECT_CLASS_LINE) {
                    if (type === 2) {
                        v = 4.9;
                    } else if (type === 3) {
                        v = 3.3;
                    } else if (type === 4 || type === 5 || type === 6) {
                        v = 6.66;
                    } else if (type === 7) {
                        v = 0.0;
                    } else {
                        v = 10.0;
                    }
                }
            } else {
                // Last arrow
                // if (JXG.exists(ev_la.type)) {
                //     type = Type.evaluate(ev_la.type);
                // }
                //type = a.typeLast;

                v = 10.0;
                if (type === 2) {
                    node3.setAttributeNS(null, 'd', 'M 0,0 L 10,5 L 0,10 L 5,5 z');
                } else if (type === 3) {
                    v = 3.3;
                    node3.setAttributeNS(null, 'd', 'M 0,0 L 3.33,0 L 3.33,10 L 0,10 z');
                } else if (type === 4) {
                    // insetRatio:0.8 tipAngle:45 wingCurve:15 tailCurve:0
                    h = 3.31;
                    node3.setAttributeNS(null, 'd', 'M 10.00,3.31 C 6.47,3.84 2.87,4.50 0.00,6.63 C 0.67,5.52 1.33,4.42 2.00,3.31 C 1.33,2.21 0.67,1.10 0.00,0.00 C 2.87,2.13 6.47,2.79 10.00,3.31');
                } else if (type === 5) {
                    // insetRatio:0.9 tipAngle:40 wingCurve:5 tailCurve:15
                    h = 3.28;
                    node3.setAttributeNS(null, 'd', 'M 10.00,3.28 C 6.61,4.19 3.19,5.07 0.00,6.55 C 0.62,5.56 1.00,4.44 1.00,3.28 C 1.00,2.11 0.62,0.99 0.00,0.00 C 3.19,1.49 6.61,2.37 10.00,3.28');
                } else if (type === 6) {
                    // insetRatio:0.9 tipAngle:35 wingCurve:5 tailCurve:0
                    h = 2.84;
                    node3.setAttributeNS(null, 'd', 'M 10.00,2.84 C 6.61,3.59 3.21,4.35 0.00,5.68 C 0.33,4.73 0.67,3.78 1.00,2.84 C 0.67,1.89 0.33,0.95 0.00,0.00 C 3.21,1.33 6.61,2.09 10.00,2.84');
                } else if (type === 7) {
                    // insetRatio:0.9 tipAngle:60 wingCurve:30 tailCurve:0
                    h = 5.20;
                    node3.setAttributeNS(null, 'd', 'M 10.00,5.20 C 5.96,5.20 2.01,6.92 0.00,10.39 M 0.00,0.00 C 2.01,3.47 5.96,5.20 10.00,5.20');
                } else {
                    // type == 1 or > 6
                    node3.setAttributeNS(null, 'd', 'M 0,0 L 10,5 L 0,10 z');
                }
                if (/*!Type.exists(el.rendNode.getTotalLength) &&*/ el.elementClass === Const.OBJECT_CLASS_LINE) {
                    if (type === 2) {
                        v = 5.1;
                    } else if (type === 3) {
                        v = 0.02;
                    } else if (type === 4 || type === 5 || type === 6) {
                        v = 3.33;
                    } else if (type === 7) {
                        v = 10.0;
                    } else {
                        v = 0.05;
                    }
                }
            }
            if (type === 7) {
                node2.setAttributeNS(null, 'fill', 'none');
                node2.setAttributeNS(null, 'stroke-width', 1);  // this is the stroke-width of the arrow head.
            }
            node2.setAttributeNS(null, 'refY', h);
            node2.setAttributeNS(null, 'refX', v);

            node2.appendChild(node3);
            return node2;
        },

        /**
         * Updates color of an arrow DOM node.
         * @param {Node} node The arrow node.
         * @param {String} color Color value in a HTML compatible format, e.g. <tt>#00ff00</tt> or <tt>green</tt> for green.
         * @param {Number} opacity
         * @param {JXG.GeometryElement} el The element the arrows are to be attached to
         */
        _setArrowColor: function (node, color, opacity, el, type) {
            if (node) {
                if (Type.isString(color)) {
                    if (type !== 7) {
                        this._setAttribute(function () {
                            node.setAttributeNS(null, 'stroke', color);
                            node.setAttributeNS(null, 'fill', color);
                            node.setAttributeNS(null, 'stroke-opacity', opacity);
                            node.setAttributeNS(null, 'fill-opacity', opacity);
                        }, el.visPropOld.fillcolor);
                    } else {
                        this._setAttribute(function () {
                            node.setAttributeNS(null, 'fill', 'none');
                            node.setAttributeNS(null, 'stroke', color);
                            node.setAttributeNS(null, 'stroke-opacity', opacity);
                        }, el.visPropOld.fillcolor);
                    }
                }

                if (this.isIE) {
                    el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode);
                }
            }

        },

        // Already documented in JXG.AbstractRenderer
        _setArrowWidth: function (node, width, parentNode, size) {
            var s, d;

            if (node) {
                // if (width === 0) {
                //     // display:none does not work well in webkit
                //     node.setAttributeNS(null, 'display', 'none');
                // } else {
                    s = width;
                    d = s * size;
                    node.setAttributeNS(null, 'viewBox', (0) + ' ' + (0) + ' ' + (s * 10) + ' ' + (s * 10));
                    node.setAttributeNS(null, 'markerHeight', d);
                    node.setAttributeNS(null, 'markerWidth', d);
                    node.setAttributeNS(null, 'display', 'inherit');
                // }

                if (this.isIE) {
                    parentNode.parentNode.insertBefore(parentNode, parentNode);
                }
            }
        },

        /* ******************************** *
         *  This renderer does not need to
         *  override draw/update* methods
         *  since it provides draw/update*Prim
         *  methods except for some cases like
         *  internal texts or images.
         * ******************************** */

        /* **************************
         *    Lines
         * **************************/

        // documented in AbstractRenderer
        updateTicks: function (ticks) {
            var i, j, c, node, x, y,
                tickStr = '',
                len = ticks.ticks.length,
                len2, str,
                isReal = true;

            for (i = 0; i < len; i++) {
                c = ticks.ticks[i];
                x = c[0];
                y = c[1];

                len2 = x.length;
                str = ' M ' + x[0] + ' ' + y[0];
                if (!Type.isNumber(x[0])) {
                    isReal = false;
                }
                for (j = 1; isReal && j < len2; ++j) {
                    if (Type.isNumber(x[j])) {
                        str += ' L ' + x[j] + ' ' + y[j];
                    } else {
                        isReal = false;
                    }

                }
                if (isReal) {
                    tickStr += str;
                }
            }

            node = ticks.rendNode;

            if (!Type.exists(node)) {
                node = this.createPrim('path', ticks.id);
                this.appendChildPrim(node, Type.evaluate(ticks.visProp.layer));
                ticks.rendNode = node;
            }

            node.setAttributeNS(null, 'stroke', Type.evaluate(ticks.visProp.strokecolor));
            node.setAttributeNS(null, 'fill', 'none');
            // node.setAttributeNS(null, 'fill', Type.evaluate(ticks.visProp.fillcolor));
            // node.setAttributeNS(null, 'fill-opacity', Type.evaluate(ticks.visProp.fillopacity));
            node.setAttributeNS(null, 'stroke-opacity', Type.evaluate(ticks.visProp.strokeopacity));
            node.setAttributeNS(null, 'stroke-width', Type.evaluate(ticks.visProp.strokewidth));
            this.updatePathPrim(node, tickStr, ticks.board);
        },

        /* **************************
         *    Text related stuff
         * **************************/

        // Already documented in JXG.AbstractRenderer
        displayCopyright: function (str, fontsize) {
            var node = this.createPrim('text', 'licenseText'),
                t;
            node.setAttributeNS(null, 'x', '20px');
            node.setAttributeNS(null, 'y', (2 + fontsize) + 'px');
            node.setAttributeNS(null, "style", "font-family:Arial,Helvetica,sans-serif; font-size:" + fontsize + "px; fill:#356AA0;  opacity:0.3;");
            t = this.container.ownerDocument.createTextNode(str);
            node.appendChild(t);
            this.appendChildPrim(node, 0);
        },

        // Already documented in JXG.AbstractRenderer
        drawInternalText: function (el) {
            var node = this.createPrim('text', el.id);

            //node.setAttributeNS(null, "style", "alignment-baseline:middle"); // Not yet supported by Firefox
            // Preserve spaces
            //node.setAttributeNS("http://www.w3.org/XML/1998/namespace", "space", "preserve");
            node.style.whiteSpace = 'nowrap';

            el.rendNodeText = this.container.ownerDocument.createTextNode('');
            node.appendChild(el.rendNodeText);
            this.appendChildPrim(node, Type.evaluate(el.visProp.layer));

            return node;
        },

        // Already documented in JXG.AbstractRenderer
        updateInternalText: function (el) {
            var content = el.plaintext, v,
                ev_ax = el.getAnchorX(),
                ev_ay = el.getAnchorY();

            if (el.rendNode.getAttributeNS(null, "class") !== el.visProp.cssclass) {
                el.rendNode.setAttributeNS(null, "class", Type.evaluate(el.visProp.cssclass));
                el.needsSizeUpdate = true;
            }

            if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
                // Horizontal
                v = el.coords.scrCoords[1];
                if (el.visPropOld.left !== (ev_ax + v)) {
                    el.rendNode.setAttributeNS(null, 'x', v + 'px');

                    if (ev_ax === 'left') {
                        el.rendNode.setAttributeNS(null, 'text-anchor', 'start');
                    } else if (ev_ax === 'right') {
                        el.rendNode.setAttributeNS(null, 'text-anchor', 'end');
                    } else if (ev_ax === 'middle') {
                        el.rendNode.setAttributeNS(null, 'text-anchor', 'middle');
                    }
                    el.visPropOld.left = ev_ax + v;
                }

                // Vertical
                v = el.coords.scrCoords[2];
                if (el.visPropOld.top !== (ev_ay + v)) {
                    el.rendNode.setAttributeNS(null, 'y', (v + this.vOffsetText * 0.5) + 'px');

                    if (ev_ay === 'bottom') {
                        el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-after-edge');
                    } else if (ev_ay === 'top') {
                        el.rendNode.setAttributeNS(null, 'dy', '1.6ex');
                        //el.rendNode.setAttributeNS(null, 'dominant-baseline', 'text-before-edge'); // Not supported by IE, edge
                    } else if (ev_ay === 'middle') {
                        //el.rendNode.setAttributeNS(null, 'dominant-baseline', 'middle');
                        el.rendNode.setAttributeNS(null, 'dy', '0.6ex');
                    }
                    el.visPropOld.top = ev_ay + v;
                }
            }
            if (el.htmlStr !== content) {
                el.rendNodeText.data = content;
                el.htmlStr = content;
            }
            this.transformImage(el, el.transformations);
        },

        /**
         * Set color and opacity of internal texts.
         * SVG needs its own version.
         * @private
         * @see JXG.AbstractRenderer#updateTextStyle
         * @see JXG.AbstractRenderer#updateInternalTextStyle
         */
        updateInternalTextStyle: function (el, strokeColor, strokeOpacity, duration) {
            this.setObjectFillColor(el, strokeColor, strokeOpacity);
        },

        /* **************************
         *    Image related stuff
         * **************************/

        // Already documented in JXG.AbstractRenderer
        drawImage: function (el) {
            var node = this.createPrim('image', el.id);

            node.setAttributeNS(null, 'preserveAspectRatio', 'none');
            this.appendChildPrim(node, Type.evaluate(el.visProp.layer));
            el.rendNode = node;

            this.updateImage(el);
        },

        // Already documented in JXG.AbstractRenderer
        transformImage: function (el, t) {
            var s, m,
                node = el.rendNode,
                str = "",
                len = t.length;

            if (len > 0) {
                m = this.joinTransforms(el, t);
                s = [m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]].join(',');
                str += ' matrix(' + s + ') ';
                node.setAttributeNS(null, 'transform', str);
            }
        },

        // Already documented in JXG.AbstractRenderer
        updateImageURL: function (el) {
            var url = Type.evaluate(el.url);

            if (el._src !== url) {
                el.imgIsLoaded = false;
                el.rendNode.setAttributeNS(this.xlinkNamespace, 'xlink:href', url);
                el._src = url;

                return true;
            }

            return false;
        },

        // Already documented in JXG.AbstractRenderer
        updateImageStyle: function (el, doHighlight) {
            var css = Type.evaluate(doHighlight ? el.visProp.highlightcssclass : el.visProp.cssclass);

            el.rendNode.setAttributeNS(null, 'class', css);
        },

        // Already documented in JXG.AbstractRenderer
        drawForeignObject: function (el) {
            el.rendNode = this.appendChildPrim(this.createPrim('foreignObject', el.id),
                                    Type.evaluate(el.visProp.layer));

            this.appendNodesToElement(el, 'foreignObject');
            this.updateForeignObject(el);
        },

        // Already documented in JXG.AbstractRenderer
        updateForeignObject: function(el) {
            if (el._useUserSize) {
                el.rendNode.style.overflow = 'hidden';
            } else {
                el.rendNode.style.overflow = 'visible';
            }

            this.updateRectPrim(el.rendNode, el.coords.scrCoords[1],
                el.coords.scrCoords[2] - el.size[1], el.size[0], el.size[1]);

            el.rendNode.innerHTML = el.content;
            this._updateVisual(el, {stroke: true, dash: true}, true);
        },

        /* **************************
         * Render primitive objects
         * **************************/

        // Already documented in JXG.AbstractRenderer
        appendChildPrim: function (node, level) {
            if (!Type.exists(level)) { // trace nodes have level not set
                level = 0;
            } else if (level >= Options.layer.numlayers) {
                level = Options.layer.numlayers - 1;
            }

            this.layer[level].appendChild(node);

            return node;
        },

        // Already documented in JXG.AbstractRenderer
        createPrim: function (type, id) {
            var node = this.container.ownerDocument.createElementNS(this.svgNamespace, type);
            node.setAttributeNS(null, 'id', this.container.id + '_' + id);
            node.style.position = 'absolute';
            if (type === 'path') {
                node.setAttributeNS(null, 'stroke-linecap', 'round');
                node.setAttributeNS(null, 'stroke-linejoin', 'round');
                node.setAttributeNS(null, 'fill-rule', 'evenodd');
            }
            return node;
        },

        // Already documented in JXG.AbstractRenderer
        remove: function (shape) {
            if (Type.exists(shape) && Type.exists(shape.parentNode)) {
                shape.parentNode.removeChild(shape);
            }
        },

        // Already documented in JXG.AbstractRenderer
        setLayer: function (el, level) {
            if (!Type.exists(level)) {
                level = 0;
            } else if (level >= Options.layer.numlayers) {
                level = Options.layer.numlayers - 1;
            }

            this.layer[level].appendChild(el.rendNode);
        },

        // Already documented in JXG.AbstractRenderer
        makeArrows: function (el, a) {
            var node2,
                ev_fa = a.evFirst,
                ev_la = a.evLast;

            // Test if the arrow heads already exist
            if (el.visPropOld.firstarrow === ev_fa &&
                el.visPropOld.lastarrow === ev_la) {
                if (this.isIE && el.visPropCalc.visible &&
                    (ev_fa || ev_la)) {
                    el.rendNode.parentNode.insertBefore(el.rendNode, el.rendNode);
                }
                return;
            }

            if (ev_fa) {
                node2 = el.rendNodeTriangleStart;
                if (!Type.exists(node2)) {
                    node2 = this._createArrowHead(el, 'End', a.typeFirst);
                    this.defs.appendChild(node2);
                    el.rendNodeTriangleStart = node2;
                    el.rendNode.setAttributeNS(null, 'marker-start', 'url(#' + this.container.id + '_' + el.id + 'TriangleEnd)');
                } else {
                    this.defs.appendChild(node2);
                }
            } else {
                node2 = el.rendNodeTriangleStart;
                if (Type.exists(node2)) {
                    this.remove(node2);
                }
            }
            if (ev_la) {
                node2 = el.rendNodeTriangleEnd;
                if (!Type.exists(node2)) {
                    node2 = this._createArrowHead(el, 'Start', a.typeLast);
                    this.defs.appendChild(node2);
                    el.rendNodeTriangleEnd = node2;
                    el.rendNode.setAttributeNS(null, 'marker-end', 'url(#' + this.container.id + '_' + el.id + 'TriangleStart)');
                } else {
                    this.defs.appendChild(node2);
                }
            } else {
                node2 = el.rendNodeTriangleEnd;
                if (Type.exists(node2)) {
                    this.remove(node2);
                }
            }
            el.visPropOld.firstarrow = ev_fa;
            el.visPropOld.lastarrow = ev_la;
        },

        // Already documented in JXG.AbstractRenderer
        updateEllipsePrim: function (node, x, y, rx, ry) {
            var huge = 1000000;

            huge = 200000; // IE
            // webkit does not like huge values if the object is dashed
            // iE doesn't like huge values above 216000
            x = Math.abs(x) < huge ? x : huge * x / Math.abs(x);
            y = Math.abs(y) < huge ? y : huge * y / Math.abs(y);
            rx = Math.abs(rx) < huge ? rx : huge * rx / Math.abs(rx);
            ry = Math.abs(ry) < huge ? ry : huge * ry / Math.abs(ry);

            node.setAttributeNS(null, 'cx', x);
            node.setAttributeNS(null, 'cy', y);
            node.setAttributeNS(null, 'rx', Math.abs(rx));
            node.setAttributeNS(null, 'ry', Math.abs(ry));
        },

        // Already documented in JXG.AbstractRenderer
        updateLinePrim: function (node, p1x, p1y, p2x, p2y) {
            var huge = 1000000;

            huge = 200000; //IE
            if (!isNaN(p1x + p1y + p2x + p2y)) {
                // webkit does not like huge values if the object is dashed
                // IE doesn't like huge values above 216000
                p1x = Math.abs(p1x) < huge ? p1x : huge * p1x / Math.abs(p1x);
                p1y = Math.abs(p1y) < huge ? p1y : huge * p1y / Math.abs(p1y);
                p2x = Math.abs(p2x) < huge ? p2x : huge * p2x / Math.abs(p2x);
                p2y = Math.abs(p2y) < huge ? p2y : huge * p2y / Math.abs(p2y);

                node.setAttributeNS(null, 'x1', p1x);
                node.setAttributeNS(null, 'y1', p1y);
                node.setAttributeNS(null, 'x2', p2x);
                node.setAttributeNS(null, 'y2', p2y);
            }
        },

        // Already documented in JXG.AbstractRenderer
        updatePathPrim: function (node, pointString) {
            if (pointString === '') {
                pointString = 'M 0 0';
            }
            node.setAttributeNS(null, 'd', pointString);
        },

        // Already documented in JXG.AbstractRenderer
        updatePathStringPoint: function (el, size, type) {
            var s = '',
                scr = el.coords.scrCoords,
                sqrt32 = size * Math.sqrt(3) * 0.5,
                s05 = size * 0.5;

            if (type === 'x') {
                s = ' M ' + (scr[1] - size) + ' ' + (scr[2] - size) +
                    ' L ' + (scr[1] + size) + ' ' + (scr[2] + size) +
                    ' M ' + (scr[1] + size) + ' ' + (scr[2] - size) +
                    ' L ' + (scr[1] - size) + ' ' + (scr[2] + size);
            } else if (type === '+') {
                s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
                    ' L ' + (scr[1] + size) + ' ' + (scr[2]) +
                    ' M ' + (scr[1]) + ' ' + (scr[2] - size) +
                    ' L ' + (scr[1]) + ' ' + (scr[2] + size);
            } else if (type === '<>') {
                s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
                    ' L ' + (scr[1]) + ' ' + (scr[2] + size) +
                    ' L ' + (scr[1] + size) + ' ' + (scr[2]) +
                    ' L ' + (scr[1]) + ' ' + (scr[2] - size) + ' Z ';
            } else if (type === '^') {
                s = ' M ' + (scr[1]) + ' ' + (scr[2] - size) +
                    ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] + s05) +
                    ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] + s05) +
                    ' Z ';  // close path
            } else if (type === 'v') {
                s = ' M ' + (scr[1]) + ' ' + (scr[2] + size) +
                    ' L ' + (scr[1] - sqrt32) + ' ' + (scr[2] - s05) +
                    ' L ' + (scr[1] + sqrt32) + ' ' + (scr[2] - s05) +
                    ' Z ';
            } else if (type === '>') {
                s = ' M ' + (scr[1] + size) + ' ' + (scr[2]) +
                    ' L ' + (scr[1] - s05) + ' ' + (scr[2] - sqrt32) +
                    ' L ' + (scr[1] - s05) + ' ' + (scr[2] + sqrt32) +
                    ' Z ';
            } else if (type === '<') {
                s = ' M ' + (scr[1] - size) + ' ' + (scr[2]) +
                    ' L ' + (scr[1] + s05) + ' ' + (scr[2] - sqrt32) +
                    ' L ' + (scr[1] + s05) + ' ' + (scr[2] + sqrt32) +
                    ' Z ';
            }
            return s;
        },

        // Already documented in JXG.AbstractRenderer
        updatePathStringPrim: function (el) {
            var i, scr, len,
                symbm = ' M ',
                symbl = ' L ',
                symbc = ' C ',
                nextSymb = symbm,
                maxSize = 5000.0,
                pStr = '';

            if (el.numberPoints <= 0) {
                return '';
            }

            len = Math.min(el.points.length, el.numberPoints);

            if (el.bezierDegree === 1) {
                for (i = 0; i < len; i++) {
                    scr = el.points[i].scrCoords;
                    if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
                        nextSymb = symbm;
                    } else {
                        // Chrome has problems with values being too far away.
                        scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize);
                        scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize);

                        // Attention: first coordinate may be inaccurate if far way
                        //pStr += [nextSymb, scr[1], ' ', scr[2]].join('');
                        pStr += nextSymb + scr[1] + ' ' + scr[2];   // Seems to be faster now (webkit and firefox)
                        nextSymb = symbl;
                    }
                }
            } else if (el.bezierDegree === 3) {
                i = 0;
                while (i < len) {
                    scr = el.points[i].scrCoords;
                    if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
                        nextSymb = symbm;
                    } else {
                        pStr += nextSymb + scr[1] + ' ' + scr[2];
                        if (nextSymb === symbc) {
                            i += 1;
                            scr = el.points[i].scrCoords;
                            pStr += ' ' + scr[1] + ' ' + scr[2];
                            i += 1;
                            scr = el.points[i].scrCoords;
                            pStr += ' ' + scr[1] + ' ' + scr[2];
                        }
                        nextSymb = symbc;
                    }
                    i += 1;
                }
            }
            return pStr;
        },

        // Already documented in JXG.AbstractRenderer
        updatePathStringBezierPrim: function (el) {
            var i, j, k, scr, lx, ly, len,
                symbm = ' M ',
                symbl = ' C ',
                nextSymb = symbm,
                maxSize = 5000.0,
                pStr = '',
                f = Type.evaluate(el.visProp.strokewidth),
                isNoPlot = (Type.evaluate(el.visProp.curvetype) !== 'plot');

            if (el.numberPoints <= 0) {
                return '';
            }

            if (isNoPlot && el.board.options.curve.RDPsmoothing) {
                el.points = Numerics.RamerDouglasPeucker(el.points, 0.5);
            }

            len = Math.min(el.points.length, el.numberPoints);
            for (j = 1; j < 3; j++) {
                nextSymb = symbm;
                for (i = 0; i < len; i++) {
                    scr = el.points[i].scrCoords;

                    if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
                        nextSymb = symbm;
                    } else {
                        // Chrome has problems with values being too far away.
                        scr[1] = Math.max(Math.min(scr[1], maxSize), -maxSize);
                        scr[2] = Math.max(Math.min(scr[2], maxSize), -maxSize);

                        // Attention: first coordinate may be inaccurate if far way
                        if (nextSymb === symbm) {
                            //pStr += [nextSymb, scr[1], ' ', scr[2]].join('');
                            pStr += nextSymb + scr[1] + ' ' + scr[2];   // Seems to be faster now (webkit and firefox)
                        } else {
                            k = 2 * j;
                            pStr += [nextSymb,
                                (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j)), ' ',
                                (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j)), ' ',
                                (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j)), ' ',
                                (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j)), ' ',
                                scr[1], ' ', scr[2]].join('');
                        }

                        nextSymb = symbl;
                        lx = scr[1];
                        ly = scr[2];
                    }
                }
            }
            return pStr;
        },

        // Already documented in JXG.AbstractRenderer
        updatePolygonPrim: function (node, el) {
            var i,
                pStr = '',
                scrCoords,
                len = el.vertices.length;

            node.setAttributeNS(null, 'stroke', 'none');
            if (el.elType === 'polygonalchain') {
                len++;
            }

            for (i = 0; i < len - 1; i++) {
                if (el.vertices[i].isReal) {
                    scrCoords = el.vertices[i].coords.scrCoords;
                    pStr = pStr + scrCoords[1] + "," + scrCoords[2];
                } else {
                    node.setAttributeNS(null, 'points', '');
                    return;
                }

                if (i < len - 2) {
                    pStr += " ";
                }
            }
            if (pStr.indexOf('NaN') === -1) {
                node.setAttributeNS(null, 'points', pStr);
            }
        },

        // Already documented in JXG.AbstractRenderer
        updateRectPrim: function (node, x, y, w, h) {
            node.setAttributeNS(null, 'x', x);
            node.setAttributeNS(null, 'y', y);
            node.setAttributeNS(null, 'width', w);
            node.setAttributeNS(null, 'height', h);
        },

        /* **************************
         *  Set Attributes
         * **************************/

        // documented in JXG.AbstractRenderer
        setPropertyPrim: function (node, key, val) {
            if (key === 'stroked') {
                return;
            }
            node.setAttributeNS(null, key, val);
        },

        display: function (el, val) {
            var node;

            if (el && el.rendNode) {
                el.visPropOld.visible = val;
                node = el.rendNode;
                if (val) {
                    node.setAttributeNS(null, 'display', 'inline');
                    node.style.visibility = "inherit";
                } else {
                    node.setAttributeNS(null, 'display', 'none');
                    node.style.visibility = "hidden";
                }
            }
        },

        // documented in JXG.AbstractRenderer
        show: function (el) {
            JXG.deprecated('Board.renderer.show()', 'Board.renderer.display()');
            this.display(el, true);
            // var node;
            //
            // if (el && el.rendNode) {
            //     node = el.rendNode;
            //     node.setAttributeNS(null, 'display', 'inline');
            //     node.style.visibility = "inherit";
            // }
        },

        // documented in JXG.AbstractRenderer
        hide: function (el) {
            JXG.deprecated('Board.renderer.hide()', 'Board.renderer.display()');
            this.display(el, false);
            // var node;
            //
            // if (el && el.rendNode) {
            //     node = el.rendNode;
            //     node.setAttributeNS(null, 'display', 'none');
            //     node.style.visibility = "hidden";
            // }
        },

        // documented in JXG.AbstractRenderer
        setBuffering: function (el, type) {
            el.rendNode.setAttribute('buffered-rendering', type);
        },

        // documented in JXG.AbstractRenderer
        setDashStyle: function (el) {
            var dashStyle = Type.evaluate(el.visProp.dash),
                node = el.rendNode;

            if (dashStyle > 0) {
                node.setAttributeNS(null, 'stroke-dasharray', this.dashArray[dashStyle - 1]);
            } else {
                if (node.hasAttributeNS(null, 'stroke-dasharray')) {
                    node.removeAttributeNS(null, 'stroke-dasharray');
                }
            }
        },

        // documented in JXG.AbstractRenderer
        setGradient: function (el) {
            var fillNode = el.rendNode,
                node, node2, node3,
                ev_g = Type.evaluate(el.visProp.gradient);

            if (ev_g === 'linear' || ev_g === 'radial') {
                node = this.createPrim(ev_g + 'Gradient', el.id + '_gradient');
                node2 = this.createPrim('stop', el.id + '_gradient1');
                node3 = this.createPrim('stop', el.id + '_gradient2');
                node.appendChild(node2);
                node.appendChild(node3);
                this.defs.appendChild(node);
                fillNode.setAttributeNS(null, 'style', 'fill:url(#' + this.container.id + '_' + el.id + '_gradient)');
                el.gradNode1 = node2;
                el.gradNode2 = node3;
                el.gradNode = node;
            } else {
                fillNode.removeAttributeNS(null, 'style');
            }
        },

        /**
         * Set the gradient angle for linear color gradients.
         *
         * @private
         * @param {SVGnode} node SVG gradient node of an arbitrary JSXGraph element.
         * @param {Number} radians angle value in radians. 0 is horizontal from left to right, Pi/4 is vertical from top to bottom.
         */
        updateGradientAngle: function(node, radians) {
            // Angles:
            // 0: ->
            // 90: down
            // 180: <-
            // 90: up
            var f = 1.0,
                co = Math.cos(radians),
                si = Math.sin(radians);

            if (Math.abs(co) > Math.abs(si)) {
                f /= Math.abs(co);
            } else {
                f /= Math.abs(si);
            }

            if (co >= 0) {
                node.setAttributeNS(null, 'x1', 0);
                node.setAttributeNS(null, 'x2', co * f);
            } else {
                node.setAttributeNS(null, 'x1', -co * f);
                node.setAttributeNS(null, 'x2', 0);
            }
            if (si >= 0) {
                node.setAttributeNS(null, 'y1', 0);
                node.setAttributeNS(null, 'y2', si * f);
            } else {
                node.setAttributeNS(null, 'y1', -si * f);
                node.setAttributeNS(null, 'y2', 0);
            }
        },

        /**
         * Set circles for radial color gradients.
         *
         * @private
         * @param {SVGnode} node SVG gradient node
         * @param {Number} cx SVG value cx (value between 0 and 1)
         * @param {Number} cy  SVG value cy (value between 0 and 1)
         * @param {Number} r  SVG value r (value between 0 and 1)
         * @param {Number} fx  SVG value fx (value between 0 and 1)
         * @param {Number} fy  SVG value fy (value between 0 and 1)
         * @param {Number} fr  SVG value fr (value between 0 and 1)
         */
        updateGradientCircle: function(node, cx, cy, r, fx, fy, fr) {
            node.setAttributeNS(null, 'cx', cx * 100 + '%');   // Center first color
            node.setAttributeNS(null, 'cy', cy * 100 + '%');
            node.setAttributeNS(null, 'r', r * 100 + '%');
            node.setAttributeNS(null, 'fx', fx * 100 + '%');   // Center second color / focal point
            node.setAttributeNS(null, 'fy', fy * 100 + '%');
            node.setAttributeNS(null, 'fr', fr * 100 + '%');
        },

        // documented in JXG.AbstractRenderer
        updateGradient: function (el) {
            var col, op,
                node2 = el.gradNode1,
                node3 = el.gradNode2,
                ev_g = Type.evaluate(el.visProp.gradient);

            if (!Type.exists(node2) || !Type.exists(node3)) {
                return;
            }

            op = Type.evaluate(el.visProp.fillopacity);
            op = (op > 0) ? op : 0;
            col = Type.evaluate(el.visProp.fillcolor);

            node2.setAttributeNS(null, 'style', 'stop-color:' + col + ';stop-opacity:' + op);
            node3.setAttributeNS(null, 'style',
                    'stop-color:'    + Type.evaluate(el.visProp.gradientsecondcolor) +
                    ';stop-opacity:' + Type.evaluate(el.visProp.gradientsecondopacity)
                );
            node2.setAttributeNS(null, 'offset', Type.evaluate(el.visProp.gradientstartoffset) * 100 + '%');
            node3.setAttributeNS(null, 'offset', Type.evaluate(el.visProp.gradientendoffset) * 100 + '%');
            if (ev_g === 'linear') {
                this.updateGradientAngle(el.gradNode, Type.evaluate(el.visProp.gradientangle));
            } else if (ev_g === 'radial') {
                this.updateGradientCircle(el.gradNode,
                    Type.evaluate(el.visProp.gradientcx),
                    Type.evaluate(el.visProp.gradientcy),
                    Type.evaluate(el.visProp.gradientr),
                    Type.evaluate(el.visProp.gradientfx),
                    Type.evaluate(el.visProp.gradientfy),
                    Type.evaluate(el.visProp.gradientfr)
                );
            }
        },

        // documented in JXG.AbstractRenderer
        setObjectTransition: function (el, duration) {
            var node, transitionStr,
                i, len,
                nodes = ['rendNode',
                    'rendNodeTriangleStart',
                    'rendNodeTriangleEnd'];

            if (duration === undefined) {
                duration = Type.evaluate(el.visProp.transitionduration);
            }

            if (duration === el.visPropOld.transitionduration) {
                return;
            }

            if (el.elementClass === Const.OBJECT_CLASS_TEXT &&
                Type.evaluate(el.visProp.display) === 'html') {
                transitionStr = ' color ' + duration + 'ms,' +
                    ' opacity ' + duration + 'ms';
            } else {
                transitionStr = ' fill ' + duration + 'ms,' +
                    ' fill-opacity ' + duration + 'ms,' +
                    ' stroke ' + duration + 'ms,' +
                    ' stroke-opacity ' + duration + 'ms';
            }

            len = nodes.length;
            for (i = 0; i < len; ++i) {
                if (el[nodes[i]]) {
                    node = el[nodes[i]];
                    node.style.transition = transitionStr;
                }
            }

            el.visPropOld.transitionduration = duration;
        },

        /**
         * Call user-defined function to set visual attributes.
         * If "testAttribute" is the empty string, the function
         * is called immediately, otherwise it is called in a timeOut.
         *
         * This is necessary to realize smooth transitions but avoid transitions
         * when first creating the objects.
         *
         * Usually, the string in testAttribute is the visPropOld attribute
         * of the values which are set.
         *
         * @param {Function} setFunc       Some function which usually sets some attributes
         * @param {String} testAttribute If this string is the empty string  the function is called immediately,
         *                               otherwise it is called in a setImeout.
         * @see JXG.SVGRenderer#setObjectFillColor
         * @see JXG.SVGRenderer#setObjectStrokeColor
         * @see JXG.SVGRenderer#_setArrowColor
         * @private
         */
        _setAttribute: function (setFunc, testAttribute) {
            if (testAttribute === '') {
                setFunc();
            } else {
                window.setTimeout(setFunc, 1);
            }
        },

        // documented in JXG.AbstractRenderer
        setObjectFillColor: function (el, color, opacity, rendNode) {
            var node, c, rgbo, oo,
                rgba = Type.evaluate(color),
                o = Type.evaluate(opacity),
                grad = Type.evaluate(el.visProp.gradient);

            o = (o > 0) ? o : 0;

            // TODO  save gradient and gradientangle
            if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o && grad === null) {
                return;
            }

            if (Type.exists(rgba) && rgba !== false) {
                if (rgba.length !== 9) {          // RGB, not RGBA
                    c = rgba;
                    oo = o;
                } else {                       // True RGBA, not RGB
                    rgbo = Color.rgba2rgbo(rgba);
                    c = rgbo[0];
                    oo = o * rgbo[1];
                }

                if (rendNode === undefined) {
                    node = el.rendNode;
                } else {
                    node = rendNode;
                }

                if (c !== 'none') {
                    this._setAttribute(function () {
                        node.setAttributeNS(null, 'fill', c);
                    }, el.visPropOld.fillcolor);
                }

                if (el.type === JXG.OBJECT_TYPE_IMAGE) {
                    this._setAttribute(function () {
                        node.setAttributeNS(null, 'opacity', oo);
                    }, el.visPropOld.fillopacity);
                    //node.style['opacity'] = oo;  // This would overwrite values set by CSS class.
                } else {
                    if (c === 'none') {  // This is done only for non-images
                        // because images have no fill color.
                        oo = 0;
                        // This is necessary if there is a foreignObject below.
                        node.setAttributeNS(null, 'pointer-events', 'visibleStroke');
                    } else {
                        // This is the default
                        node.setAttributeNS(null, 'pointer-events', 'visiblePainted');
                    }
                    this._setAttribute(function () {
                        node.setAttributeNS(null, 'fill-opacity', oo);
                    }, el.visPropOld.fillopacity);
                }

                if (grad === 'linear' || grad === 'radial') {
                    this.updateGradient(el);
                }
            }
            el.visPropOld.fillcolor = rgba;
            el.visPropOld.fillopacity = o;
        },

        // documented in JXG.AbstractRenderer
        setObjectStrokeColor: function (el, color, opacity) {
            var rgba = Type.evaluate(color), c, rgbo,
                o = Type.evaluate(opacity), oo,
                node;

            o = (o > 0) ? o : 0;

            if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
                return;
            }

            if (Type.exists(rgba) && rgba !== false) {
                if (rgba.length !== 9) {          // RGB, not RGBA
                    c = rgba;
                    oo = o;
                } else {                       // True RGBA, not RGB
                    rgbo = Color.rgba2rgbo(rgba);
                    c = rgbo[0];
                    oo = o * rgbo[1];
                }

                node = el.rendNode;

                if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
                    if (Type.evaluate(el.visProp.display) === 'html') {
                        this._setAttribute(function () {
                            node.style.color = c;
                            node.style.opacity = oo;
                        }, el.visPropOld.strokecolor);

                    } else {
                        this._setAttribute(function () {
                            node.setAttributeNS(null, "style", "fill:" + c);
                            node.setAttributeNS(null, "style", "fill-opacity:" + oo);
                        }, el.visPropOld.strokecolor);
                    }
                } else {
                    this._setAttribute(function () {
                        node.setAttributeNS(null, 'stroke', c);
                        node.setAttributeNS(null, 'stroke-opacity', oo);
                    }, el.visPropOld.strokecolor);
                }

                if (el.elementClass === Const.OBJECT_CLASS_CURVE ||
                    el.elementClass === Const.OBJECT_CLASS_LINE) {
                    if (Type.evaluate(el.visProp.firstarrow)) {
                        this._setArrowColor(el.rendNodeTriangleStart, c, oo, el, el.visPropCalc.typeFirst);
                    }

                    if (Type.evaluate(el.visProp.lastarrow)) {
                        this._setArrowColor(el.rendNodeTriangleEnd, c, oo, el, el.visPropCalc.typeLast);
                    }
                }
            }

            el.visPropOld.strokecolor = rgba;
            el.visPropOld.strokeopacity = o;
        },

        // documented in JXG.AbstractRenderer
        setObjectStrokeWidth: function (el, width) {
            var node,
                w = Type.evaluate(width);

            if (isNaN(w) || el.visPropOld.strokewidth === w) {
                return;
            }

            node = el.rendNode;
            this.setPropertyPrim(node, 'stroked', 'true');
            if (Type.exists(w)) {
                this.setPropertyPrim(node, 'stroke-width', w + 'px');

                // if (el.elementClass === Const.OBJECT_CLASS_CURVE ||
                // el.elementClass === Const.OBJECT_CLASS_LINE) {
                //     if (Type.evaluate(el.visProp.firstarrow)) {
                //         this._setArrowWidth(el.rendNodeTriangleStart, w, el.rendNode);
                //     }
                //
                //     if (Type.evaluate(el.visProp.lastarrow)) {
                //         this._setArrowWidth(el.rendNodeTriangleEnd, w, el.rendNode);
                //     }
                // }
            }
            el.visPropOld.strokewidth = w;
        },

        // documented in JXG.AbstractRenderer
        setLineCap: function (el) {
            var capStyle = Type.evaluate(el.visProp.linecap);

            if (capStyle === undefined || capStyle === '' || el.visPropOld.linecap === capStyle ||
                !Type.exists(el.rendNode)) {
                return;
            }

            this.setPropertyPrim(el.rendNode, 'stroke-linecap', capStyle);
            el.visPropOld.linecap = capStyle;

        },

        // documented in JXG.AbstractRenderer
        setShadow: function (el) {
            var ev_s = Type.evaluate(el.visProp.shadow);
            if (el.visPropOld.shadow === ev_s) {
                return;
            }

            if (Type.exists(el.rendNode)) {
                if (ev_s) {
                    el.rendNode.setAttributeNS(null, 'filter', 'url(#' + this.container.id + '_' + 'f1)');
                } else {
                    el.rendNode.removeAttributeNS(null, 'filter');
                }
            }
            el.visPropOld.shadow = ev_s;
        },

        /* **************************
         * renderer control
         * **************************/

        // documented in JXG.AbstractRenderer
        suspendRedraw: function () {
            // It seems to be important for the Linux version of firefox
            //this.suspendHandle = this.svgRoot.suspendRedraw(10000);
        },

        // documented in JXG.AbstractRenderer
        unsuspendRedraw: function () {
            //this.svgRoot.unsuspendRedraw(this.suspendHandle);
            //this.svgRoot.unsuspendRedrawAll();
            //this.svgRoot.forceRedraw();
        },

        // documented in AbstractRenderer
        resize: function (w, h) {
            // this.svgRoot.style.width  = parseFloat(w) + 'px';
            // this.svgRoot.style.height = parseFloat(h) + 'px';

            this.svgRoot.setAttribute('width',  parseFloat(w));
            this.svgRoot.setAttribute('height', parseFloat(h));
            // this.svgRoot.setAttribute('width',  '100%');
            // this.svgRoot.setAttribute('height', '100%');
        },

        // documented in JXG.AbstractRenderer
        createTouchpoints: function (n) {
            var i, na1, na2, node;
            this.touchpoints = [];
            for (i = 0; i < n; i++) {
                na1 = 'touchpoint1_' + i;
                node = this.createPrim('path', na1);
                this.appendChildPrim(node, 19);
                node.setAttributeNS(null, 'd', 'M 0 0');
                this.touchpoints.push(node);

                this.setPropertyPrim(node, 'stroked', 'true');
                this.setPropertyPrim(node, 'stroke-width', '1px');
                node.setAttributeNS(null, 'stroke', '#000000');
                node.setAttributeNS(null, 'stroke-opacity', 1.0);
                node.setAttributeNS(null, 'display', 'none');

                na2 = 'touchpoint2_' + i;
                node = this.createPrim('ellipse', na2);
                this.appendChildPrim(node, 19);
                this.updateEllipsePrim(node, 0, 0, 0, 0);
                this.touchpoints.push(node);

                this.setPropertyPrim(node, 'stroked', 'true');
                this.setPropertyPrim(node, 'stroke-width', '1px');
                node.setAttributeNS(null, 'stroke', '#000000');
                node.setAttributeNS(null, 'stroke-opacity', 1.0);
                node.setAttributeNS(null, 'fill', '#ffffff');
                node.setAttributeNS(null, 'fill-opacity', 0.0);

                node.setAttributeNS(null, 'display', 'none');
            }
        },

        // documented in JXG.AbstractRenderer
        showTouchpoint: function (i) {
            if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
                this.touchpoints[2 * i].setAttributeNS(null, 'display', 'inline');
                this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'inline');
            }
        },

        // documented in JXG.AbstractRenderer
        hideTouchpoint: function (i) {
            if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
                this.touchpoints[2 * i].setAttributeNS(null, 'display', 'none');
                this.touchpoints[2 * i + 1].setAttributeNS(null, 'display', 'none');
            }
        },

        // documented in JXG.AbstractRenderer
        updateTouchpoint: function (i, pos) {
            var x, y,
                d = 37;

            if (this.touchpoints && i >= 0 && 2 * i < this.touchpoints.length) {
                x = pos[0];
                y = pos[1];

                this.touchpoints[2 * i].setAttributeNS(null, 'd', 'M ' + (x - d) + ' ' + y + ' ' +
                    'L ' + (x + d) + ' ' + y + ' ' +
                    'M ' + x + ' ' + (y - d) + ' ' +
                    'L ' + x + ' ' + (y + d));
                this.updateEllipsePrim(this.touchpoints[2 * i + 1], pos[0], pos[1], 25, 25);
            }
        },

        /**
         * Walk recursively through the DOM subtree of a node and collect all
         * value attributes together with the id of that node.
         * <b>Attention:</b> Only values of nodes having a valid id are taken.
         * @param  {Node} node   root node of DOM subtree that will be searched recursively.
         * @return {Array}      Array with entries of the form [id, value]
         * @private
         */
        _getValuesOfDOMElements: function (node) {
            var values = [];
            if (node.nodeType === 1) {
                node = node.firstChild;
                while (node) {
                    if (node.id !== undefined && node.value !== undefined) {
                        values.push([node.id, node.value]);
                    }
                    values = values.concat(this._getValuesOfDOMElements(node));
                    node = node.nextSibling;
                }
            }
            return values;
        },

        _getDataUri: function (url, callback) {
            var image = new Image();

            image.onload = function () {
                var canvas = document.createElement('canvas');
                canvas.width = this.naturalWidth; // or 'width' if you want a special/scaled size
                canvas.height = this.naturalHeight; // or 'height' if you want a special/scaled size

                canvas.getContext('2d').drawImage(this, 0, 0);

                callback(canvas.toDataURL('image/png'));
                canvas.remove();
            };

            image.src = url;
        },

        _getImgDataURL: function(svgRoot) {
            var images, len, canvas, ctx,
                ur, i;

            images = svgRoot.getElementsByTagName("image");
            len = images.length;
            if (len > 0) {
                canvas = document.createElement('canvas');
                //img = new Image();
                for (i = 0; i < len; i++) {
                    images[i].setAttribute("crossorigin", "anonymous");
                    //img.src = images[i].href;
                    //img.onload = function() {
                    // img.crossOrigin = "anonymous";
                    ctx = canvas.getContext('2d');
                    canvas.width = images[i].getAttribute("width");
                    canvas.height = images[i].getAttribute("height");
                    try {
                        ctx.drawImage(images[i], 0, 0, canvas.width, canvas.height);

                        // If the image is not png, the format must be specified here
                        ur = canvas.toDataURL();
                        images[i].setAttribute("xlink:href", ur);
                    } catch (err) {
                        console.log("CORS problem! Image can not be used", err);
                    }
                }
                //canvas.remove();
            }
            return true;
        },

        /**
         * Return a data URI of the SVG code representeing the construction.
         * The SVG code of the construction is base64 encoded. The return string starts
         * with "data:image/svg+xml;base64,...".
         *
         * @param {Boolean} ignoreTexts If true, the foreignObject tag is set to display=none.
         * This is necessary for older versions of Safari. Default: false
         * @returns {String}  data URI string
         */
        dumpToDataURI: function (ignoreTexts) {
            var svgRoot = this.svgRoot,
                btoa = window.btoa || Base64.encode,
                svg,
                virtualNode, doc,
                i, len,
                values = [];

            // Move all HTML tags (beside the SVG root) of the container
            // to the foreignObject element inside of the svgRoot node
            // Problem:
            // input values are not copied. This can be verified by looking at an innerHTML output
            // of an input element. Therefore, we do it "by hand".
            if (this.container.hasChildNodes() && Type.exists(this.foreignObjLayer)) {
                if (!ignoreTexts) {
                    this.foreignObjLayer.setAttribute('display', 'inline');
                }
                while (svgRoot.nextSibling) {

                    // Copy all value attributes
                    values = values.concat(this._getValuesOfDOMElements(svgRoot.nextSibling));

                    this.foreignObjLayer.appendChild(svgRoot.nextSibling);
                }
            }

            this._getImgDataURL(svgRoot);

            // Convert the SVG graphic into a string containing SVG code
            svgRoot.setAttribute("xmlns", "http://www.w3.org/2000/svg");
            svg = new XMLSerializer().serializeToString(svgRoot);

            if (ignoreTexts !== true) {
                // Handle SVG texts
                // Insert all value attributes back into the svg string
                len = values.length;
                for (i = 0; i < len; i++) {
                    svg = svg.replace('id="' + values[i][0] + '"', 'id="' + values[i][0] + '" value="' + values[i][1] + '"');
                }
            }

            // if (false) {
            //     // Debug: use example svg image
            //     svg = '<svg xmlns="http://www.w3.org/2000/svg" version="1.0" width="220" height="220"><rect width="66" height="30" x="21" y="32" stroke="#204a87" stroke-width="2" fill="none" /></svg>';
            // }

            // In IE we have to remove the namespace again.
            if ((svg.match(/xmlns="http:\/\/www.w3.org\/2000\/svg"/g) || []).length > 1) {
                svg = svg.replace(/xmlns="http:\/\/www.w3.org\/2000\/svg"/g, '');
            }

            // Safari fails if the svg string contains a "&nbsp;"
            // Obsolete with Safari 12+
            svg = svg.replace(/&nbsp;/g, ' ');

            // Move all HTML tags back from
            // the foreignObject element to the container
            if (Type.exists(this.foreignObjLayer) && this.foreignObjLayer.hasChildNodes()) {
                // Restore all HTML elements
                while (this.foreignObjLayer.firstChild) {
                    this.container.appendChild(this.foreignObjLayer.firstChild);
                }
                this.foreignObjLayer.setAttribute("display", "none");
            }

            return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg)));
        },

        /**
         * Convert the SVG construction into an HTML canvas image.
         * This works for all SVG supporting browsers. Implemented as Promise.
         * <p>
         * For IE, it is realized as function.
         * It works from version 9, with the exception that HTML texts
         * are ignored on IE. The drawing is done with a delay of
         * 200 ms. Otherwise there would be problems with IE.
         *
         * @param {String} canvasId Id of an HTML canvas element
         * @param {Number} w Width in pixel of the dumped image, i.e. of the canvas tag.
         * @param {Number} h Height in pixel of the dumped image, i.e. of the canvas tag.
         * @param {Boolean} ignoreTexts If true, the foreignObject tag is taken out from the SVG root.
         * This is necessary for older versions of Safari. Default: false
         * @returns {Promise}  Promise object
         *
         * @example
         * 	board.renderer.dumpToCanvas('canvas').then(function() { console.log('done'); });
         *
         * @example
         *  // IE 11 example:
         * 	board.renderer.dumpToCanvas('canvas');
         * 	setTimeout(function() { console.log('done'); }, 400);
         */
        dumpToCanvas: function (canvasId, w, h, ignoreTexts) {
            var svg, tmpImg, cv, ctx;

            // Prepare the canvas element
            cv = document.getElementById(canvasId);

            // Clear the canvas
            /* eslint-disable no-self-assign */
            cv.width = cv.width;
            /* eslint-enable no-self-assign */

            ctx = cv.getContext("2d");
            if (w !== undefined && h !== undefined) {
                cv.style.width = parseFloat(w) + 'px';
                cv.style.height = parseFloat(h) + 'px';
                // Scale twice the CSS size to make the image crisp
                // cv.setAttribute('width', 2 * parseFloat(wOrg));
                // cv.setAttribute('height', 2 * parseFloat(hOrg));
                // ctx.scale(2 * wOrg / w, 2 * hOrg / h);
                cv.setAttribute('width', parseFloat(w));
                cv.setAttribute('height', parseFloat(h));
            }

            // Display the SVG string as data-uri in an HTML img.
            tmpImg = new Image();
            svg = this.dumpToDataURI(ignoreTexts);
            tmpImg.src = svg;

            // Finally, draw the HTML img in the canvas.
            if (!('Promise' in window)) {
                tmpImg.onload = function () {
                    // IE needs a pause...
                    // Seems to be broken
                    window.setTimeout(function() {
                        try {
                            ctx.drawImage(tmpImg, 0, 0, w, h);
                        } catch (err) {
                            console.log("screenshots not longer supported on IE");
                        }
                    }, 200);
                };
                return this;
            }

            return new Promise(function(resolve, reject) {
                try {
                    tmpImg.onload = function () {
                        ctx.drawImage(tmpImg, 0, 0, w, h);
                        resolve();
                    };
                } catch (e) {
                    reject(e);
                }
            });

        },

        /**
         * Display SVG image in html img-tag which enables
         * easy download for the user.
         *
         * Support:
         * <ul>
         * <li> IE: No
         * <li> Edge: full
         * <li>Firefox: full
         * <li> Chrome: full
         * <li> Safari: full (No text support in versions prior to 12).
         * </ul>
         *
         * @param {JXG.Board} board Link to the board.
         * @param {String} imgId Optional id of an img object. If given and different from the empty string,
         * the screenshot is copied to this img object. The width and height will be set to the values of the
         * JSXGraph container.
         * @param {Boolean} ignoreTexts If set to true, the foreignObject is taken out of the
         *  SVGRoot and texts are not displayed. This is mandatory for Safari. Default: false
         * @return {Object}       the svg renderer object
         */
        screenshot: function (board, imgId, ignoreTexts) {
            var node,
                doc = this.container.ownerDocument,
                parent = this.container.parentNode,
                cPos,
                canvas, id,
                img,
                button, buttonText,
                w, h,
                bas = board.attr.screenshot,
                zbar, zbarDisplay, cssTxt,
                newImg = false,
                _copyCanvasToImg,
                isDebug = false;

            if (this.type === 'no') {
                return this;
            }

            w = bas.scale * this.container.getBoundingClientRect().width;
            h = bas.scale * this.container.getBoundingClientRect().height;

            if (imgId === undefined || imgId === '') {
                newImg = true;
                img = new Image(); //doc.createElement('img');
                img.style.width = w + 'px';
                img.style.height = h + 'px';
            } else {
                newImg = false;
                img = doc.getElementById(imgId);
            }
            // img.crossOrigin = 'anonymous';

            // Create div which contains canvas element and close button
            if (newImg) {
                node = doc.createElement('div');
                node.style.cssText = bas.css;
                node.style.width = (w) + 'px';
                node.style.height = (h) + 'px';
                node.style.zIndex = this.container.style.zIndex + 120;

                // Try to position the div exactly over the JSXGraph board
                node.style.position = 'absolute';
                node.style.top = this.container.offsetTop + 'px';
                node.style.left = this.container.offsetLeft + 'px';
            }

            if (!isDebug) {
                // Create canvas element and add it to the DOM
                // It will be removed after the image has been stored.
                canvas = doc.createElement('canvas');
                id = Math.random().toString(36).substr(2, 5);
                canvas.setAttribute('id', id);
                canvas.setAttribute('width', w);
                canvas.setAttribute('height', h);
                canvas.style.width = w + 'px';
                canvas.style.height = w + 'px';
                canvas.style.display = 'none';
                parent.appendChild(canvas);
            } else {
                // Debug: use canvas element 'jxgbox_canvas' from jsxdev/dump.html
                id = 'jxgbox_canvas';
                canvas = document.getElementById(id);
            }

            if (newImg) {
                // Create close button
                button = doc.createElement('span');
                buttonText = doc.createTextNode('\u2716');
                button.style.cssText = bas.cssButton;
                button.appendChild(buttonText);
                button.onclick = function () {
                    node.parentNode.removeChild(node);
                };

                // Add all nodes
                node.appendChild(img);
                node.appendChild(button);
                parent.insertBefore(node, this.container.nextSibling);
            }

            // Hide navigation bar in board
            zbar = document.getElementById(this.container.id + '_navigationbar');
            if (Type.exists(zbar)) {
                zbarDisplay = zbar.style.display;
                zbar.style.display = 'none';
            }

            _copyCanvasToImg = function() {
                // Show image in img tag
                img.src = canvas.toDataURL('image/png');

                // Remove canvas node
                if (!isDebug) {
                    parent.removeChild(canvas);
                }
            };

            // Create screenshot in image element
            if ('Promise' in window) {
                this.dumpToCanvas(id, w, h, ignoreTexts).then(_copyCanvasToImg);
            } else {
                // IE
                this.dumpToCanvas(id, w, h, ignoreTexts);
                window.setTimeout(_copyCanvasToImg, 200);
            }

            // Show navigation bar in board
            if (Type.exists(zbar)) {
                zbar.style.display = zbarDisplay;
            }

            return this;
        }

    });

    return JXG.SVGRenderer;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */
/*jslint nomen: true, plusplus: true, newcap:true*/

/* depends:
 jxg
 renderer/abstract
 base/constants
 utils/type
 utils/color
 math/math
 math/numerics
*/

define('renderer/vml',[
    'jxg', 'renderer/abstract', 'base/constants', 'utils/type', 'utils/color', 'math/math', 'math/numerics'
], function (JXG, AbstractRenderer, Const, Type, Color, Mat, Numerics) {

    "use strict";

    /**
     * Uses VML to implement the rendering methods defined in {@link JXG.AbstractRenderer}.
     * VML was used in very old Internet Explorer versions upto IE 8.
     * 
     * 
     * @class JXG.VMLRenderer
     * @augments JXG.AbstractRenderer
     * @param {Node} container Reference to a DOM node containing the board.
     * @see JXG.AbstractRenderer
     * @deprecated
     */
    JXG.VMLRenderer = function (container) {
        this.type = 'vml';

        this.container = container;
        this.container.style.overflow = 'hidden';
        if (this.container.style.position === '') {
            this.container.style.position = 'relative';
        }
        this.container.onselectstart = function () {
            return false;
        };

        this.resolution = 10; // Paths are drawn with a a resolution of this.resolution/pixel.

        // Add VML includes and namespace
        // Original: IE <=7
        //container.ownerDocument.createStyleSheet().addRule("v\\:*", "behavior: url(#default#VML);");
        if (!Type.exists(JXG.vmlStylesheet)) {
            container.ownerDocument.namespaces.add("jxgvml", "urn:schemas-microsoft-com:vml");
            JXG.vmlStylesheet = this.container.ownerDocument.createStyleSheet();
            JXG.vmlStylesheet.addRule(".jxgvml", "behavior:url(#default#VML)");
        }

        try {
            if (!container.ownerDocument.namespaces.jxgvml) {
                container.ownerDocument.namespaces.add("jxgvml", "urn:schemas-microsoft-com:vml");
            }

            this.createNode = function (tagName) {
                return container.ownerDocument.createElement('<jxgvml:' + tagName + ' class="jxgvml">');
            };
        } catch (e) {
            this.createNode = function (tagName) {
                return container.ownerDocument.createElement('<' + tagName + ' xmlns="urn:schemas-microsoft.com:vml" class="jxgvml">');
            };
        }

        // dash styles
        this.dashArray = ['Solid', '1 1', 'ShortDash', 'Dash', 'LongDash', 'ShortDashDot', 'LongDashDot'];
    };

    JXG.VMLRenderer.prototype = new AbstractRenderer();

    JXG.extend(JXG.VMLRenderer.prototype, /** @lends JXG.VMLRenderer.prototype */ {

        /**
         * Sets attribute <tt>key</tt> of node <tt>node</tt> to <tt>value</tt>.
         * @param {Node} node A DOM node.
         * @param {String} key Name of the attribute.
         * @param {String} val New value of the attribute.
         * @param {Boolean} [iFlag=false] If false, the attribute's name is case insensitive.
         */
        _setAttr: function (node, key, val, iFlag) {
            try {
                if (this.container.ownerDocument.documentMode === 8) {
                    node[key] = val;
                } else {
                    node.setAttribute(key, val, iFlag);
                }
            } catch (e) {
                JXG.debug('_setAttr:'/*node.id*/ + ' ' + key + ' ' + val + '<br>\n');
            }
        },

        /* ******************************** *
         *  This renderer does not need to
         *  override draw/update* methods
         *  since it provides draw/update*Prim
         *  methods.
         * ******************************** */

        /* **************************
         *    Lines
         * **************************/

        // documented in AbstractRenderer
        updateTicks: function (ticks) {
            var i, len, c, x, y,
                r = this.resolution,
                tickArr = [];

            len = ticks.ticks.length;
            for (i = 0; i < len; i++) {
                c = ticks.ticks[i];
                x = c[0];
                y = c[1];

                if (Type.isNumber(x[0]) && Type.isNumber(x[1])) {
                    tickArr.push(' m ' + Math.round(r * x[0]) + ', ' + Math.round(r * y[0]) +
                        ' l ' + Math.round(r * x[1]) + ', ' + Math.round(r * y[1]) + ' ');
                }
            }

            if (!Type.exists(ticks.rendNode)) {
                ticks.rendNode = this.createPrim('path', ticks.id);
                this.appendChildPrim(ticks.rendNode, Type.evaluate(ticks.visProp.layer));
            }

            this._setAttr(ticks.rendNode, 'stroked', 'true');
            this._setAttr(ticks.rendNode, 'strokecolor', Type.evaluate(ticks.visProp.strokecolor), 1);
            this._setAttr(ticks.rendNode, 'strokeweight', Type.evaluate(ticks.visProp.strokewidth));
            this._setAttr(ticks.rendNodeStroke, 'opacity', (Type.evaluate(ticks.visProp.strokeopacity) * 100) + '%');
            this.updatePathPrim(ticks.rendNode, tickArr, ticks.board);
        },

        /* **************************
         *    Text related stuff
         * **************************/

        // Already documented in JXG.AbstractRenderer
        displayCopyright: function (str, fontsize) {
            var node, t;

            node = this.createNode('textbox');
            node.style.position = 'absolute';
            this._setAttr(node, 'id', this.container.id + '_' + 'licenseText');

            node.style.left = 20;
            node.style.top = 2;
            node.style.fontSize = fontsize;
            node.style.color = '#356AA0';
            node.style.fontFamily = 'Arial,Helvetica,sans-serif';
            this._setAttr(node, 'opacity', '30%');
            node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand', enabled = false) progid:DXImageTransform.Microsoft.Alpha(opacity = 30, enabled = true)";

            t = this.container.ownerDocument.createTextNode(str);
            node.appendChild(t);
            this.appendChildPrim(node, 0);
        },

        // documented in AbstractRenderer
        drawInternalText: function (el) {
            var node;
            node = this.createNode('textbox');
            node.style.position = 'absolute';
            el.rendNodeText = this.container.ownerDocument.createTextNode('');
            node.appendChild(el.rendNodeText);
            this.appendChildPrim(node, 9);
            node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand', enabled = false) progid:DXImageTransform.Microsoft.Alpha(opacity = 100, enabled = false)";

            return node;
        },

        // documented in AbstractRenderer
        updateInternalText: function (el) {
            var v, content = el.plaintext,
                m = this.joinTransforms(el, el.transformations),
                offset = [0, 0],
                maxX, maxY, minX, minY, i,
                node = el.rendNode,
                p = [],
                ev_ax = el.getAnchorX(),
                ev_ay = el.getAnchorY();

            if (!isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
                // Horizontal
                if (ev_ax === 'right') {
                    offset[0] = 1;
                } else if (ev_ax === 'middle') {
                    offset[0] = 0.5;
                } // default (ev_ax === 'left') offset[0] = 0;

                // Vertical
                if (ev_ay === 'bottom') {
                    offset[1] = 1;
                } else if (ev_ay === 'middle') {
                    offset[1] = 0.5;
                } // default (ev_ay === 'top') offset[1] = 0;

                // Compute maxX, maxY, minX, minY
                p[0] = Mat.matVecMult(m, [1,
                                          el.coords.scrCoords[1] - offset[0] * el.size[0],
                                          el.coords.scrCoords[2] + (1 - offset[1]) * el.size[1] + this.vOffsetText]);
                p[0][1] /= p[0][0];
                p[0][2] /= p[0][0];
                p[1] = Mat.matVecMult(m, [1,
                                          el.coords.scrCoords[1] + (1 - offset[0]) * el.size[0],
                                          el.coords.scrCoords[2] + (1 - offset[1]) * el.size[1] + this.vOffsetText]);
                p[1][1] /= p[1][0];
                p[1][2] /= p[1][0];
                p[2] = Mat.matVecMult(m, [1,
                                          el.coords.scrCoords[1] + (1 - offset[0]) * el.size[0],
                                          el.coords.scrCoords[2] - offset[1] * el.size[1] + this.vOffsetText]);
                p[2][1] /= p[2][0];
                p[2][2] /= p[2][0];
                p[3] = Mat.matVecMult(m, [1,
                                          el.coords.scrCoords[1] - offset[0] * el.size[0],
                                          el.coords.scrCoords[2] - offset[1] * el.size[1] + this.vOffsetText]);
                p[3][1] /= p[3][0];
                p[3][2] /= p[3][0];
                maxX = p[0][1];
                minX = p[0][1];
                maxY = p[0][2];
                minY = p[0][2];

                for (i = 1; i < 4; i++) {
                    maxX = Math.max(maxX, p[i][1]);
                    minX = Math.min(minX, p[i][1]);
                    maxY = Math.max(maxY, p[i][2]);
                    minY = Math.min(minY, p[i][2]);
                }

                // Horizontal
                v = offset[0] === 1 ? Math.floor(el.board.canvasWidth - maxX) : Math.floor(minX);
                if (el.visPropOld.left !== (ev_ax + v)) {
                    if (offset[0] === 1) {
                        el.rendNode.style.right = v + 'px';
                        el.rendNode.style.left = 'auto';
                    } else {
                        el.rendNode.style.left = v + 'px';
                        el.rendNode.style.right = 'auto';
                    }
                    el.visPropOld.left = ev_ax + v;
                }

                // Vertical
                v = offset[1] === 1 ? Math.floor(el.board.canvasHeight - maxY) : Math.floor(minY);
                if (el.visPropOld.top !== (ev_ay + v)) {
                    if (offset[1] === 1) {
                        el.rendNode.style.bottom = v + 'px';
                        el.rendNode.style.top = 'auto';
                    } else {
                        el.rendNode.style.top = v + 'px';
                        el.rendNode.style.bottom = 'auto';
                    }
                    el.visPropOld.top = ev_ay + v;
                }

            }

            if (el.htmlStr !== content) {
                el.rendNodeText.data = content;
                el.htmlStr = content;
            }

            //this.transformImage(el, el.transformations);
            node.filters.item(0).M11 = m[1][1];
            node.filters.item(0).M12 = m[1][2];
            node.filters.item(0).M21 = m[2][1];
            node.filters.item(0).M22 = m[2][2];
            node.filters.item(0).enabled = true;
        },

        /* **************************
         *    Image related stuff
         * **************************/

        // Already documented in JXG.AbstractRenderer
        drawImage: function (el) {
            // IE 8: Bilder ueber data URIs werden bis 32kB unterstuetzt.
            var node;

            node = this.container.ownerDocument.createElement('img');
            node.style.position = 'absolute';
            this._setAttr(node, 'id', this.container.id + '_' + el.id);

            this.container.appendChild(node);
            this.appendChildPrim(node, Type.evaluate(el.visProp.layer));

            // Adding the rotation filter. This is always filter item 0:
            // node.filters.item(0), see transformImage
            // Also add the alpha filter. This is always filter item 1
            // node.filters.item(1), see setObjectFillColor and setObjectSTrokeColor
            //node.style.filter = node.style['-ms-filter'] = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand')";
            node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') progid:DXImageTransform.Microsoft.Alpha(opacity = 100, enabled = false)";
            el.rendNode = node;
            this.updateImage(el);
        },

        // Already documented in JXG.AbstractRenderer
        transformImage: function (el, t) {
            var m, s, maxX, maxY, minX, minY, i, nt,
                node = el.rendNode,
                p = [],
                len = t.length;

            if (len > 0) {
                /*
                nt = el.rendNode.style.filter.toString();
                if (!nt.match(/DXImageTransform/)) {
                    node.style.filter = "progid:DXImageTransform.Microsoft.Matrix(M11='1.0', sizingMethod='auto expand') " + nt;
                }
                */

                m = this.joinTransforms(el, t);
                p[0] = Mat.matVecMult(m, el.coords.scrCoords);
                p[0][1] /= p[0][0];
                p[0][2] /= p[0][0];
                p[1] = Mat.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2]]);
                p[1][1] /= p[1][0];
                p[1][2] /= p[1][0];
                p[2] = Mat.matVecMult(m, [1, el.coords.scrCoords[1] + el.size[0], el.coords.scrCoords[2] - el.size[1]]);
                p[2][1] /= p[2][0];
                p[2][2] /= p[2][0];
                p[3] = Mat.matVecMult(m, [1, el.coords.scrCoords[1], el.coords.scrCoords[2] - el.size[1]]);
                p[3][1] /= p[3][0];
                p[3][2] /= p[3][0];
                maxX = p[0][1];
                minX = p[0][1];
                maxY = p[0][2];
                minY = p[0][2];

                for (i = 1; i < 4; i++) {
                    maxX = Math.max(maxX, p[i][1]);
                    minX = Math.min(minX, p[i][1]);
                    maxY = Math.max(maxY, p[i][2]);
                    minY = Math.min(minY, p[i][2]);
                }
                node.style.left = Math.floor(minX) + 'px';
                node.style.top  = Math.floor(minY) + 'px';

                node.filters.item(0).M11 = m[1][1];
                node.filters.item(0).M12 = m[1][2];
                node.filters.item(0).M21 = m[2][1];
                node.filters.item(0).M22 = m[2][2];
                node.filters.item(0).enabled = true;
            }
        },

        // Already documented in JXG.AbstractRenderer
        updateImageURL: function (el) {
            var url = Type.evaluate(el.url);

            this._setAttr(el.rendNode, 'src', url);
        },

        /* **************************
         * Render primitive objects
         * **************************/

        // Already documented in JXG.AbstractRenderer
        appendChildPrim: function (node, level) {
            // For trace nodes
            if (!Type.exists(level)) {
                level = 0;
            }

            node.style.zIndex = level;
            this.container.appendChild(node);

            return node;
        },

        // Already documented in JXG.AbstractRenderer
        appendNodesToElement: function (el, type) {
            if (type === 'shape' || type === 'path' || type === 'polygon') {
                el.rendNodePath = this.getElementById(el.id + '_path');
            }
            el.rendNodeFill = this.getElementById(el.id + '_fill');
            el.rendNodeStroke = this.getElementById(el.id + '_stroke');
            el.rendNodeShadow = this.getElementById(el.id + '_shadow');
            el.rendNode = this.getElementById(el.id);
        },

        // Already documented in JXG.AbstractRenderer
        createPrim: function (type, id) {
            var node, pathNode,
                fillNode = this.createNode('fill'),
                strokeNode = this.createNode('stroke'),
                shadowNode = this.createNode('shadow');

            this._setAttr(fillNode, 'id', this.container.id + '_' + id + '_fill');
            this._setAttr(strokeNode, 'id', this.container.id + '_' + id + '_stroke');
            this._setAttr(shadowNode, 'id', this.container.id + '_' + id + '_shadow');

            if (type === 'circle' || type === 'ellipse') {
                node = this.createNode('oval');
                node.appendChild(fillNode);
                node.appendChild(strokeNode);
                node.appendChild(shadowNode);
            } else if (type === 'polygon' || type === 'path' || type === 'shape' || type === 'line') {
                node = this.createNode('shape');
                node.appendChild(fillNode);
                node.appendChild(strokeNode);
                node.appendChild(shadowNode);
                pathNode = this.createNode('path');
                this._setAttr(pathNode, 'id', this.container.id + '_' + id + '_path');
                node.appendChild(pathNode);
            } else {
                node = this.createNode(type);
                node.appendChild(fillNode);
                node.appendChild(strokeNode);
                node.appendChild(shadowNode);
            }

            node.style.position = 'absolute';
            node.style.left = '0px';
            node.style.top = '0px';
            this._setAttr(node, 'id', this.container.id + '_' + id);

            return node;
        },

        // Already documented in JXG.AbstractRenderer
        remove: function (node) {
            if (Type.exists(node)) {
                node.removeNode(true);
            }
        },

        // Already documented in JXG.AbstractRenderer
        makeArrows: function (el) {
            var nodeStroke,
                ev_fa = Type.evaluate(el.visProp.firstarrow),
                ev_la = Type.evaluate(el.visProp.lastarrow);

            if (el.visPropOld.firstarrow === ev_fa && el.visPropOld.lastarrow === ev_la) {
                return;
            }

            if (ev_fa) {
                nodeStroke = el.rendNodeStroke;
                this._setAttr(nodeStroke, 'startarrow', 'block');
                this._setAttr(nodeStroke, 'startarrowlength', 'long');
            } else {
                nodeStroke = el.rendNodeStroke;
                if (Type.exists(nodeStroke)) {
                    this._setAttr(nodeStroke, 'startarrow', 'none');
                }
            }

            if (ev_la) {
                nodeStroke = el.rendNodeStroke;
                this._setAttr(nodeStroke, 'id', this.container.id + '_' + el.id + "stroke");
                this._setAttr(nodeStroke, 'endarrow', 'block');
                this._setAttr(nodeStroke, 'endarrowlength', 'long');
            } else {
                nodeStroke = el.rendNodeStroke;
                if (Type.exists(nodeStroke)) {
                    this._setAttr(nodeStroke, 'endarrow', 'none');
                }
            }
            el.visPropOld.firstarrow = ev_fa;
            el.visPropOld.lastarrow = ev_la;
        },

        // Already documented in JXG.AbstractRenderer
        updateEllipsePrim: function (node, x, y, rx, ry) {
            node.style.left = Math.floor(x - rx) + 'px';
            node.style.top =  Math.floor(y - ry) + 'px';
            node.style.width = Math.floor(Math.abs(rx) * 2) + 'px';
            node.style.height = Math.floor(Math.abs(ry) * 2) + 'px';
        },

        // Already documented in JXG.AbstractRenderer
        updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) {
            var s, r = this.resolution;

            if (!isNaN(p1x + p1y + p2x + p2y)) {
                s = ['m ', Math.floor(r * p1x), ', ', Math.floor(r * p1y), ' l ', Math.floor(r * p2x), ', ', Math.floor(r * p2y)];
                this.updatePathPrim(node, s, board);
            }
        },

        // Already documented in JXG.AbstractRenderer
        updatePathPrim: function (node, pointString, board) {
            var x = board.canvasWidth,
                y = board.canvasHeight;
            if (pointString.length <= 0) {
                pointString = ['m 0,0'];
            }
            node.style.width = x;
            node.style.height = y;
            this._setAttr(node, 'coordsize', [Math.floor(this.resolution * x), Math.floor(this.resolution * y)].join(','));
            this._setAttr(node, 'path', pointString.join(""));
        },

        // Already documented in JXG.AbstractRenderer
        updatePathStringPoint: function (el, size, type) {
            var s = [],
                mround = Math.round,
                scr = el.coords.scrCoords,
                sqrt32 = size * Math.sqrt(3) * 0.5,
                s05 = size * 0.5,
                r = this.resolution;

            if (type === 'x') {
                s.push([
                    ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] - size)),
                    ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] + size)),
                    ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2] - size)),
                    ' l ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2] + size))
                ].join(''));
            } else if (type === '+') {
                s.push([
                    ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
                    ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
                    ' m ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] - size)),
                    ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] + size))
                ].join(''));
            } else if (type === '<>') {

                s.push([
                    ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
                    ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] + size)),
                    ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
                    ' l ', mround(r * (scr[1])),        ', ', mround(r * (scr[2] - size)),
                    ' x e '
                ].join(''));
            } else if (type === '^') {
                s.push([
                    ' m ', mround(r * (scr[1])),          ', ', mround(r * (scr[2] - size)),
                    ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] + s05)),
                    ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] + s05)),
                    ' x e '
                ].join(''));
            } else if (type === 'v') {
                s.push([
                    ' m ', mround(r * (scr[1])),          ', ', mround(r * (scr[2] + size)),
                    ' l ', mround(r * (scr[1] - sqrt32)), ', ', mround(r * (scr[2] - s05)),
                    ' l ', mround(r * (scr[1] + sqrt32)), ', ', mround(r * (scr[2] - s05)),
                    ' x e '
                ].join(''));
            } else if (type === '>') {
                s.push([
                    ' m ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2])),
                    ' l ', mround(r * (scr[1] - s05)),  ', ', mround(r * (scr[2] - sqrt32)),
                    ' l ', mround(r * (scr[1] - s05)),  ', ', mround(r * (scr[2] + sqrt32)),
                    ' l ', mround(r * (scr[1] + size)), ', ', mround(r * (scr[2]))
                ].join(''));
            } else if (type === '<') {
                s.push([
                    ' m ', mround(r * (scr[1] - size)), ', ', mround(r * (scr[2])),
                    ' l ', mround(r * (scr[1] + s05)),  ', ', mround(r * (scr[2] - sqrt32)),
                    ' l ', mround(r * (scr[1] + s05)),  ', ', mround(r * (scr[2] + sqrt32)),
                    ' x e '
                ].join(''));
            }

            return s;
        },

        // Already documented in JXG.AbstractRenderer
        updatePathStringPrim: function (el) {
            var i, scr,
                pStr = [],
                r = this.resolution,
                mround = Math.round,
                symbm = ' m ',
                symbl = ' l ',
                symbc = ' c ',
                nextSymb = symbm,
                len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html

            if (el.numberPoints <= 0) {
                return '';
            }
            len = Math.min(len, el.points.length);

            if (el.bezierDegree === 1) {
                /*
                if (isNotPlot && el.board.options.curve.RDPsmoothing) {
                    el.points = Numerics.RamerDouglasPeucker(el.points, 1.0);
                }
                */

                for (i = 0; i < len; i++) {
                    scr = el.points[i].scrCoords;
                    if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
                        nextSymb = symbm;
                    } else {
                        // IE has problems with values  being too far away.
                        if (scr[1] > 20000.0) {
                            scr[1] = 20000.0;
                        } else if (scr[1] < -20000.0) {
                            scr[1] = -20000.0;
                        }

                        if (scr[2] > 20000.0) {
                            scr[2] = 20000.0;
                        } else if (scr[2] < -20000.0) {
                            scr[2] = -20000.0;
                        }

                        pStr.push([nextSymb, mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
                        nextSymb = symbl;
                    }
                }
            } else if (el.bezierDegree === 3) {
                i = 0;
                while (i < len) {
                    scr = el.points[i].scrCoords;
                    if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
                        nextSymb = symbm;
                    } else {
                        pStr.push([nextSymb, mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
                        if (nextSymb === symbc) {
                            i += 1;
                            scr = el.points[i].scrCoords;
                            pStr.push([' ', mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
                            i += 1;
                            scr = el.points[i].scrCoords;
                            pStr.push([' ', mround(r * scr[1]), ', ', mround(r * scr[2])].join(''));
                        }
                        nextSymb = symbc;
                    }
                    i += 1;
                }
            }
            pStr.push(' e');
            return pStr;
        },

        // Already documented in JXG.AbstractRenderer
        updatePathStringBezierPrim: function (el) {
            var i, j, k, scr, lx, ly,
                pStr = [],
                f = Type.evaluate(el.visProp.strokewidth),
                r = this.resolution,
                mround = Math.round,
                symbm = ' m ',
                symbl = ' c ',
                nextSymb = symbm,
                isNoPlot = (Type.evaluate(el.visProp.curvetype) !== 'plot'),
                len = Math.min(el.numberPoints, 8192); // otherwise IE 7 crashes in hilbert.html

            if (el.numberPoints <= 0) {
                return '';
            }
            if (isNoPlot && el.board.options.curve.RDPsmoothing) {
                el.points = Numerics.RamerDouglasPeucker(el.points, 1.0);
            }
            len = Math.min(len, el.points.length);

            for (j = 1; j < 3; j++) {
                nextSymb = symbm;
                for (i = 0; i < len; i++) {
                    scr = el.points[i].scrCoords;
                    if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
                        nextSymb = symbm;
                    } else {
                        // IE has problems with values  being too far away.
                        if (scr[1] > 20000.0) {
                            scr[1] = 20000.0;
                        } else if (scr[1] < -20000.0) {
                            scr[1] = -20000.0;
                        }

                        if (scr[2] > 20000.0) {
                            scr[2] = 20000.0;
                        } else if (scr[2] < -20000.0) {
                            scr[2] = -20000.0;
                        }

                        if (nextSymb === symbm) {
                            pStr.push([nextSymb,
                                mround(r * (scr[1])), ' ', mround(r * (scr[2]))].join(''));
                        } else {
                            k = 2 * j;
                            pStr.push([nextSymb,
                                mround(r * (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j))), ' ',
                                mround(r * (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j))), ' ',
                                mround(r * (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j))), ' ',
                                mround(r * (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j))), ' ',
                                mround(r * scr[1]), ' ',
                                mround(r * scr[2])].join(''));
                        }
                        nextSymb = symbl;
                        lx = scr[1];
                        ly = scr[2];
                    }
                }
            }
            pStr.push(' e');
            return pStr;
        },

        // Already documented in JXG.AbstractRenderer
        updatePolygonPrim: function (node, el) {
            var i,
                len = el.vertices.length,
                r = this.resolution,
                scr,
                pStr = [];

            this._setAttr(node, 'stroked', 'false');
            scr = el.vertices[0].coords.scrCoords;

            if (isNaN(scr[1] + scr[2])) {
                return;
            }

            pStr.push(["m ", Math.floor(r * scr[1]), ",", Math.floor(r * scr[2]), " l "].join(''));

            for (i = 1; i < len - 1; i++) {
                if (el.vertices[i].isReal) {
                    scr = el.vertices[i].coords.scrCoords;

                    if (isNaN(scr[1] + scr[2])) {
                        return;
                    }

                    pStr.push(Math.floor(r * scr[1]) + "," + Math.floor(r * scr[2]));
                } else {
                    this.updatePathPrim(node, '', el.board);
                    return;
                }
                if (i < len - 2) {
                    pStr.push(", ");
                }
            }
            pStr.push(" x e");
            this.updatePathPrim(node, pStr, el.board);
        },

        // Already documented in JXG.AbstractRenderer
        updateRectPrim: function (node, x, y, w, h) {
            node.style.left = Math.floor(x) + 'px';
            node.style.top = Math.floor(y) + 'px';

            if (w >= 0) {
                node.style.width = w + 'px';
            }

            if (h >= 0) {
                node.style.height = h + 'px';
            }
        },

        /* **************************
         *  Set Attributes
         * **************************/

        // Already documented in JXG.AbstractRenderer
        setPropertyPrim: function (node, key, val) {
            var keyVml = '',
                v;

            switch (key) {
            case 'stroke':
                keyVml = 'strokecolor';
                break;
            case 'stroke-width':
                keyVml = 'strokeweight';
                break;
            case 'stroke-dasharray':
                keyVml = 'dashstyle';
                break;
            }

            if (keyVml !== '') {
                v = Type.evaluate(val);
                this._setAttr(node, keyVml, v);
            }
        },

        // Already documented in JXG.AbstractRenderer
        display: function(el, val) {
            if (el && el.rendNode) {
                el.visPropOld.visible = val;
                if (val) {
                    el.rendNode.style.visibility = "inherit";
                } else {
                    el.rendNode.style.visibility = "hidden";
                }
            }
        },

        // Already documented in JXG.AbstractRenderer
        show: function (el) {
            JXG.deprecated('Board.renderer.show()', 'Board.renderer.display()');

            if (el && el.rendNode) {
                el.rendNode.style.visibility = "inherit";
            }
        },

        // Already documented in JXG.AbstractRenderer
        hide: function (el) {
            JXG.deprecated('Board.renderer.hide()', 'Board.renderer.display()');

            if (el && el.rendNode) {
                el.rendNode.style.visibility = "hidden";
            }
        },

        // Already documented in JXG.AbstractRenderer
        setDashStyle: function (el, visProp) {
            var node;
            if (visProp.dash >= 0) {
                node = el.rendNodeStroke;
                this._setAttr(node, 'dashstyle', this.dashArray[visProp.dash]);
            }
        },

        // Already documented in JXG.AbstractRenderer
        setGradient: function (el) {
            var nodeFill = el.rendNodeFill,
                ev_g = Type.evaluate(el.visProp.gradient);

            if (ev_g === 'linear') {
                this._setAttr(nodeFill, 'type', 'gradient');
                this._setAttr(nodeFill, 'color2', Type.evaluate(el.visProp.gradientsecondcolor));
                this._setAttr(nodeFill, 'opacity2', Type.evaluate(el.visProp.gradientsecondopacity));
                this._setAttr(nodeFill, 'angle', Type.evaluate(el.visProp.gradientangle));
            } else if (ev_g === 'radial') {
                this._setAttr(nodeFill, 'type', 'gradientradial');
                this._setAttr(nodeFill, 'color2', Type.evaluate(el.visProp.gradientsecondcolor));
                this._setAttr(nodeFill, 'opacity2', Type.evaluate(el.visProp.gradientsecondopacity));
                this._setAttr(nodeFill, 'focusposition', Type.evaluate(el.visProp.gradientpositionx) * 100 + '%,' +
                            Type.evaluate(el.visProp.gradientpositiony) * 100 + '%');
                this._setAttr(nodeFill, 'focussize', '0,0');
            } else {
                this._setAttr(nodeFill, 'type', 'solid');
            }
        },

        // Already documented in JXG.AbstractRenderer
        setObjectFillColor: function (el, color, opacity) {
            var rgba = Type.evaluate(color), c, rgbo,
                o = Type.evaluate(opacity), oo,
                node = el.rendNode,
                t;

            o = (o > 0) ? o : 0;

            if (el.visPropOld.fillcolor === rgba && el.visPropOld.fillopacity === o) {
                return;
            }

            if (Type.exists(rgba) && rgba !== false) {
                // RGB, not RGBA
                if (rgba.length !== 9) {
                    c = rgba;
                    oo = o;
                // True RGBA, not RGB
                } else {
                    rgbo = Color.rgba2rgbo(rgba);
                    c = rgbo[0];
                    oo = o * rgbo[1];
                }
                if (c === 'none' || c === false) {
                    this._setAttr(el.rendNode, 'filled', 'false');
                } else {
                    this._setAttr(el.rendNode, 'filled', 'true');
                    this._setAttr(el.rendNode, 'fillcolor', c);

                    if (Type.exists(oo) && el.rendNodeFill) {
                        this._setAttr(el.rendNodeFill, 'opacity', (oo * 100) + '%');
                    }
                }
                if (el.type === Const.OBJECT_TYPE_IMAGE) {
                    /*
                    t = el.rendNode.style.filter.toString();
                    if (t.match(/alpha/)) {
                        el.rendNode.style.filter = t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + (oo * 100) + ')');
                    } else {
                        el.rendNode.style.filter += ' alpha(opacity = ' + (oo * 100) + ')';
                    }
                    */
                    if (node.filters.length > 1) {
                        // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]?
                        // Setting axes:true shows text labels!
                        node.filters.item(1).opacity = Math.round(oo * 100); // Why does setObjectFillColor not use Math.round?
                        node.filters.item(1).enabled = true;
                    }
                }
            }
            el.visPropOld.fillcolor = rgba;
            el.visPropOld.fillopacity = o;
        },

        // Already documented in JXG.AbstractRenderer
        setObjectStrokeColor: function (el, color, opacity) {
            var rgba = Type.evaluate(color), c, rgbo, t,
                o = Type.evaluate(opacity), oo,
                node = el.rendNode, nodeStroke;

            o = (o > 0) ? o : 0;

            if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
                return;
            }

            // this looks like it could be merged with parts of VMLRenderer.setObjectFillColor

            if (Type.exists(rgba) && rgba !== false) {
                // RGB, not RGBA
                if (rgba.length !== 9) {
                    c = rgba;
                    oo = o;
                // True RGBA, not RGB
                } else {
                    rgbo = color.rgba2rgbo(rgba);
                    c = rgbo[0];
                    oo = o * rgbo[1];
                }
                if (el.elementClass === Const.OBJECT_CLASS_TEXT) {
                    //node.style.filter = ' alpha(opacity = ' + oo + ')';
                    /*
                    t = node.style.filter.toString();
                    if (t.match(/alpha/)) {
                        node.style.filter =
                        t.replace(/alpha\(opacity *= *[0-9\.]+\)/, 'alpha(opacity = ' + oo + ')');
                    } else {
                        node.style.filter += ' alpha(opacity = ' + oo + ')';
                    }
                    */
                    if (node.filters.length > 1) {
                        // Why am I sometimes seeing node.filters.length==0 here when I move the pointer around near [0,0]?
                        // Setting axes:true shows text labels!
                        node.filters.item(1).opacity = Math.round(oo * 100);
                        node.filters.item(1).enabled = true;
                    }

                    node.style.color = c;
                } else {
                    if (c !== false) {
                        this._setAttr(node, 'stroked', 'true');
                        this._setAttr(node, 'strokecolor', c);
                    }

                    nodeStroke = el.rendNodeStroke;
                    if (Type.exists(oo) && el.type !== Const.OBJECT_TYPE_IMAGE) {
                        this._setAttr(nodeStroke, 'opacity', (oo * 100) + '%');
                    }
                }
            }
            el.visPropOld.strokecolor = rgba;
            el.visPropOld.strokeopacity = o;
        },

        // Already documented in JXG.AbstractRenderer
        setObjectStrokeWidth: function (el, width) {
            var w = Type.evaluate(width),
                node;

            if (isNaN(w) || el.visPropOld.strokewidth === w) {
                return;
            }

            node = el.rendNode;
            this.setPropertyPrim(node, 'stroked', 'true');

            if (Type.exists(w)) {

                this.setPropertyPrim(node, 'stroke-width', w);
                if (w === 0 && Type.exists(el.rendNodeStroke)) {
                    this._setAttr(node, 'stroked', 'false');
                }
            }

            el.visPropOld.strokewidth = w;

        },

        // Already documented in JXG.AbstractRenderer
        setShadow: function (el) {
            var nodeShadow = el.rendNodeShadow,
                ev_s = Type.evaluate(el.visProp.shadow);

            if (!nodeShadow || el.visPropOld.shadow === ev_s) {
                return;
            }

            if (ev_s) {
                this._setAttr(nodeShadow, 'On', 'True');
                this._setAttr(nodeShadow, 'Offset', '3pt,3pt');
                this._setAttr(nodeShadow, 'Opacity', '60%');
                this._setAttr(nodeShadow, 'Color', '#aaaaaa');
            } else {
                this._setAttr(nodeShadow, 'On', 'False');
            }

            el.visPropOld.shadow = ev_s;
        },

        /* **************************
         * renderer control
         * **************************/

        // Already documented in JXG.AbstractRenderer
        suspendRedraw: function () {
            this.container.style.display = 'none';
        },

        // Already documented in JXG.AbstractRenderer
        unsuspendRedraw: function () {
            this.container.style.display = '';
        }
    });

    return JXG.VMLRenderer;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, AMprocessNode: true, document: true, Image: true, module: true, require: true */
/*jslint nomen: true, plusplus: true, newcap:true*/

/* depends:
 jxg
 renderer/abstract
 base/constants
 utils/env
 utils/type
 utils/uuid
 utils/color
 base/coords
 math/math
 math/geometry
 math/numerics
*/

define('renderer/canvas',[
    'jxg', 'renderer/abstract', 'base/constants', 'utils/env', 'utils/type', 'utils/uuid', 'utils/color',
    'base/coords', 'math/math', 'math/geometry', 'math/numerics'
], function (JXG, AbstractRenderer, Const, Env, Type, UUID, Color, Coords, Mat, Geometry, Numerics) {

    "use strict";

    /**
     * Uses HTML Canvas to implement the rendering methods defined in {@link JXG.AbstractRenderer}.
     * 
     * @class JXG.CanvasRenderer
     * @augments JXG.AbstractRenderer
     * @param {Node} container Reference to a DOM node containing the board.
     * @param {Object} dim The dimensions of the board
     * @param {Number} dim.width
     * @param {Number} dim.height
     * @see JXG.AbstractRenderer
     */
    JXG.CanvasRenderer = function (container, dim) {
        this.type = 'canvas';

        this.canvasRoot = null;
        this.suspendHandle = null;
        this.canvasId = UUID.genUUID();

        this.canvasNamespace = null;

        if (Env.isBrowser) {
            this.container = container;
            this.container.style.MozUserSelect = 'none';
            this.container.style.userSelect = 'none';

            this.container.style.overflow = 'hidden';
            if (this.container.style.position === '') {
                this.container.style.position = 'relative';
            }

            this.container.innerHTML = ['<canvas id="', this.canvasId,
                '" width="', dim.width,
                'px" height="', dim.height,
                'px"><', '/canvas>'].join('');
            this.canvasRoot = this.container.ownerDocument.getElementById(this.canvasId);
            this.canvasRoot.style.display = 'block';
            this.context = this.canvasRoot.getContext('2d');

        } else if (Env.isNode()) {
            try {
                this.canvasId = (typeof module === 'object' ? module.require('canvas') : require('canvas'));
                this.canvasRoot = new this.canvasId(500, 500);
                this.context = this.canvasRoot.getContext('2d');
            } catch (err) {
                console.log("Warning: 'canvas' not found. You might need to call 'npm install canvas'");
            }
        }

        this.dashArray = [[2, 2], [5, 5], [10, 10], [20, 20], [20, 10, 10, 10], [20, 5, 10, 5]];
    };

    JXG.CanvasRenderer.prototype = new AbstractRenderer();

    JXG.extend(JXG.CanvasRenderer.prototype, /** @lends JXG.CanvasRenderer.prototype */ {

        /* **************************
         *   private methods only used
         *   in this renderer. Should
         *   not be called from outside.
         * **************************/

        /**
         * Draws a filled polygon.
         * @param {Array} shape A matrix presented by a two dimensional array of numbers.
         * @see JXG.AbstractRenderer#drawArrows
         * @private
         */
        _drawPolygon: function (shape, degree, doFill) {
            var i, len = shape.length,
                context = this.context;

            if (len > 0) {
                if (doFill) {
                    context.lineWidth = 0;
                }
                context.beginPath();
                context.moveTo(shape[0][0], shape[0][1]);
                if (degree === 1) {
                    for (i = 1; i < len; i++) {
                        context.lineTo(shape[i][0], shape[i][1]);
                    }
                } else {
                    for (i = 1; i < len; i += 3) {
                        context.bezierCurveTo(shape[i][0], shape[i][1], shape[i + 1][0], shape[i + 1][1], shape[i + 2][0], shape[i + 2][1]);
                    }
                }
                if (doFill) {
                    context.lineTo(shape[0][0], shape[0][1]);
                    context.closePath();
                    context.fill();
                } else {
                    context.stroke();
                }
            }
        },

        /**
         * Sets the fill color and fills an area.
         * @param {JXG.GeometryElement} el An arbitrary JSXGraph element, preferably one with an area.
         * @private
         */
        _fill: function (el) {
            var context = this.context;

            context.save();
            if (this._setColor(el, 'fill')) {
                context.fill();
            }
            context.restore();
        },

        /**
         * Rotates a point around <tt>(0, 0)</tt> by a given angle.
         * @param {Number} angle An angle, given in rad.
         * @param {Number} x X coordinate of the point.
         * @param {Number} y Y coordinate of the point.
         * @returns {Array} An array containing the x and y coordinate of the rotated point.
         * @private
         */
        _rotatePoint: function (angle, x, y) {
            return [
                (x * Math.cos(angle)) - (y * Math.sin(angle)),
                (x * Math.sin(angle)) + (y * Math.cos(angle))
            ];
        },

        /**
         * Rotates an array of points around <tt>(0, 0)</tt>.
         * @param {Array} shape An array of array of point coordinates.
         * @param {Number} angle The angle in rad the points are rotated by.
         * @returns {Array} Array of array of two dimensional point coordinates.
         * @private
         */
        _rotateShape: function (shape, angle) {
            var i, rv = [], len = shape.length;

            if (len <= 0) {
                return shape;
            }

            for (i = 0; i < len; i++) {
                rv.push(this._rotatePoint(angle, shape[i][0], shape[i][1]));
            }

            return rv;
        },

        /**
         * Set the gradient angle for linear color gradients.
         *
         * @private
         * @param {JXG.GeometryElement} node An arbitrary JSXGraph element, preferably one with an area.
         * @param {Number} radians angle value in radians. 0 is horizontal from left to right, Pi/4 is vertical from top to bottom.
         */
        updateGradientAngle: function(el, radians) {
            // Angles:
            // 0: ->
            // 90: down
            // 180: <-
            // 90: up
            var f = 1.0,
                co = Math.cos(-radians),
                si = Math.sin(-radians),
                bb = el.getBoundingBox(),
                c1, c2, x1, x2, y1, y2, x1s, x2s, y1s, y2s, dx, dy;

            if (Math.abs(co) > Math.abs(si)) {
                f /= Math.abs(co);
            } else {
                f /= Math.abs(si);
            }
            if (co >= 0) {
                x1 = 0;
                x2 = co * f;
            } else {
                x1 = -co * f;
                x2 = 0;
            }
            if (si >= 0) {
                y1 = 0;
                y2 = si * f;
            } else {
                y1 = -si * f;
                y2 = 0;
            }

            c1 = new Coords(Const.COORDS_BY_USER, [bb[0], bb[1]], el.board);
            c2 = new Coords(Const.COORDS_BY_USER, [bb[2], bb[3]], el.board);
            dx = c2.scrCoords[1] - c1.scrCoords[1];
            dy = c2.scrCoords[2] - c1.scrCoords[2];
            x1s = c1.scrCoords[1] + dx * x1;
            y1s = c1.scrCoords[2] + dy * y1;
            x2s = c1.scrCoords[1] + dx * x2;
            y2s = c1.scrCoords[2] + dy * y2;

            return this.context.createLinearGradient(x1s, y1s, x2s, y2s);
        },

        /**
         * Set circles for radial color gradients.
         *
         * @private
         * @param {SVGnode} node SVG gradient node
         * @param {Number} cx Canvas value x1 (but value between 0 and 1)
         * @param {Number} cy  Canvas value y1 (but value between 0 and 1)
         * @param {Number} r  Canvas value r1 (but value between 0 and 1)
         * @param {Number} fx  Canvas value x0 (but value between 0 and 1)
         * @param {Number} fy  Canvas value x1 (but value between 0 and 1)
         * @param {Number} fr  Canvas value r0 (but value between 0 and 1)
         */
        updateGradientCircle: function(el, cx, cy, r, fx, fy, fr) {
            var bb = el.getBoundingBox(),
                c1, c2, cxs, cys, rs, fxs, fys, frs, dx, dy;

            c1 = new Coords(Const.COORDS_BY_USER, [bb[0], bb[1]], el.board);
            c2 = new Coords(Const.COORDS_BY_USER, [bb[2], bb[3]], el.board);
            dx = c2.scrCoords[1] - c1.scrCoords[1];
            dy = c1.scrCoords[2] - c2.scrCoords[2];

            cxs = c1.scrCoords[1] + dx * cx;
            cys = c2.scrCoords[2] + dy * cy;
            fxs = c1.scrCoords[1] + dx * fx;
            fys = c2.scrCoords[2] + dy * fy;
            rs = r * (dx + dy) * 0.5;
            frs = fr * (dx + dy) * 0.5;

            return this.context.createRadialGradient(fxs, fys, frs, cxs, cys, rs);
        },

        // documented in JXG.AbstractRenderer
        updateGradient: function(el) {
            var col, op,
                ev_g = Type.evaluate(el.visProp.gradient),
                gradient;

            op = Type.evaluate(el.visProp.fillopacity);
            op = (op > 0) ? op : 0;
            col = Type.evaluate(el.visProp.fillcolor);

            if (ev_g === 'linear') {
                gradient = this.updateGradientAngle(el, Type.evaluate(el.visProp.gradientangle));
            } else if (ev_g === 'radial') {
                gradient = this.updateGradientCircle(el,
                    Type.evaluate(el.visProp.gradientcx),
                    Type.evaluate(el.visProp.gradientcy),
                    Type.evaluate(el.visProp.gradientr),
                    Type.evaluate(el.visProp.gradientfx),
                    Type.evaluate(el.visProp.gradientfy),
                    Type.evaluate(el.visProp.gradientfr)
                );
            }
            gradient.addColorStop(Type.evaluate(el.visProp.gradientstartoffset), col);
            gradient.addColorStop(Type.evaluate(el.visProp.gradientendoffset),
                                  Type.evaluate(el.visProp.gradientsecondcolor));
        return gradient;
        },

        /**
         * Sets color and opacity for filling and stroking.
         * type is the attribute from visProp and targetType the context[targetTypeStyle].
         * This is necessary, because the fill style of a text is set by the stroke attributes of the text element.
         * @param {JXG.GeometryElement} el Any JSXGraph element.
         * @param {String} [type='stroke'] Either <em>fill</em> or <em>stroke</em>.
         * @param {String} [targetType=type] (optional) Either <em>fill</em> or <em>stroke</em>.
         * @returns {Boolean} If the color could be set, <tt>true</tt> is returned.
         * @private
         */
        _setColor: function (el, type, targetType) {
            var hasColor = true,
                ev = el.visProp, hl, sw,
                rgba, rgbo, c, o, oo,
                grad;

            type = type || 'stroke';
            targetType = targetType || type;

            hl = this._getHighlighted(el);

            grad = Type.evaluate(el.visProp.gradient);
            if (grad === 'linear' || grad === 'radial') {
                // TODO: opacity
                this.context[targetType + 'Style'] = this.updateGradient(el);
                return hasColor;
            }

            // type is equal to 'fill' or 'stroke'
            rgba = Type.evaluate(ev[hl + type + 'color']);
            if (rgba !== 'none' && rgba !== false) {
                o = Type.evaluate(ev[hl + type + 'opacity']);
                o = (o > 0) ? o : 0;

                // RGB, not RGBA
                if (rgba.length !== 9) {
                    c = rgba;
                    oo = o;
                // True RGBA, not RGB
                } else {
                    rgbo = Color.rgba2rgbo(rgba);
                    c = rgbo[0];
                    oo = o * rgbo[1];
                }
                this.context.globalAlpha = oo;

                this.context[targetType + 'Style'] = c;

            } else {
                hasColor = false;
            }

            sw = parseFloat(Type.evaluate(ev[hl + 'strokewidth']));
            if (type === 'stroke' && !isNaN(sw)) {
                if (sw === 0) {
                    this.context.globalAlpha = 0;
                } else {
                    this.context.lineWidth = sw;
                }
            }

            if (type === 'stroke' && ev.linecap !== undefined && ev.linecap !== '') {
                this.context.lineCap = ev.linecap;
            }

            return hasColor;
        },

        /**
         * Sets color and opacity for drawing paths and lines and draws the paths and lines.
         * @param {JXG.GeometryElement} el An JSXGraph element with a stroke.
         * @private
         */
        _stroke: function (el) {
            var context = this.context,
                ev_dash = Type.evaluate(el.visProp.dash);

            context.save();

            if (ev_dash > 0) {
                if (context.setLineDash) {
                    context.setLineDash(this.dashArray[ev_dash]);
                }
            } else {
                this.context.lineDashArray = [];
            }

            if (this._setColor(el, 'stroke')) {
                context.stroke();
            }

            context.restore();
        },

        /**
         * Translates a set of points.
         * @param {Array} shape An array of point coordinates.
         * @param {Number} x Translation in X direction.
         * @param {Number} y Translation in Y direction.
         * @returns {Array} An array of translated point coordinates.
         * @private
         */
        _translateShape: function (shape, x, y) {
            var i, rv = [], len = shape.length;

            if (len <= 0) {
                return shape;
            }

            for (i = 0; i < len; i++) {
                rv.push([ shape[i][0] + x, shape[i][1] + y ]);
            }

            return rv;
        },

        /* ******************************** *
         *    Point drawing and updating    *
         * ******************************** */

        // documented in AbstractRenderer
        drawPoint: function (el) {
            var f = Type.evaluate(el.visProp.face),
                size = Type.evaluate(el.visProp.size),
                scr = el.coords.scrCoords,
                sqrt32 = size * Math.sqrt(3) * 0.5,
                s05 = size * 0.5,
                stroke05 = parseFloat(Type.evaluate(el.visProp.strokewidth)) / 2.0,
                context = this.context;

            if (!el.visPropCalc.visible) {
                return;
            }

            switch (f) {
            case 'cross':  // x
            case 'x':
                context.beginPath();
                context.moveTo(scr[1] - size, scr[2] - size);
                context.lineTo(scr[1] + size, scr[2] + size);
                context.moveTo(scr[1] + size, scr[2] - size);
                context.lineTo(scr[1] - size, scr[2] + size);
                context.lineCap = 'round';
                context.lineJoin = 'round';
                context.closePath();
                this._stroke(el);
                break;
            case 'circle': // dot
            case 'o':
                context.beginPath();
                context.arc(scr[1], scr[2], size + 1 + stroke05, 0, 2 * Math.PI, false);
                context.closePath();
                this._fill(el);
                this._stroke(el);
                break;
            case 'square':  // rectangle
            case '[]':
                if (size <= 0) {
                    break;
                }

                context.save();
                if (this._setColor(el, 'stroke', 'fill')) {
                    context.fillRect(scr[1] - size - stroke05, scr[2] - size - stroke05, size * 2 + 3 * stroke05, size * 2 + 3 * stroke05);
                }
                context.restore();
                context.save();
                this._setColor(el, 'fill');
                context.fillRect(scr[1] - size + stroke05, scr[2] - size + stroke05, size * 2 - stroke05, size * 2 - stroke05);
                context.restore();
                break;
            case 'plus':  // +
            case '+':
                context.beginPath();
                context.moveTo(scr[1] - size, scr[2]);
                context.lineTo(scr[1] + size, scr[2]);
                context.moveTo(scr[1], scr[2] - size);
                context.lineTo(scr[1], scr[2] + size);
                context.lineCap = 'round';
                context.lineJoin = 'round';
                context.closePath();
                this._stroke(el);
                break;
            case 'diamond':   // <>
            case '<>':
                context.beginPath();
                context.moveTo(scr[1] - size, scr[2]);
                context.lineTo(scr[1], scr[2] + size);
                context.lineTo(scr[1] + size, scr[2]);
                context.lineTo(scr[1], scr[2] - size);
                context.closePath();
                this._fill(el);
                this._stroke(el);
                break;
            case 'triangleup':
            case 'a':
            case '^':
                context.beginPath();
                context.moveTo(scr[1], scr[2] - size);
                context.lineTo(scr[1] - sqrt32, scr[2] + s05);
                context.lineTo(scr[1] + sqrt32, scr[2] + s05);
                context.closePath();
                this._fill(el);
                this._stroke(el);
                break;
            case 'triangledown':
            case 'v':
                context.beginPath();
                context.moveTo(scr[1], scr[2] + size);
                context.lineTo(scr[1] - sqrt32, scr[2] - s05);
                context.lineTo(scr[1] + sqrt32, scr[2] - s05);
                context.closePath();
                this._fill(el);
                this._stroke(el);
                break;
            case 'triangleleft':
            case '<':
                context.beginPath();
                context.moveTo(scr[1] - size, scr[2]);
                context.lineTo(scr[1] + s05, scr[2] - sqrt32);
                context.lineTo(scr[1] + s05, scr[2] + sqrt32);
                context.closePath();
                this._fill(el);
                this._stroke(el);
                break;
            case 'triangleright':
            case '>':
                context.beginPath();
                context.moveTo(scr[1] + size, scr[2]);
                context.lineTo(scr[1] - s05, scr[2] - sqrt32);
                context.lineTo(scr[1] - s05, scr[2] + sqrt32);
                context.closePath();
                this._fill(el);
                this._stroke(el);
                break;
            }
        },

        // documented in AbstractRenderer
        updatePoint: function (el) {
            this.drawPoint(el);
        },

        /* ******************************** *
         *           Lines                  *
         * ******************************** */

        /**
         * Draws arrows of an element (usually a line) in canvas renderer.
         * @param {JXG.GeometryElement} el Line to be drawn.
         * @param {Array} scr1 Screen coordinates of the start position of the line or curve.
         * @param {Array} scr2 Screen coordinates of the end position of the line or curve.
         * @param {String} hl String which carries information if the element is highlighted. Used for getting the correct attribute.
         * @private
         */
        drawArrows: function (el, scr1, scr2, hl, a) {
             var x1, y1, x2, y2,
                 w0, w,
                 arrowHead,
                 arrowTail,
                 context = this.context,
                 size = 6,
                 type = 1,
                 type_fa, type_la,
                 degree_fa = 1,
                 degree_la = 1,
                 doFill,
                 i, len,
                 d1x, d1y, d2x, d2y, last,
                 ang1, ang2,
                 ev_fa = a.evFirst,
                 ev_la = a.evLast;

            if (Type.evaluate(el.visProp.strokecolor) !== 'none' &&
                     (ev_fa || ev_la)) {

                if (el.elementClass === Const.OBJECT_CLASS_LINE) {
                    x1 = scr1.scrCoords[1];
                    y1 = scr1.scrCoords[2];
                    x2 = scr2.scrCoords[1];
                    y2 = scr2.scrCoords[2];
                    ang1 = ang2 = Math.atan2(y2 - y1, x2 - x1);
                } else {
                    x1 = el.points[0].scrCoords[1];
                    y1 = el.points[0].scrCoords[2];

                    last = el.points.length - 1;
                    if (last < 1) {
                        // No arrows for curves consisting of 1 point
                        return;
                    }
                    x2 = el.points[el.points.length - 1].scrCoords[1];
                    y2 = el.points[el.points.length - 1].scrCoords[2];

                    d1x = el.points[1].scrCoords[1] - el.points[0].scrCoords[1];
                    d1y = el.points[1].scrCoords[2] - el.points[0].scrCoords[2];
                    d2x = el.points[last].scrCoords[1] - el.points[last - 1].scrCoords[1];
                    d2y = el.points[last].scrCoords[2] - el.points[last - 1].scrCoords[2];
                    if (ev_fa) {
                        ang1 = Math.atan2(d1y, d1x);
                    }
                    if (ev_la) {
                        ang2 = Math.atan2(d2y, d2x);
                    }
                }

                w0 = Type.evaluate(el.visProp[hl + 'strokewidth']);

                if (ev_fa) {
                    size = a.sizeFirst;

                    w = w0 * size;

                    type = a.typeFirst;
                    type_fa = type;

                    if (type === 2) {
                        arrowTail = [
                                 [ w,      -w * 0.5],
                                 [ 0.0,         0.0],
                                 [ w,       w * 0.5],
                                 [ w * 0.5,     0.0],
                             ];
                    } else if (type === 3) {
                        arrowTail = [
                                 [ w / 3.0,   -w * 0.5],
                                 [ 0.0,       -w * 0.5],
                                 [ 0.0,        w * 0.5],
                                 [ w / 3.0,    w * 0.5]
                             ];
                    } else if (type === 4) {
                        w /= 10;
                        degree_fa = 3;
                        arrowTail = [
                            [10.00, 3.31],
                            [6.47, 3.84],
                            [2.87, 4.50],
                            [0.00, 6.63],
                            [0.67, 5.52],
                            [1.33, 4.42],
                            [2.00, 3.31],
                            [1.33, 2.21],
                            [0.67, 1.10],
                            [0.00, 0.00],
                            [2.87, 2.13],
                            [6.47, 2.79],
                            [10.00, 3.31]
                        ];
                        len = arrowTail.length;
                        for (i = 0; i < len; i++) {
                            arrowTail[i][0] *= -w;
                            arrowTail[i][1] *= w;
                            arrowTail[i][0] += 10 * w;
                            arrowTail[i][1] -= 3.31 * w;
                        }
                    } else if (type === 5) {
                        w /= 10;
                        degree_fa = 3;
                        arrowTail = [
                            [10.00,3.28],
                            [6.61,4.19],
                            [3.19,5.07],
                            [0.00,6.55],
                            [0.62,5.56],
                            [1.00,4.44],
                            [1.00,3.28],
                            [1.00,2.11],
                            [0.62,0.99],
                            [0.00,0.00],
                            [3.19,1.49],
                            [6.61,2.37],
                            [10.00,3.28]
                        ];
                        len = arrowTail.length;
                        for (i = 0; i < len; i++) {
                            arrowTail[i][0] *= -w;
                            arrowTail[i][1] *= w;
                            arrowTail[i][0] += 10 * w;
                            arrowTail[i][1] -= 3.28 * w;
                        }
                    } else if (type === 6) {
                        w /= 10;
                        degree_fa = 3;
                        arrowTail = [
                            [10.00,2.84],
                            [6.61,3.59],
                            [3.21,4.35],
                            [0.00,5.68],
                            [0.33,4.73],
                            [0.67,3.78],
                            [1.00,2.84],
                            [0.67,1.89],
                            [0.33,0.95],
                            [0.00,0.00],
                            [3.21,1.33],
                            [6.61,2.09],
                            [10.00,2.84]
                        ];
                        len = arrowTail.length;
                        for (i = 0; i < len; i++) {
                            arrowTail[i][0] *= -w;
                            arrowTail[i][1] *= w;
                            arrowTail[i][0] += 10 * w;
                            arrowTail[i][1] -= 2.84 * w;
                        }
                    } else if (type === 7) {
                        w = w0;
                        degree_fa = 3;
                        arrowTail = [
                            [0.00,10.39],
                            [2.01,6.92],
                            [5.96,5.20],
                            [10.00,5.20],
                            [5.96,5.20],
                            [2.01,3.47],
                            [0.00,0.00]
                        ];
                        len = arrowTail.length;
                        for (i = 0; i < len; i++) {
                            arrowTail[i][0] *= -w;
                            arrowTail[i][1] *= w;
                            arrowTail[i][0] += 10 * w;
                            arrowTail[i][1] -= 5.20 * w;
                        }
                    } else {
                        arrowTail = [
                             [ w,   -w * 0.5],
                             [ 0.0,      0.0],
                             [ w,    w * 0.5]
                        ];
                    }
                }

                if (ev_la) {
                    size = a.sizeLast;
                    w = w0 * size;

                    type = a.typeLast;
                    type_la = type;
                    if (type === 2) {
                        arrowHead = [
                             [ -w, -w * 0.5],
                             [ 0.0,     0.0],
                             [ -w,  w * 0.5],
                             [ -w * 0.5, 0.0]
                        ];
                    } else if (type === 3) {
                        arrowHead = [
                                 [-w / 3.0,   -w * 0.5],
                                 [ 0.0,       -w * 0.5],
                                 [ 0.0,        w * 0.5],
                                 [-w / 3.0,    w * 0.5]
                             ];
                    } else if (type === 4) {
                        w /= 10;
                        degree_la = 3;
                        arrowHead = [
                            [10.00, 3.31],
                            [6.47, 3.84],
                            [2.87, 4.50],
                            [0.00, 6.63],
                            [0.67, 5.52],
                            [1.33, 4.42],
                            [2.00, 3.31],
                            [1.33, 2.21],
                            [0.67, 1.10],
                            [0.00, 0.00],
                            [2.87, 2.13],
                            [6.47, 2.79],
                            [10.00, 3.31]
                        ];
                        len = arrowHead.length;
                        for (i = 0; i < len; i++) {
                            arrowHead[i][0] *= w;
                            arrowHead[i][1] *= w;
                            arrowHead[i][0] -= 10 * w;
                            arrowHead[i][1] -= 3.31 * w;

                        }
                    } else if (type === 5) {
                        w /= 10;
                        degree_la = 3;
                        arrowHead = [
                            [10.00,3.28],
                            [6.61,4.19],
                            [3.19,5.07],
                            [0.00,6.55],
                            [0.62,5.56],
                            [1.00,4.44],
                            [1.00,3.28],
                            [1.00,2.11],
                            [0.62,0.99],
                            [0.00,0.00],
                            [3.19,1.49],
                            [6.61,2.37],
                            [10.00,3.28]
                        ];
                        len = arrowHead.length;
                        for (i = 0; i < len; i++) {
                            arrowHead[i][0] *= w;
                            arrowHead[i][1] *= w;
                            arrowHead[i][0] -= 10 * w;
                            arrowHead[i][1] -= 3.28 * w;

                        }
                    } else if (type === 6) {
                        w /= 10;
                        degree_la = 3;
                        arrowHead = [
                            [10.00,2.84],
                            [6.61,3.59],
                            [3.21,4.35],
                            [0.00,5.68],
                            [0.33,4.73],
                            [0.67,3.78],
                            [1.00,2.84],
                            [0.67,1.89],
                            [0.33,0.95],
                            [0.00,0.00],
                            [3.21,1.33],
                            [6.61,2.09],
                            [10.00,2.84]
                        ];
                        len = arrowHead.length;
                        for (i = 0; i < len; i++) {
                            arrowHead[i][0] *= w;
                            arrowHead[i][1] *= w;
                            arrowHead[i][0] -= 10 * w;
                            arrowHead[i][1] -= 2.84 * w;

                        }

                    } else if (type === 7) {
                        w = w0;
                        degree_la = 3;
                        arrowHead = [
                            [0.00,10.39],
                            [2.01,6.92],
                            [5.96,5.20],
                            [10.00,5.20],
                            [5.96,5.20],
                            [2.01,3.47],
                            [0.00,0.00]
                        ];
                        len = arrowHead.length;
                        for (i = 0; i < len; i++) {
                            arrowHead[i][0] *= w;
                            arrowHead[i][1] *= w;
                            arrowHead[i][0] -= 10 * w;
                            arrowHead[i][1] -= 5.20 * w;

                        }
                    } else {
                        arrowHead = [
                             [ -w, -w * 0.5],
                             [ 0.0,     0.0],
                             [ -w,  w * 0.5]
                         ];
                    }
                }

                context.save();
                if (this._setColor(el, 'stroke', 'fill')) {
                    this._setColor(el, 'stroke');
                    if (ev_fa) {
                        if (type_fa === 7) {
                            doFill = false;
                        } else {
                            doFill = true;
                        }
                        this._drawPolygon(this._translateShape(this._rotateShape(arrowTail, ang1), x1, y1), degree_fa, doFill);
                    }
                    if (ev_la) {
                        if (type_la === 7) {
                            doFill = false;
                        } else {
                            doFill = true;
                        }
                        this._drawPolygon(this._translateShape(this._rotateShape(arrowHead, ang2), x2, y2), degree_la, doFill);
                    }
                }
                context.restore();
            }
        },

        // documented in AbstractRenderer
        drawLine: function (el) {
            var c1_org, c2_org,
                c1 = new Coords(Const.COORDS_BY_USER, el.point1.coords.usrCoords, el.board),
                c2 = new Coords(Const.COORDS_BY_USER, el.point2.coords.usrCoords, el.board),
                margin = null,
                hl, w, arrowData;

            if (!el.visPropCalc.visible) {
                return;
            }

            hl = this._getHighlighted(el);
            w = Type.evaluate(el.visProp[hl + 'strokewidth']);
            arrowData = this.getArrowHeadData(el, w, hl);

            if (arrowData.evFirst || arrowData.evLast) {
                margin = -4;
            }
            Geometry.calcStraight(el, c1, c2, margin);
            this.handleTouchpoints(el, c1, c2, arrowData);

            c1_org = new Coords(Const.COORDS_BY_USER, c1.usrCoords, el.board);
            c2_org = new Coords(Const.COORDS_BY_USER, c2.usrCoords, el.board);

            this.getPositionArrowHead(el, c1, c2, arrowData);

            this.context.beginPath();
            this.context.moveTo(c1.scrCoords[1], c1.scrCoords[2]);
            this.context.lineTo(c2.scrCoords[1], c2.scrCoords[2]);
            this._stroke(el);

            if ((arrowData.evFirst/* && obj.sFirst > 0*/) ||
                (arrowData.evLast/* && obj.sLast > 0*/)) {

                this.drawArrows(el, c1_org, c2_org, hl, arrowData);
            }
        },

        // documented in AbstractRenderer
        updateLine: function (el) {
            this.drawLine(el);
        },

        // documented in AbstractRenderer
        drawTicks: function () {
            // this function is supposed to initialize the svg/vml nodes in the SVG/VMLRenderer.
            // but in canvas there are no such nodes, hence we just do nothing and wait until
            // updateTicks is called.
        },

        // documented in AbstractRenderer
        updateTicks: function (ticks) {
            var i, c, x, y,
                len = ticks.ticks.length,
                len2, j,
                context = this.context;

            context.beginPath();
            for (i = 0; i < len; i++) {
                c = ticks.ticks[i];
                x = c[0];
                y = c[1];

                // context.moveTo(x[0], y[0]);
                // context.lineTo(x[1], y[1]);
                len2 = x.length;
                context.moveTo(x[0], y[0]);
                for (j = 1; j < len2; ++j) {
                    context.lineTo(x[j], y[j]);
                }

            }
            // Labels
            // for (i = 0; i < len; i++) {
            //     c = ticks.ticks[i].scrCoords;
            //     if (ticks.ticks[i].major &&
            //             (ticks.board.needsFullUpdate || ticks.needsRegularUpdate) &&
            //             ticks.labels[i] &&
            //             ticks.labels[i].visPropCalc.visible) {
            //         this.updateText(ticks.labels[i]);
            //     }
            // }
            context.lineCap = 'round';
            this._stroke(ticks);
        },

        /* **************************
         *    Curves
         * **************************/

        // documented in AbstractRenderer
        drawCurve: function (el) {
            var hl, w, arrowData;

            if (Type.evaluate(el.visProp.handdrawing)) {
                this.updatePathStringBezierPrim(el);
            } else {
                this.updatePathStringPrim(el);
            }
            if (el.numberPoints > 1) {
                hl = this._getHighlighted(el);
                w = Type.evaluate(el.visProp[hl + 'strokewidth']);
                arrowData = this.getArrowHeadData(el, w, hl);
                if ((arrowData.evFirst/* && obj.sFirst > 0*/) ||
                    (arrowData.evLast/* && obj.sLast > 0*/)) {
                    this.drawArrows(el, null, null, hl, arrowData);
                }
            }
        },

        // documented in AbstractRenderer
        updateCurve: function (el) {
            this.drawCurve(el);
        },

        /* **************************
         *    Circle related stuff
         * **************************/

        // documented in AbstractRenderer
        drawEllipse: function (el) {
            var m1 = el.center.coords.scrCoords[1],
                m2 = el.center.coords.scrCoords[2],
                sX = el.board.unitX,
                sY = el.board.unitY,
                rX = 2 * el.Radius(),
                rY = 2 * el.Radius(),
                aWidth = rX * sX,
                aHeight = rY * sY,
                aX = m1 - aWidth / 2,
                aY = m2 - aHeight / 2,
                hB = (aWidth / 2) * 0.5522848,
                vB = (aHeight / 2) * 0.5522848,
                eX = aX + aWidth,
                eY = aY + aHeight,
                mX = aX + aWidth / 2,
                mY = aY + aHeight / 2,
                context = this.context;

            if (rX > 0.0 && rY > 0.0 && !isNaN(m1 + m2)) {
                context.beginPath();
                context.moveTo(aX, mY);
                context.bezierCurveTo(aX, mY - vB, mX - hB, aY, mX, aY);
                context.bezierCurveTo(mX + hB, aY, eX, mY - vB, eX, mY);
                context.bezierCurveTo(eX, mY + vB, mX + hB, eY, mX, eY);
                context.bezierCurveTo(mX - hB, eY, aX, mY + vB, aX, mY);
                context.closePath();
                this._fill(el);
                this._stroke(el);
            }
        },

        // documented in AbstractRenderer
        updateEllipse: function (el) {
            return this.drawEllipse(el);
        },

        /* **************************
         *    Polygon
         * **************************/

        // nothing here, using AbstractRenderer implementations

        /* **************************
         *    Text related stuff
         * **************************/

        // Already documented in JXG.AbstractRenderer
        displayCopyright: function (str, fontSize) {
            var context = this.context;

            // this should be called on EVERY update, otherwise it won't be shown after the first update
            context.save();
            context.font = fontSize + 'px Arial';
            context.fillStyle = '#aaa';
            context.lineWidth = 0.5;
            context.fillText(str, 10, 2 + fontSize);
            context.restore();
        },

        // Already documented in JXG.AbstractRenderer
        drawInternalText: function (el) {
            var ev_fs = Type.evaluate(el.visProp.fontsize),
                fontUnit = Type.evaluate(el.visProp.fontunit),
                ev_ax = el.getAnchorX(),
                ev_ay = el.getAnchorY(),
                context = this.context;

            context.save();
            if (this._setColor(el, 'stroke', 'fill') &&
                    !isNaN(el.coords.scrCoords[1] + el.coords.scrCoords[2])) {
                context.font = (ev_fs > 0 ? ev_fs : 0) + fontUnit + ' Arial';

                this.transformImage(el, el.transformations);
                if (ev_ax === 'left') {
                    context.textAlign = 'left';
                } else if (ev_ax === 'right') {
                    context.textAlign = 'right';
                } else if (ev_ax === 'middle') {
                    context.textAlign = 'center';
                }
                if (ev_ay === 'bottom') {
                    context.textBaseline = 'bottom';
                } else if (ev_ay === 'top') {
                    context.textBaseline = 'top';
                } else if (ev_ay === 'middle') {
                    context.textBaseline = 'middle';
                }
                context.fillText(el.plaintext, el.coords.scrCoords[1], el.coords.scrCoords[2]);
            }
            context.restore();
            return null;
        },

        // Already documented in JXG.AbstractRenderer
        updateInternalText: function (el) {
            this.drawInternalText(el);
        },

        // documented in JXG.AbstractRenderer
        // Only necessary for texts
        setObjectStrokeColor: function (el, color, opacity) {
            var rgba = Type.evaluate(color), c, rgbo,
                o = Type.evaluate(opacity), oo,
                node;

            o = (o > 0) ? o : 0;

            if (el.visPropOld.strokecolor === rgba && el.visPropOld.strokeopacity === o) {
                return;
            }

            // Check if this could be merged with _setColor

            if (Type.exists(rgba) && rgba !== false) {
                // RGB, not RGBA
                if (rgba.length !== 9) {
                    c = rgba;
                    oo = o;
                // True RGBA, not RGB
                } else {
                    rgbo = Color.rgba2rgbo(rgba);
                    c = rgbo[0];
                    oo = o * rgbo[1];
                }
                node = el.rendNode;
                if (el.elementClass === Const.OBJECT_CLASS_TEXT && Type.evaluate(el.visProp.display) === 'html') {
                    node.style.color = c;
                    node.style.opacity = oo;
                }
            }

            el.visPropOld.strokecolor = rgba;
            el.visPropOld.strokeopacity = o;
        },

        /* **************************
         *    Image related stuff
         * **************************/

        // Already documented in JXG.AbstractRenderer
        drawImage: function (el) {
            el.rendNode = new Image();
            // Store the file name of the image.
            // Before, this was done in el.rendNode.src
            // But there, the file name is expanded to
            // the full url. This may be different from
            // the url computed in updateImageURL().
            el._src = '';
            this.updateImage(el);
        },

        // Already documented in JXG.AbstractRenderer
        updateImage: function (el) {
            var context = this.context,
                o = Type.evaluate(el.visProp.fillopacity),
                paintImg = Type.bind(function () {
                    el.imgIsLoaded = true;
                    if (el.size[0] <= 0 || el.size[1] <= 0) {
                        return;
                    }
                    context.save();
                    context.globalAlpha = o;
                    // If det(el.transformations)=0, FireFox 3.6. breaks down.
                    // This is tested in transformImage
                    this.transformImage(el, el.transformations);
                    context.drawImage(el.rendNode,
                        el.coords.scrCoords[1],
                        el.coords.scrCoords[2] - el.size[1],
                        el.size[0],
                        el.size[1]);
                    context.restore();
                }, this);

            if (this.updateImageURL(el)) {
                el.rendNode.onload = paintImg;
            } else {
                if (el.imgIsLoaded) {
                    paintImg();
                }
            }
        },

        // Already documented in JXG.AbstractRenderer
        transformImage: function (el, t) {
            var m, len = t.length,
                ctx = this.context;

            if (len > 0) {
                m = this.joinTransforms(el, t);
                if (Math.abs(Numerics.det(m)) >= Mat.eps) {
                    ctx.transform(m[1][1], m[2][1], m[1][2], m[2][2], m[1][0], m[2][0]);
                }
            }
        },

        // Already documented in JXG.AbstractRenderer
        updateImageURL: function (el) {
            var url;

            url = Type.evaluate(el.url);
            if (el._src !== url) {
                el.imgIsLoaded = false;
                el.rendNode.src = url;
                el._src = url;
                return true;
            }

            return false;
        },

        /* **************************
         * Render primitive objects
         * **************************/

        // documented in AbstractRenderer
        remove: function (shape) {
            // sounds odd for a pixel based renderer but we need this for html texts
            if (Type.exists(shape) && Type.exists(shape.parentNode)) {
                shape.parentNode.removeChild(shape);
            }
        },

        // documented in AbstractRenderer
        updatePathStringPrim: function (el) {
            var i, scr, scr1, scr2, len,
                symbm = 'M',
                symbl = 'L',
                symbc = 'C',
                nextSymb = symbm,
                maxSize = 5000.0,
                context = this.context;

            if (el.numberPoints <= 0) {
                return;
            }

            len = Math.min(el.points.length, el.numberPoints);
            context.beginPath();

            if (el.bezierDegree === 1) {
                /*
                if (isNotPlot && el.board.options.curve.RDPsmoothing) {
                    el.points = Numerics.RamerDouglasPeucker(el.points, 0.5);
                }
                */

                for (i = 0; i < len; i++) {
                    scr = el.points[i].scrCoords;

                    if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
                        nextSymb = symbm;
                    } else {
                        // Chrome has problems with values  being too far away.
                        if (scr[1] > maxSize) {
                            scr[1] = maxSize;
                        } else if (scr[1] < -maxSize) {
                            scr[1] = -maxSize;
                        }

                        if (scr[2] > maxSize) {
                            scr[2] = maxSize;
                        } else if (scr[2] < -maxSize) {
                            scr[2] = -maxSize;
                        }

                        if (nextSymb === symbm) {
                            context.moveTo(scr[1], scr[2]);
                        } else {
                            context.lineTo(scr[1], scr[2]);
                        }
                        nextSymb = symbl;
                    }
                }
            } else if (el.bezierDegree === 3) {
                i = 0;
                while (i < len) {
                    scr = el.points[i].scrCoords;
                    if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
                        nextSymb = symbm;
                    } else {
                        if (nextSymb === symbm) {
                            context.moveTo(scr[1], scr[2]);
                        } else {
                            i += 1;
                            scr1 = el.points[i].scrCoords;
                            i += 1;
                            scr2 = el.points[i].scrCoords;
                            context.bezierCurveTo(scr[1], scr[2], scr1[1], scr1[2], scr2[1], scr2[2]);
                        }
                        nextSymb = symbc;
                    }
                    i += 1;
                }
            }
            context.lineCap = 'round';
            this._fill(el);
            this._stroke(el);
        },

        // Already documented in JXG.AbstractRenderer
        updatePathStringBezierPrim: function (el) {
            var i, j, k, scr, lx, ly, len,
                symbm = 'M',
                symbl = 'C',
                nextSymb = symbm,
                maxSize = 5000.0,
                f = Type.evaluate(el.visProp.strokewidth),
                isNoPlot = (Type.evaluate(el.visProp.curvetype) !== 'plot'),
                context = this.context;

            if (el.numberPoints <= 0) {
                return;
            }

            if (isNoPlot && el.board.options.curve.RDPsmoothing) {
                el.points = Numerics.RamerDouglasPeucker(el.points, 0.5);
            }

            len = Math.min(el.points.length, el.numberPoints);
            context.beginPath();

            for (j = 1; j < 3; j++) {
                nextSymb = symbm;
                for (i = 0; i < len; i++) {
                    scr = el.points[i].scrCoords;

                    if (isNaN(scr[1]) || isNaN(scr[2])) {  // PenUp
                        nextSymb = symbm;
                    } else {
                        // Chrome has problems with values being too far away.
                        if (scr[1] > maxSize) {
                            scr[1] = maxSize;
                        } else if (scr[1] < -maxSize) {
                            scr[1] = -maxSize;
                        }

                        if (scr[2] > maxSize) {
                            scr[2] = maxSize;
                        } else if (scr[2] < -maxSize) {
                            scr[2] = -maxSize;
                        }

                        if (nextSymb === symbm) {
                            context.moveTo(scr[1], scr[2]);
                        } else {
                            k = 2 * j;
                            context.bezierCurveTo(
                                (lx + (scr[1] - lx) * 0.333 + f * (k * Math.random() - j)),
                                (ly + (scr[2] - ly) * 0.333 + f * (k * Math.random() - j)),
                                (lx + (scr[1] - lx) * 0.666 + f * (k * Math.random() - j)),
                                (ly + (scr[2] - ly) * 0.666 + f * (k * Math.random() - j)),
                                scr[1],
                                scr[2]
                            );
                        }
                        nextSymb = symbl;
                        lx = scr[1];
                        ly = scr[2];
                    }
                }
            }
            context.lineCap = 'round';
            this._fill(el);
            this._stroke(el);
        },

        // documented in AbstractRenderer
        updatePolygonPrim: function (node, el) {
            var scrCoords, i, j,
                len = el.vertices.length,
                context = this.context,
                isReal = true;

            if (len <= 0 || !el.visPropCalc.visible) {
                return;
            }
            if (el.elType === 'polygonalchain') {
                len++;
            }

            context.beginPath();
            i = 0;
            while (!el.vertices[i].isReal && i < len - 1) {
                i++;
                isReal = false;
            }
            scrCoords = el.vertices[i].coords.scrCoords;
            context.moveTo(scrCoords[1], scrCoords[2]);

            for (j = i; j < len - 1; j++) {
                if (!el.vertices[j].isReal) {
                    isReal = false;
                }
                scrCoords = el.vertices[j].coords.scrCoords;
                context.lineTo(scrCoords[1], scrCoords[2]);
            }
            context.closePath();

            if (isReal) {
                this._fill(el);    // The edges of a polygon are displayed separately (as segments).
            }
        },

        // **************************  Set Attributes *************************

        // Already documented in JXG.AbstractRenderer
        display: function(el, val) {
             if (el && el.rendNode) {
                 el.visPropOld.visible = val;
                 if (val) {
                     el.rendNode.style.visibility = "inherit";
                 } else {
                     el.rendNode.style.visibility = "hidden";
                 }
             }
         },

        // documented in AbstractRenderer
        show: function (el) {
            JXG.deprecated('Board.renderer.show()', 'Board.renderer.display()');

            if (Type.exists(el.rendNode)) {
                el.rendNode.style.visibility = "inherit";
            }
        },

        // documented in AbstractRenderer
        hide: function (el) {
            JXG.deprecated('Board.renderer.hide()', 'Board.renderer.display()');

            if (Type.exists(el.rendNode)) {
                el.rendNode.style.visibility = "hidden";
            }
        },

        // documented in AbstractRenderer
        setGradient: function (el) {
            var // col,
                op;

            op = Type.evaluate(el.visProp.fillopacity);
            op = (op > 0) ? op : 0;

            // col = Type.evaluate(el.visProp.fillcolor);
        },

        // documented in AbstractRenderer
        setShadow: function (el) {
            if (el.visPropOld.shadow === el.visProp.shadow) {
                return;
            }

            // not implemented yet
            // we simply have to redraw the element
            // probably the best way to do so would be to call el.updateRenderer(), i think.

            el.visPropOld.shadow = el.visProp.shadow;
        },

        // documented in AbstractRenderer
        highlight: function (obj) {
            if (obj.elementClass === Const.OBJECT_CLASS_TEXT && Type.evaluate(obj.visProp.display) === 'html') {
                this.updateTextStyle(obj, true);
            } else {
                obj.board.prepareUpdate();
                obj.board.renderer.suspendRedraw(obj.board);
                obj.board.updateRenderer();
                obj.board.renderer.unsuspendRedraw();
            }
            return this;
        },

        // documented in AbstractRenderer
        noHighlight: function (obj) {
            if (obj.elementClass === Const.OBJECT_CLASS_TEXT && Type.evaluate(obj.visProp.display) === 'html') {
                this.updateTextStyle(obj, false);
            } else {
                obj.board.prepareUpdate();
                obj.board.renderer.suspendRedraw(obj.board);
                obj.board.updateRenderer();
                obj.board.renderer.unsuspendRedraw();
            }
            return this;
        },

        /* **************************
         * renderer control
         * **************************/

        // documented in AbstractRenderer
        suspendRedraw: function (board) {
            this.context.save();
            this.context.clearRect(0, 0, this.canvasRoot.width, this.canvasRoot.height);

            if (board && board.attr.showcopyright) {
                this.displayCopyright(JXG.licenseText, 12);
            }
        },

        // documented in AbstractRenderer
        unsuspendRedraw: function () {
            this.context.restore();
        },

        // document in AbstractRenderer
        resize: function (w, h) {
            if (this.container) {
                this.canvasRoot.style.width = parseFloat(w) + 'px';
                this.canvasRoot.style.height = parseFloat(h) + 'px';

                this.canvasRoot.setAttribute('width', (2 * parseFloat(w)) + 'px');
                this.canvasRoot.setAttribute('height',(2 * parseFloat(h)) + 'px');
            } else {
                this.canvasRoot.width = 2 * parseFloat(w);
                this.canvasRoot.height = 2 * parseFloat(h);
            }
            this.context = this.canvasRoot.getContext('2d');
            // The width and height of the canvas is set to twice the CSS values,
            // followed by an appropiate scaling.
            // See http://stackoverflow.com/questions/22416462/canvas-element-with-blurred-lines
            this.context.scale(2, 2);
        },

        removeToInsertLater: function () {
            return function () {};
        }
    });

    return JXG.CanvasRenderer;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, AMprocessNode: true, MathJax: true, document: true */
/*jslint nomen: true, plusplus: true, newcap:true, unparam: true*/
/*eslint no-unused-vars: "off"*/

/* depends:
 jxg
 renderer/abstract
*/

/**
 * @fileoverview JSXGraph can use various technologies to render the contents of a construction, e.g.
 * SVG, VML, and HTML5 Canvas. To accomplish this, The rendering and the logic and control mechanisms
 * are completely separated from each other. Every rendering technology has it's own class, called
 * Renderer, e.g. SVGRenderer for SVG, the same for VML and Canvas. The common base for all available
 * renderers is the class AbstractRenderer.
 */

define('renderer/no',['jxg', 'renderer/abstract'], function (JXG, AbstractRenderer) {

    "use strict";

    /**
     * This renderer draws nothing. It is intended to be used in environments where none of our rendering engines
     * are available, e.g. WebWorkers. All methods are empty.
     * 
     * @class JXG.NoRenderer
     * @augments JXG.AbstractRenderer
     * @see JXG.AbstractRenderer
     */
    JXG.NoRenderer = function () {
        /**
         * If this property is set to <tt>true</tt> the visual properties of the elements are updated
         * on every update. Visual properties means: All the stuff stored in the
         * {@link JXG.GeometryElement#visProp} property won't be set if enhancedRendering is <tt>false</tt>
         * @type Boolean
         * @default true
         */
        this.enhancedRendering = false;

        /**
         * This is used to easily determine which renderer we are using
         * @example if (board.renderer.type === 'vml') {
         *     // do something
         * }
         * @type String
         */
        this.type = 'no';
    };

    JXG.extend(JXG.NoRenderer.prototype, /** @lends JXG.NoRenderer.prototype */ {
        /* ******************************** *
         *    Point drawing and updating    *
         * ******************************** */

        /**
         * Draws a point on the {@link JXG.Board}.
         * @param {JXG.Point} element Reference to a {@link JXG.Point} object that has to be drawn.
         * @see Point
         * @see JXG.Point
         * @see JXG.AbstractRenderer#updatePoint
         * @see JXG.AbstractRenderer#changePointStyle
         */
        drawPoint: function (element) {},

        /**
         * Updates visual appearance of the renderer element assigned to the given {@link JXG.Point}.
         * @param {JXG.Point} element Reference to a {@link JXG.Point} object, that has to be updated.
         * @see Point
         * @see JXG.Point
         * @see JXG.AbstractRenderer#drawPoint
         * @see JXG.AbstractRenderer#changePointStyle
         */
        updatePoint: function (element) { },

        /**
         * Changes the style of a {@link JXG.Point}. This is required because the point styles differ in what
         * elements have to be drawn, e.g. if the point is marked by a "x" or a "+" two lines are drawn, if
         * it's marked by spot a circle is drawn. This method removes the old renderer element(s) and creates
         * the new one(s).
         * @param {JXG.Point} element Reference to a {@link JXG.Point} object, that's style is changed.
         * @see Point
         * @see JXG.Point
         * @see JXG.AbstractRenderer#updatePoint
         * @see JXG.AbstractRenderer#drawPoint
         */
        changePointStyle: function (element) { },

        /* ******************************** *
         *           Lines                  *
         * ******************************** */

        /**
         * Draws a line on the {@link JXG.Board}.
         * @param {JXG.Line} element Reference to a line object, that has to be drawn.
         * @see Line
         * @see JXG.Line
         * @see JXG.AbstractRenderer#updateLine
         */
        drawLine: function (element) { },

        /**
         * Updates visual appearance of the renderer element assigned to the given {@link JXG.Line}.
         * @param {JXG.Line} element Reference to the {@link JXG.Line} object that has to be updated.
         * @see Line
         * @see JXG.Line
         * @see JXG.AbstractRenderer#drawLine
         */
        updateLine: function (element) { },

        /**
         * Creates a rendering node for ticks added to a line.
         * @param {JXG.Line} element A arbitrary line.
         * @see Line
         * @see Ticks
         * @see JXG.Line
         * @see JXG.Ticks
         * @see JXG.AbstractRenderer#updateTicks
         */
        drawTicks: function (element) { },

        /**
         * Update {@link Ticks} on a {@link JXG.Line}. This method is only a stub and has to be implemented
         * in any descendant renderer class.
         * @param {JXG.Line} element Reference of an line object, thats ticks have to be updated.
         * @see Line
         * @see Ticks
         * @see JXG.Line
         * @see JXG.Ticks
         * @see JXG.AbstractRenderer#drawTicks
         */
        updateTicks: function (element) { /* stub */ },

        /* **************************
         *    Curves
         * **************************/

        /**
         * Draws a {@link JXG.Curve} on the {@link JXG.Board}.
         * @param {JXG.Curve} element Reference to a graph object, that has to be plotted.
         * @see Curve
         * @see JXG.Curve
         * @see JXG.AbstractRenderer#updateCurve
         */
        drawCurve: function (element) { },

        /**
         * Updates visual appearance of the renderer element assigned to the given {@link JXG.Curve}.
         * @param {JXG.Curve} element Reference to a {@link JXG.Curve} object, that has to be updated.
         * @see Curve
         * @see JXG.Curve
         * @see JXG.AbstractRenderer#drawCurve
         */
        updateCurve: function (element) { },

        /* **************************
         *    Circle related stuff
         * **************************/

        /**
         * Draws a {@link JXG.Circle}
         * @param {JXG.Circle} element Reference to a {@link JXG.Circle} object that has to be drawn.
         * @see Circle
         * @see JXG.Circle
         * @see JXG.AbstractRenderer#updateEllipse
         */
        drawEllipse: function (element) { },

        /**
         * Updates visual appearance of a given {@link JXG.Circle} on the {@link JXG.Board}.
         * @param {JXG.Circle} element Reference to a {@link JXG.Circle} object, that has to be updated.
         * @see Circle
         * @see JXG.Circle
         * @see JXG.AbstractRenderer#drawEllipse
         */
        updateEllipse: function (element) { },


        /* **************************
         *   Polygon related stuff
         * **************************/

        /**
         * Draws a {@link JXG.Polygon} on the {@link JXG.Board}.
         * @param {JXG.Polygon} element Reference to a Polygon object, that is to be drawn.
         * @see Polygon
         * @see JXG.Polygon
         * @see JXG.AbstractRenderer#updatePolygon
         */
        drawPolygon: function (element) { },

        /**
         * Updates properties of a {@link JXG.Polygon}'s rendering node.
         * @param {JXG.Polygon} element Reference to a {@link JXG.Polygon} object, that has to be updated.
         * @see Polygon
         * @see JXG.Polygon
         * @see JXG.AbstractRenderer#drawPolygon
         */
        updatePolygon: function (element) { },

        /* **************************
         *    Text related stuff
         * **************************/

        /**
         * Shows a small copyright notice in the top left corner of the board.
         * @param {String} str The copyright notice itself
         * @param {Number} fontsize Size of the font the copyright notice is written in
         */
        displayCopyright: function (str, fontsize) { /* stub */ },

        /**
         * An internal text is a {@link JXG.Text} element which is drawn using only
         * the given renderer but no HTML. This method is only a stub, the drawing
         * is done in the special renderers.
         * @param {JXG.Text} element Reference to a {@link JXG.Text} object
         * @see Text
         * @see JXG.Text
         * @see JXG.AbstractRenderer#updateInternalText
         * @see JXG.AbstractRenderer#drawText
         * @see JXG.AbstractRenderer#updateText
         * @see JXG.AbstractRenderer#updateTextStyle
         */
        drawInternalText: function (element) { /* stub */ },

        /**
         * Updates visual properties of an already existing {@link JXG.Text} element.
         * @param {JXG.Text} element Reference to an {@link JXG.Text} object, that has to be updated.
         * @see Text
         * @see JXG.Text
         * @see JXG.AbstractRenderer#drawInternalText
         * @see JXG.AbstractRenderer#drawText
         * @see JXG.AbstractRenderer#updateText
         * @see JXG.AbstractRenderer#updateTextStyle
         */
        updateInternalText: function (element) { /* stub */ },

        /**
         * Displays a {@link JXG.Text} on the {@link JXG.Board} by putting a HTML div over it.
         * @param {JXG.Text} element Reference to an {@link JXG.Text} object, that has to be displayed
         * @see Text
         * @see JXG.Text
         * @see JXG.AbstractRenderer#drawInternalText
         * @see JXG.AbstractRenderer#updateText
         * @see JXG.AbstractRenderer#updateInternalText
         * @see JXG.AbstractRenderer#updateTextStyle
         */
        drawText: function (element) { },

        /**
         * Updates visual properties of an already existing {@link JXG.Text} element.
         * @param {JXG.Text} element Reference to an {@link JXG.Text} object, that has to be updated.
         * @see Text
         * @see JXG.Text
         * @see JXG.AbstractRenderer#drawText
         * @see JXG.AbstractRenderer#drawInternalText
         * @see JXG.AbstractRenderer#updateInternalText
         * @see JXG.AbstractRenderer#updateTextStyle
         */
        updateText: function (element) { },

        /**
         * Updates CSS style properties of a {@link JXG.Text} node.
         * @param {JXG.Text} element Reference to the {@link JXG.Text} object, that has to be updated.
         * @param {Boolean} doHighlight
         * @see Text
         * @see JXG.Text
         * @see JXG.AbstractRenderer#drawText
         * @see JXG.AbstractRenderer#drawInternalText
         * @see JXG.AbstractRenderer#updateText
         * @see JXG.AbstractRenderer#updateInternalText
         */
        updateTextStyle: function (element, doHighlight) { },

        /**
         * Set color and opacity of internal texts.
         * SVG needs its own version.
         * @private
         * @see JXG.AbstractRenderer#updateTextStyle
         * @see JXG.AbstractRenderer#updateInternalTextStyle
         */
        updateInternalTextStyle: function (element, strokeColor, strokeOpacity) { /* stub */ },

        /* **************************
         *    Image related stuff
         * **************************/

        /**
         * Draws an {@link JXG.Image} on a board; This is just a template that has to be implemented by special renderers.
         * @param {JXG.Image} element Reference to the image object that is to be drawn
         * @see Image
         * @see JXG.Image
         * @see JXG.AbstractRenderer#updateImage
         */
        drawImage: function (element) { /* stub */ },

        /**
         * Updates the properties of an {@link JXG.Image} element.
         * @param {JXG.Image} element Reference to an {@link JXG.Image} object, that has to be updated.
         * @see Image
         * @see JXG.Image
         * @see JXG.AbstractRenderer#drawImage
         */
        updateImage: function (element) { },

        /**
         * Applies transformations on images and text elements. This method is just a stub and has to be implemented in all
         * descendant classes where text and image transformations are to be supported.
         * @param {JXG.Image|JXG.Text} element A {@link JXG.Image} or {@link JXG.Text} object.
         * @param {Array} transformations An array of {@link JXG.Transformation} objects. This is usually the transformations property
         * of the given element <tt>el</tt>.
         */
        transformImage: function (element, transformations) { /* stub */ },

        /**
         * If the URL of the image is provided by a function the URL has to be updated during updateImage()
         * @param {JXG.Image} element Reference to an image object.
         * @see JXG.AbstractRenderer#updateImage
         */
        updateImageURL: function (element) { /* stub */ },

        /* **************************
         * Render primitive objects
         * **************************/

        /**
         * Appends a node to a specific layer level. This is just an abstract method and has to be implemented
         * in all renderers that want to use the <tt>createPrim</tt> model to draw.
         * @param {Node} node A DOM tree node.
         * @param {Number} level The layer the node is attached to. This is the index of the layer in
         * {@link JXG.SVGRenderer#layer} or the <tt>z-index</tt> style property of the node in VMLRenderer.
         */
        appendChildPrim: function (node, level) { /* stub */ },

        /**
         * Stores the rendering nodes. This is an abstract method which has to be implemented in all renderers that use
         * the <tt>createPrim</tt> method.
         * @param {JXG.GeometryElement} element A JSXGraph element.
         * @param {String} type The XML node name. Only used in VMLRenderer.
         */
        appendNodesToElement: function (element, type) { /* stub */ },

        /**
         * Creates a node of a given type with a given id.
         * @param {String} type The type of the node to create.
         * @param {String} id Set the id attribute to this.
         * @returns {Node} Reference to the created node.
         */
        createPrim: function (type, id) {
            /* stub */
            return null;
        },

        /**
         * Removes an element node. Just a stub.
         * @param {Node} node The node to remove.
         */
        remove: function (node) { /* stub */ },

        /**
         * Can be used to create the nodes to display arrows. This is an abstract method which has to be implemented
         * in any descendant renderer.
         * @param {JXG.GeometryElement} element The element the arrows are to be attached to.
         */
        makeArrows: function (element) { /* stub */ },

        /**
         * Updates an ellipse node primitive. This is an abstract method which has to be implemented in all renderers
         * that use the <tt>createPrim</tt> method.
         * @param {Node} node Reference to the node.
         * @param {Number} x Centre X coordinate
         * @param {Number} y Centre Y coordinate
         * @param {Number} rx The x-axis radius.
         * @param {Number} ry The y-axis radius.
         */
        updateEllipsePrim: function (node, x, y, rx, ry) { /* stub */ },

        /**
         * Refreshes a line node. This is an abstract method which has to be implemented in all renderers that use
         * the <tt>createPrim</tt> method.
         * @param {Node} node The node to be refreshed.
         * @param {Number} p1x The first point's x coordinate.
         * @param {Number} p1y The first point's y coordinate.
         * @param {Number} p2x The second point's x coordinate.
         * @param {Number} p2y The second point's y coordinate.
         * @param {JXG.Board} board
         */
        updateLinePrim: function (node, p1x, p1y, p2x, p2y, board) { /* stub */ },

        /**
         * Updates a path element. This is an abstract method which has to be implemented in all renderers that use
         * the <tt>createPrim</tt> method.
         * @param {Node} node The path node.
         * @param {String} pathString A string formatted like e.g. <em>'M 1,2 L 3,1 L5,5'</em>. The format of the string
         * depends on the rendering engine.
         * @param {JXG.Board} board Reference to the element's board.
         */
        updatePathPrim: function (node, pathString, board) { /* stub */ },

        /**
         * Builds a path data string to draw a point with a face other than <em>rect</em> and <em>circle</em>. Since
         * the format of such a string usually depends on the renderer this method
         * is only an abstract method. Therefore, it has to be implemented in the descendant renderer itself unless
         * the renderer does not use the createPrim interface but the draw* interfaces to paint.
         * @param {JXG.Point} element The point element
         * @param {Number} size A positive number describing the size. Usually the half of the width and height of
         * the drawn point.
         * @param {String} type A string describing the point's face. This method only accepts the shortcut version of
         * each possible face: <tt>x, +, <>, ^, v, >, <
         */
        updatePathStringPoint: function (element, size, type) { /* stub */ },

        /**
         * Builds a path data string from a {@link JXG.Curve} element. Since the path data strings heavily depend on the
         * underlying rendering technique this method is just a stub. Although such a path string is of no use for the
         * CanvasRenderer, this method is used there to draw a path directly.
         * @param element
         */
        updatePathStringPrim: function (element) { /* stub */ },

        /**
         * Builds a path data string from a {@link JXG.Curve} element such that the curve looks like
         * hand drawn.
         * Since the path data strings heavily depend on the
         * underlying rendering technique this method is just a stub. Although such a path string is of no use for the
         * CanvasRenderer, this method is used there to draw a path directly.
         * @param element
         */
        updatePathStringBezierPrim: function (element) { /* stub */ },


        /**
         * Update a polygon primitive.
         * @param {Node} node
         * @param {JXG.Polygon} element A JSXGraph element of type {@link JXG.Polygon}
         */
        updatePolygonPrim: function (node, element) { /* stub */ },

        /**
         * Update a rectangle primitive. This is used only for points with face of type 'rect'.
         * @param {Node} node The node yearning to be updated.
         * @param {Number} x x coordinate of the top left vertex.
         * @param {Number} y y coordinate of the top left vertex.
         * @param {Number} w Width of the rectangle.
         * @param {Number} h The rectangle's height.
         */
        updateRectPrim: function (node, x, y, w, h) { /* stub */ },

        /* **************************
         *  Set Attributes
         * **************************/

        /**
         * Sets a node's attribute.
         * @param {Node} node The node that is to be updated.
         * @param {String} key Name of the attribute.
         * @param {String} val New value for the attribute.
         */
        setPropertyPrim: function (node, key, val) { /* stub */ },

        /**
         * Shows or hides an element on the canvas; Only a stub, requires implementation in the derived renderer.
         * @param {JXG.GeometryElement} element Reference to the object that has to appear.
         * @param {Boolean} value true to show the element, false to hide the element.
         */
        display: function (element, value) {
            if (element) {
                element.visPropOld.visible = value;
            }
        },

        /**
         * Shows a hidden element on the canvas; Only a stub, requires implementation in the derived renderer.
         *
         * Please use JXG.AbstractRenderer#display instead
         * @param {JXG.GeometryElement} element Reference to the object that has to appear.
         * @see JXG.AbstractRenderer#hide
         * @deprecated
         */
        show: function (element) { /* stub */ },

        /**
         * Hides an element on the canvas; Only a stub, requires implementation in the derived renderer.
         *
         * Please use JXG.AbstractRenderer#display instead
         * @param {JXG.GeometryElement} element Reference to the geometry element that has to disappear.
         * @see JXG.AbstractRenderer#show
         * @deprecated
         */
        hide: function (element) { /* stub */ },

        /**
         * Sets the buffering as recommended by SVGWG. Until now only Opera supports this and will be ignored by
         * other browsers. Although this feature is only supported by SVG we have this method in {@link JXG.AbstractRenderer}
         * because it is called from outside the renderer.
         * @param {Node} node The SVG DOM Node which buffering type to update.
         * @param {String} type Either 'auto', 'dynamic', or 'static'. For an explanation see
         *   {@link http://www.w3.org/TR/SVGTiny12/painting.html#BufferedRenderingProperty}.
         */
        setBuffering: function (node, type) { /* stub */ },

        /**
         * Sets an element's dash style.
         * @param {JXG.GeometryElement} element An JSXGraph element.
         */
        setDashStyle: function (element) { /* stub */ },

        /**
         * Puts an object into draft mode, i.e. it's visual appearance will be changed. For GEONE<sub>x</sub>T backwards compatibility.
         * @param {JXG.GeometryElement} element Reference of the object that is in draft mode.
         */
        setDraft: function (element) { },

        /**
         * Puts an object from draft mode back into normal mode.
         * @param {JXG.GeometryElement} element Reference of the object that no longer is in draft mode.
         */
        removeDraft: function (element) { },

        /**
         * Sets up nodes for rendering a gradient fill.
         * @param element
         */
        setGradient: function (element) { /* stub */ },

        /**
         * Updates the gradient fill.
         * @param {JXG.GeometryElement} element An JSXGraph element with an area that can be filled.
         */
        updateGradient: function (element) { /* stub */ },

        /**
         * Sets the transition duration (in milliseconds) for fill color and stroke
         * color and opacity.
         * @param {JXG.GeometryElement} element Reference of the object that wants a
         *         new transition duration.
         * @param {Number} duration (Optional) duration in milliseconds. If not given,
         *        element.visProp.transitionDuration is taken. This is the default.
         */
        setObjectTransition: function (element, duration) { /* stub */ },

        /**
         * Sets an objects fill color.
         * @param {JXG.GeometryElement} element Reference of the object that wants a new fill color.
         * @param {String} color Color in a HTML/CSS compatible format. If you don't want any fill color at all, choose 'none'.
         * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1.
         */
        setObjectFillColor: function (element, color, opacity) { /* stub */ },

        /**
         * Changes an objects stroke color to the given color.
         * @param {JXG.GeometryElement} element Reference of the {@link JXG.GeometryElement} that gets a new stroke color.
         * @param {String} color Color value in a HTML compatible format, e.g. <strong>#00ff00</strong> or <strong>green</strong> for green.
         * @param {Number} opacity Opacity of the fill color. Must be between 0 and 1.
         */
        setObjectStrokeColor: function (element, color, opacity) { /* stub */ },

        /**
         * Sets an element's stroke width.
         * @param {JXG.GeometryElement} element Reference to the geometry element.
         * @param {Number} width The new stroke width to be assigned to the element.
         */
        setObjectStrokeWidth: function (element, width) { /* stub */ },

        /**
         * Sets the shadow properties to a geometry element. This method is only a stub, it is implemented in the actual renderers.
         * @param {JXG.GeometryElement} element Reference to a geometry object, that should get a shadow
         */
        setShadow: function (element) { /* stub */ },

        /**
         * Highlights an object, i.e. changes the current colors of the object to its highlighting colors
         * @param {JXG.GeometryElement} element Reference of the object that will be highlighted.
         * @returns {JXG.AbstractRenderer} Reference to the renderer
         */
        highlight: function (element) { },

        /**
         * Uses the normal colors of an object, i.e. the opposite of {@link JXG.AbstractRenderer#highlight}.
         * @param {JXG.GeometryElement} element Reference of the object that will get its normal colors.
         * @returns {JXG.AbstractRenderer} Reference to the renderer
         */
        noHighlight: function (element) { },


        /* **************************
         * renderer control
         * **************************/

        /**
         * Stop redraw. This method is called before every update, so a non-vector-graphics based renderer
         * can use this method to delete the contents of the drawing panel. This is an abstract method every
         * descendant renderer should implement, if appropriate.
         * @see JXG.AbstractRenderer#unsuspendRedraw
         */
        suspendRedraw: function () { /* stub */ },

        /**
         * Restart redraw. This method is called after updating all the rendering node attributes.
         * @see JXG.AbstractRenderer#suspendRedraw
         */
        unsuspendRedraw: function () { /* stub */ },

        /**
         * The tiny zoom bar shown on the bottom of a board (if showNavigation on board creation is true).
         * @param {JXG.Board} board Reference to a JSXGraph board.
         */
        drawZoomBar: function (board) { },

        /**
         * Wrapper for getElementById for maybe other renderers which elements are not directly accessible by DOM methods like document.getElementById().
         * @param {String} id Unique identifier for element.
         * @returns {Object} Reference to a JavaScript object. In case of SVG/VMLRenderer it's a reference to a SVG/VML node.
         */
        getElementById: function (id) {
            return null;
        },

        /**
         * Resizes the rendering element
         * @param {Number} w New width
         * @param {Number} h New height
         */
        resize: function (w, h) { /* stub */},

        removeToInsertLater: function () {
            return function () {};
        }

    });

    /**
     * @ignore
     */
    JXG.NoRenderer.prototype = new AbstractRenderer();

    return JXG.NoRenderer;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, document:true, jQuery:true, define: true, window: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/env
 utils/type
 base/board
 reader/file
 options
 renderer/svg
 renderer/vml
 renderer/canvas
 renderer/no
 */

/**
 * @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.
 * @version 0.99
 */

define('jsxgraph',[
    'jxg', 'utils/env', 'utils/type', 'base/board', 'reader/file', 'options',
    'renderer/svg', 'renderer/vml', 'renderer/canvas', 'renderer/no'
], function (JXG, Env, Type, Board, FileReader, Options, SVGRenderer, VMLRenderer, CanvasRenderer, NoRenderer) {

    "use strict";

    /**
     * Constructs a new JSXGraph singleton object.
     * @class The JXG.JSXGraph singleton stores all properties required
     * to load, save, create and free a board.
     */
    JXG.JSXGraph = {
        /**
         * Stores the renderer that is used to draw the boards.
         * @type String
         */
        rendererType: (function () {
            Options.board.renderer = 'no';

            if (Env.supportsVML()) {
                Options.board.renderer = 'vml';
                // Ok, this is some real magic going on here. IE/VML always was so
                // terribly slow, except in one place: Examples placed in a moodle course
                // was almost as fast as in other browsers. So i grabbed all the css and
                // lib scripts from our moodle, added them to a jsxgraph example and it
                // worked. next step was to strip all the css/lib code which didn't affect
                // the VML update speed. The following five lines are what was left after
                // the last step and yes - it basically does nothing but reads two
                // properties of document.body on every mouse move. why? we don't know. if
                // you know, please let us know.
                //
                // If we want to use the strict mode we have to refactor this a little bit. Let's
                // hope the magic isn't gone now. Anywho... it's only useful in old versions of IE
                // which should not be used anymore.
                document.onmousemove = function () {
                    var t;

                    if (document.body) {
                        t = document.body.scrollLeft;
                        t += document.body.scrollTop;
                    }

                    return t;
                };
            }

            if (Env.supportsCanvas()) {
                Options.board.renderer = 'canvas';
            }

            if (Env.supportsSVG()) {
                Options.board.renderer = 'svg';
            }

            // we are inside node
            if (Env.isNode() && Env.supportsCanvas()) {
                Options.board.renderer = 'canvas';
            }

            if (Env.isNode() || Options.renderer === 'no') {
                Options.text.display = 'internal';
                Options.infobox.display = 'internal';
            }

            return Options.board.renderer;
        }()),

        /**
         * Initialize the rendering engine
         *
         * @param  {String} box        HTML id of the div-element which hosts the JSXGraph construction
         * @param  {Object} dim        The dimensions of the board
         * @param  {Object} doc        Usually, this is document object of the browser window.  If false or null, this defaults
         * to the document object of the browser.
         * @param  {Object} attrRenderer Attribute 'renderer', speficies the rendering engine. Possible values are 'auto', 'svg',
         *  'canvas', 'no', and 'vml'.
         * @returns {Object}           Reference to the rendering engine object.
         * @private
         */
        initRenderer: function (box, dim, doc, attrRenderer) {
            var boxid, renderer;

            // Former version:
            // doc = doc || document
            if ((!Type.exists(doc) || doc === false) && typeof document === 'object') {
                doc = document;
            }

            if (typeof doc === 'object' && box !== null) {
                boxid = doc.getElementById(box);

                // Remove everything from the container before initializing the renderer and the board
                while (boxid.firstChild) {
                    boxid.removeChild(boxid.firstChild);
                }
            } else {
                boxid = box;
            }

            // If attrRenderer is not supplied take the first available renderer
            if (attrRenderer === undefined || attrRenderer === 'auto') {
                attrRenderer = this.rendererType;
            }
            // create the renderer
            if (attrRenderer === 'svg') {
                renderer = new SVGRenderer(boxid, dim);
            } else if (attrRenderer === 'vml') {
                renderer = new VMLRenderer(boxid);
            } else if (attrRenderer === 'canvas') {
                renderer = new CanvasRenderer(boxid, dim);
            } else {
                renderer = new NoRenderer();
            }

            return renderer;
        },

        /**
         * Merge the user supplied attributes with the attributes in options.js
         *
         * @param {Object} attributes User supplied attributes
         * @returns {Object} Merged attributes for the board
         *
         * @private
         */
        _setAttributes: function(attributes) {
            // merge attributes
            var attr = Type.copyAttributes(attributes, Options, 'board');

            // The attributes which are objects have to be copied separately
            attr.zoom = Type.copyAttributes(attr, Options, 'board', 'zoom');
            attr.pan = Type.copyAttributes(attr, Options, 'board', 'pan');
            attr.drag = Type.copyAttributes(attr, Options, 'board', 'drag');
            attr.keyboard = Type.copyAttributes(attr, Options, 'board', 'keyboard');
            attr.selection = Type.copyAttributes(attr, Options, 'board', 'selection');
            attr.navbar = Type.copyAttributes(attr.navbar, Options, 'navbar');
            attr.screenshot = Type.copyAttributes(attr, Options, 'board', 'screenshot');
            attr.resize = Type.copyAttributes(attr, Options, 'board', 'resize');
            attr.fullscreen = Type.copyAttributes(attr, Options, 'board', 'fullscreen');

            // Treat moveTarget separately, because deepCopy will not work here.
            // Reason: moveTarget will be an HTML node and it is prevented that Type.deepCopy will copy it.
            attr.movetarget = attributes.moveTarget || attributes.movetarget || Options.board.moveTarget;

            return attr;
        },

        /**
         * Further initialization of the board. Set some properties from attribute values.
         *
         * @param {JXG.Board} board
         * @param {Object} attr attributes object
         * @param {Object} dimensions Object containing dimensions of the canvas
         *
         * @private
         */
        _fillBoard: function(board, attr, dimensions) {
            board.initInfobox();
            board.maxboundingbox = attr.maxboundingbox;
            board.resizeContainer(dimensions.width, dimensions.height, true, true);
            board._createSelectionPolygon(attr);
            board.renderer.drawZoomBar(board, attr.navbar);
            JXG.boards[board.id] = board;
        },

        /**
         *
         * @param {String} container HTML-ID to the HTML-element in which the board is painted.
         * @param {*} attr An object that sets some of the board properties.
         *
         * @private
         */
        _setARIA: function(container, attr) {
            var doc = attr.document || document,
                node_jsx, newNode, parent,
                id_label, id_description;

            if (typeof doc !== 'object') {
                return;
            }

            node_jsx = doc.getElementById(container);
            parent = node_jsx.parentNode;

            id_label = container + '_ARIAlabel';
            id_description = container + '_ARIAdescription';

            newNode = doc.createElement('div');
            newNode.innerHTML = attr.title;
            newNode.setAttribute('id', id_label);
            newNode.style.display = 'none';
            parent.insertBefore(newNode, node_jsx);

            newNode = doc.createElement('div');
            newNode.innerHTML = attr.description;
            newNode.setAttribute('id', id_description);
            newNode.style.display = 'none';
            parent.insertBefore(newNode, node_jsx);

            node_jsx.setAttribute('aria-labelledby', id_label);
            node_jsx.setAttribute('aria-describedby', id_description);
        },

        /**
         * Remove the two corresponding ARIA divs when freeing a board
         *
         * @param {JXG.Board} board
         *
         * @private
         */
        _removeARIANodes: function(board) {
            var node, id, doc;

            doc = board.document || document;
            if (typeof doc !== 'object') {
                return;
            }

            id = board.containerObj.getAttribute('aria-labelledby');
            node = document.getElementById(id);
            if (node && node.parentNode) {
                node.parentNode.removeChild(node);
            }
            id = board.containerObj.getAttribute('aria-describedby');
            node = document.getElementById(id);
            if (node && node.parentNode) {
                node.parentNode.removeChild(node);
            }
        },

        /**
         * Initialise a new board.
         * @param {String} box HTML-ID to the HTML-element in which the board is painted.
         * @param {Object} attributes An object that sets some of the board properties. Most of these properties can be set via JXG.Options.
         * @param {Array} [attributes.boundingbox=[-5, 5, 5, -5]] An array containing four numbers describing the left, top, right and bottom boundary of the board in user coordinates
         * @param {Boolean} [attributes.keepaspectratio=false] If <tt>true</tt>, the bounding box is adjusted to the same aspect ratio as the aspect ratio of the div containing the board.
         * @param {Boolean} [attributes.showCopyright=false] Show the copyright string in the top left corner.
         * @param {Boolean} [attributes.showNavigation=false] Show the navigation buttons in the bottom right corner.
         * @param {Object} [attributes.zoom] Allow the user to zoom with the mouse wheel or the two-fingers-zoom gesture.
         * @param {Object} [attributes.pan] Allow the user to pan with shift+drag mouse or two-fingers-pan gesture.
         * @param {Object} [attributes.drag] Allow the user to drag objects with a pointer device.
         * @param {Object} [attributes.keyboard] Allow the user to drag objects with arrow keys on keyboard.
         * @param {Boolean} [attributes.axis=false] If set to true, show the axis. Can also be set to an object that is given to both axes as an attribute object.
         * @param {Boolean|Object} [attributes.grid] If set to true, shows the grid. Can also be set to an object that is given to the grid as its attribute object.
         * @param {Boolean} [attributes.registerEvents=true] Register mouse / touch events.
         * @returns {JXG.Board} Reference to the created board.
         */
        initBoard: function (box, attributes) {
            var originX, originY, unitX, unitY,
                renderer,
                offX = 0,
                offY = 0,
                w, h, dimensions,
                bbox, attr, axattr, axattr_x, axattr_y,
                board;

            attributes = attributes || {};
            attr = this._setAttributes(attributes);

            dimensions = Env.getDimensions(box, attr.document);

            if (attr.unitx || attr.unity) {
                originX = Type.def(attr.originx, 150);
                originY = Type.def(attr.originy, 150);
                unitX = Type.def(attr.unitx, 50);
                unitY = Type.def(attr.unity, 50);
            } else {
                bbox = attr.boundingbox;
                if (bbox[0] < attr.maxboundingbox[0]) { bbox[0] = attr.maxboundingbox[0]; }
                if (bbox[1] > attr.maxboundingbox[1]) { bbox[1] = attr.maxboundingbox[1]; }
                if (bbox[2] > attr.maxboundingbox[2]) { bbox[2] = attr.maxboundingbox[2]; }
                if (bbox[3] < attr.maxboundingbox[3]) { bbox[3] = attr.maxboundingbox[3]; }

                w = parseInt(dimensions.width, 10);
                h = parseInt(dimensions.height, 10);

                if (Type.exists(bbox) && attr.keepaspectratio) {
                    /*
                     * If the boundingbox attribute is given and the ratio of height and width of the
                     * sides defined by the bounding box and the ratio of the dimensions of the div tag
                     * which contains the board do not coincide, then the smaller side is chosen.
                     */
                    unitX = w / (bbox[2] - bbox[0]);
                    unitY = h / (bbox[1] - bbox[3]);

                    if (Math.abs(unitX) < Math.abs(unitY)) {
                        unitY = Math.abs(unitX) * unitY / Math.abs(unitY);
                        // Add the additional units in equal portions above and below
                        offY = (h / unitY - (bbox[1] - bbox[3])) * 0.5;
                    } else {
                        unitX = Math.abs(unitY) * unitX / Math.abs(unitX);
                        // Add the additional units in equal portions left and right
                        offX = (w / unitX - (bbox[2] - bbox[0])) * 0.5;
                    }
                } else {
                    unitX = w / (bbox[2] - bbox[0]);
                    unitY = h / (bbox[1] - bbox[3]);
                }
                originX = -unitX * (bbox[0] - offX);
                originY = unitY * (bbox[1] + offY);
            }

            renderer = this.initRenderer(box, dimensions, attr.document, attr.renderer);
            this._setARIA(box, attr);

            // create the board
            board = new Board(box, renderer, attr.id, [originX, originY],
                        attr.zoomfactor * attr.zoomx,
                        attr.zoomfactor * attr.zoomy,
                        unitX, unitY,
                        dimensions.width, dimensions.height,
                        attr);

            board.keepaspectratio = attr.keepaspectratio;

            this._fillBoard(board, attr, dimensions);

            // create elements like axes, grid, navigation, ...
            board.suspendUpdate();
            if (attr.axis) {
                axattr = typeof attr.axis === 'object' ? attr.axis : {};

                // The defaultAxes attributes are overwritten by user supplied axis object.
                axattr_x = Type.deepCopy(Options.board.defaultAxes.x, axattr);
                axattr_y = Type.deepCopy(Options.board.defaultAxes.y, axattr);
                // The user supplied defaultAxes attributes are merged in.
                if (attr.defaultaxes.x) {
                    axattr_x = Type.deepCopy(axattr_x, attr.defaultaxes.x);
                }
                if (attr.defaultaxes.y) {
                    axattr_y = Type.deepCopy(axattr_y, attr.defaultaxes.y);
                }

                board.defaultAxes = {};
                board.defaultAxes.x = board.create('axis', [[0, 0], [1, 0]], axattr_x);
                board.defaultAxes.y = board.create('axis', [[0, 0], [0, 1]], axattr_y);
            }
            if (attr.grid) {
                board.create('grid', [], (typeof attr.grid === 'object' ? attr.grid : {}));
            }
            board.unsuspendUpdate();

            return board;
        },

        /**
         * Load a board from a file containing a construction made with either GEONExT,
         * Intergeo, Geogebra, or Cinderella.
         * @param {String} box HTML-ID to the HTML-element in which the board is painted.
         * @param {String} file base64 encoded string.
         * @param {String} format containing the file format: 'Geonext' or 'Intergeo'.
         * @param {Object} attributes Attributes for the board and 'encoding'.
         *  Compressed files need encoding 'iso-8859-1'. Otherwise it probably is 'utf-8'.
         * @param {Function} callback
         * @returns {JXG.Board} Reference to the created board.
         * @see JXG.FileReader
         * @see JXG.GeonextReader
         * @see JXG.GeogebraReader
         * @see JXG.IntergeoReader
         * @see JXG.CinderellaReader
         *
         * @example
         * // Uncompressed file
         * var board = JXG.JSXGraph.loadBoardFromFile('jxgbox', 'filename', 'geonext',
         *      {encoding: 'utf-8'},
         *      function (board) { console.log("Done loading"); }
         * );
         * // Compressed file
         * var board = JXG.JSXGraph.loadBoardFromFile('jxgbox', 'filename', 'geonext',
         *      {encoding: 'iso-8859-1'},
         *      function (board) { console.log("Done loading"); }
         * );
         *
         * @example
         * // From <input type="file" id="localfile" />
         * var file = document.getElementById('localfile').files[0];
         * JXG.JSXGraph.loadBoardFromFile('jxgbox', file, 'geonext',
         *      {encoding: 'utf-8'},
         *      function (board) { console.log("Done loading"); }
         * );
         */
        loadBoardFromFile: function (box, file, format, attributes, callback) {
            var attr, renderer, board, dimensions, encoding;

            attributes = attributes || {};
            attr = this._setAttributes(attributes);

            dimensions = Env.getDimensions(box, attr.document);
            renderer = this.initRenderer(box, dimensions, attr.document, attr.renderer);
            this._setARIA(box, attr);

            /* User default parameters, in parse* the values in the gxt files are submitted to board */
            board = new Board(box, renderer, '', [150, 150], 1, 1, 50, 50, dimensions.width, dimensions.height, attr);
            this._fillBoard(board, attr, dimensions);
            encoding = attr.encoding || 'iso-8859-1';
            FileReader.parseFileContent(file, board, format, true, encoding, callback);

            return board;
        },

        /**
         * Load a board from a base64 encoded string containing a construction made with either GEONExT,
         * Intergeo, Geogebra, or Cinderella.
         * @param {String} box HTML-ID to the HTML-element in which the board is painted.
         * @param {String} string base64 encoded string.
         * @param {String} format containing the file format: 'Geonext', 'Intergeo', 'Geogebra'.
         * @param {Object} attributes Attributes for the board and 'encoding'.
         *  Compressed files need encoding 'iso-8859-1'. Otherwise it probably is 'utf-8'.
         * @param {Function} callback
         * @returns {JXG.Board} Reference to the created board.
         * @see JXG.FileReader
         * @see JXG.GeonextReader
         * @see JXG.GeogebraReader
         * @see JXG.IntergeoReader
         * @see JXG.CinderellaReader
         */
        loadBoardFromString: function (box, string, format, attributes, callback) {
            var attr, renderer, board, dimensions;

            attributes = attributes || {};
            attr = this._setAttributes(attributes);

            dimensions = Env.getDimensions(box, attr.document);
            renderer = this.initRenderer(box, dimensions, attr.document);
            this._setARIA(box, attr);

            /* User default parameters, in parse* the values in the gxt files are submitted to board */
            board = new Board(box, renderer, '', [150, 150], 1.0, 1.0, 50, 50, dimensions.width, dimensions.height, attr);
            this._fillBoard(board, attr, dimensions);
            FileReader.parseString(string, board, format, true, callback);

            return board;
        },

        /**
         * Delete a board and all its contents.
         * @param {JXG.Board,String} board HTML-ID to the DOM-element in which the board is drawn.
         */
        freeBoard: function (board) {
            var el;

            if (typeof board === 'string') {
                board = JXG.boards[board];
            }

            this._removeARIANodes(board);
            board.removeEventHandlers();
            board.suspendUpdate();

            // Remove all objects from the board.
            for (el in board.objects) {
                if (board.objects.hasOwnProperty(el)) {
                    board.objects[el].remove();
                }
            }

            // Remove all the other things, left on the board, XHTML save
            while (board.containerObj.firstChild) {
                board.containerObj.removeChild(board.containerObj.firstChild);
            }

            // Tell the browser the objects aren't needed anymore
            for (el in board.objects) {
                if (board.objects.hasOwnProperty(el)) {
                    delete board.objects[el];
                }
            }

            // Free the renderer and the algebra object
            delete board.renderer;

            // clear the creator cache
            board.jc.creator.clearCache();
            delete board.jc;

            // Finally remove the board itself from the boards array
            delete JXG.boards[board.id];
        },

        /**
         * @deprecated Use JXG#registerElement
         * @param element
         * @param creator
         */
        registerElement: function (element, creator) {
            JXG.deprecated('JXG.JSXGraph.registerElement()', 'JXG.registerElement()');
            JXG.registerElement(element, creator);
        }
    };

    // JessieScript/JessieCode startup: Search for script tags of type text/jessiescript and interprete them.
    if (Env.isBrowser && typeof window === 'object' && typeof document === 'object') {
        Env.addEvent(window, 'load', function () {
            var type, i, j, div,
                id, board, txt,
                width, height, maxWidth, aspectRatio, cssClasses,
                bbox, axis, grid, code,
                src, request, postpone = false,
                scripts = document.getElementsByTagName('script'),
                init = function (code, type, bbox) {
                    var board = JXG.JSXGraph.initBoard(id, {boundingbox: bbox, keepaspectratio: true, grid: grid, axis: axis, showReload: true});

                    if (type.toLowerCase().indexOf('script') > -1) {
                        board.construct(code);
                    } else {
                        try {
                            board.jc.parse(code);
                        } catch (e2) {
                            JXG.debug(e2);
                        }
                    }

                    return board;
                },
                makeReload = function (board, code, type, bbox) {
                    return function () {
                        var newBoard;

                        JXG.JSXGraph.freeBoard(board);
                        newBoard = init(code, type, bbox);
                        newBoard.reload = makeReload(newBoard, code, type, bbox);
                    };
                };

            for (i = 0; i < scripts.length; i++) {
                type = scripts[i].getAttribute('type', false);

                if (Type.exists(type) &&
                    (type.toLowerCase() === 'text/jessiescript' || type.toLowerCase() === 'jessiescript' ||
                     type.toLowerCase() === 'text/jessiecode' || type.toLowerCase() === 'jessiecode')) {
                    cssClasses = scripts[i].getAttribute('class', false) || '';
                    width = scripts[i].getAttribute('width', false) || '';
                    height = scripts[i].getAttribute('height', false) || '';
                    maxWidth = scripts[i].getAttribute('maxwidth', false) || '100%';
                    aspectRatio = scripts[i].getAttribute('aspectratio', false) || '1/1';
                    bbox = scripts[i].getAttribute('boundingbox', false) || '-5, 5, 5, -5';
                    id = scripts[i].getAttribute('container', false);
                    src = scripts[i].getAttribute('src', false);

                    bbox = bbox.split(',');
                    if (bbox.length !== 4) {
                        bbox = [-5, 5, 5, -5];
                    } else {
                        for (j = 0; j < bbox.length; j++) {
                            bbox[j] = parseFloat(bbox[j]);
                        }
                    }
                    axis = Type.str2Bool(scripts[i].getAttribute('axis', false) || 'false');
                    grid = Type.str2Bool(scripts[i].getAttribute('grid', false) || 'false');

                    if (!Type.exists(id)) {
                        id = 'jessiescript_autgen_jxg_' + i;
                        div = document.createElement('div');
                        div.setAttribute('id', id);

                        txt = (width !== '') ? ('width:' + width + ';') : '';
                        txt += (height !== '') ? ('height:' + height + ';') : '';
                        txt += (maxWidth !== '') ? ('max-width:' + maxWidth + ';') : '';
                        txt += (aspectRatio !== '') ? ('aspect-ratio:' + aspectRatio + ';') : '';

                        div.setAttribute('style', txt);
                        div.setAttribute('class', 'jxgbox ' + cssClasses);
                        try {
                            document.body.insertBefore(div, scripts[i]);
                        } catch (e) {
                            // there's probably jquery involved...
                            if (typeof jQuery === 'object') {
                                jQuery(div).insertBefore(scripts[i]);
                            }
                        }
                    } else {
                        div = document.getElementById(id);
                    }

                    code = '';

                    if (Type.exists(src)) {
                        postpone = true;
                        request = new XMLHttpRequest();
                        request.open("GET", src);
                        request.overrideMimeType("text/plain; charset=x-user-defined");
                        /* jshint ignore:start */
                        request.addEventListener("load", function() {
                            if (this.status < 400) {
                                code = this.responseText + '\n' + code;
                                board = init(code, type, bbox);
                                board.reload = makeReload(board, code, type, bbox);
                            } else {
                                throw new Error("\nJSXGraph: failed to load file", src, ":", this.responseText);
                            }
                        });
                        request.addEventListener("error", function(e) {
                            throw new Error("\nJSXGraph: failed to load file", src, ":", e);
                        });
                        /* jshint ignore:end */
                        request.send();
                    } else {
                        postpone = false;
                    }

                    if (document.getElementById(id)) {
                        code = scripts[i].innerHTML;
                        code = code.replace(/<!\[CDATA\[/g, '').replace(/\]\]>/g, '');
                        scripts[i].innerHTML = code;

                        if (!postpone) {
                            // Do no wait for data from "src" attribute
                            board = init(code, type, bbox);
                            board.reload = makeReload(board, code, type, bbox);
                        }
                    } else {
                        JXG.debug('JSXGraph: Apparently the div injection failed. Can\'t create a board, sorry.');
                    }
                }
            }
        }, window);
    }

    return JXG.JSXGraph;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, console: true, window: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 options
 math/math
 math/geometry
 math/numerics
 base/coords
 base/constants
 base/element
 parser/geonext
 utils/type
  elements:
   transform
 */

/**
 * @fileoverview The geometry object Point is defined in this file. Point stores all
 * style and functional properties that are required to draw and move a point on
 * a board.
 */

define('base/point',[
    'jxg', 'options', 'math/math', 'math/geometry', 'base/constants', 'base/element',
    'utils/type', 'base/coordselement'
], function (JXG, Options, Mat, Geometry, Const, GeometryElement, Type, CoordsElement) {

    "use strict";

    /**
     * A point is the basic geometric element. Based on points lines and circles can be constructed which can be intersected
     * which in turn are points again which can be used to construct new lines, circles, polygons, etc. This class holds methods for
     * all kind of points like free points, gliders, and intersection points.
     * @class Creates a new point object. Do not use this constructor to create a point. Use {@link JXG.Board#create} with
     * type {@link Point}, {@link Glider}, or {@link Intersection} instead.
     * @augments JXG.GeometryElement
     * @augments JXG.CoordsElement
     * @param {string|JXG.Board} board The board the new point is drawn on.
     * @param {Array} coordinates An array with the user coordinates of the point.
     * @param {Object} attributes An object containing visual properties like in {@link JXG.Options#point} and
     * {@link JXG.Options#elements}, and optional a name and an id.
     * @see JXG.Board#generateName
     */
    JXG.Point = function (board, coordinates, attributes) {
        this.constructor(board, attributes, Const.OBJECT_TYPE_POINT, Const.OBJECT_CLASS_POINT);
        this.element = this.board.select(attributes.anchor);
        this.coordsConstructor(coordinates);

        this.elType = 'point';

        /* Register point at board. */
        this.id = this.board.setId(this, 'P');
        this.board.renderer.drawPoint(this);
        this.board.finalizeAdding(this);

        this.createGradient();
        this.createLabel();

    };

    /**
     * Inherits here from {@link JXG.GeometryElement}.
     */
    JXG.Point.prototype = new GeometryElement();
    Type.copyPrototypeMethods(JXG.Point, CoordsElement, 'coordsConstructor');

    JXG.extend(JXG.Point.prototype, /** @lends JXG.Point.prototype */ {
        /**
         * Checks whether (x,y) is near the point.
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @returns {Boolean} True if (x,y) is near the point, False otherwise.
         * @private
         */
        hasPoint: function (x, y) {
            var coordsScr = this.coords.scrCoords, r,
                prec, type,
                unit = Type.evaluate(this.visProp.sizeunit);

            if (Type.isObject(Type.evaluate(this.visProp.precision))) {
                type = this.board._inputDevice;
                prec = Type.evaluate(this.visProp.precision[type]);
            } else {
                // 'inherit'
                prec = this.board.options.precision.hasPoint;
            }
            r = parseFloat(Type.evaluate(this.visProp.size));
            if (unit === 'user') {
                r *= Math.sqrt(this.board.unitX * this.board.unitY);
            }

            r += parseFloat(Type.evaluate(this.visProp.strokewidth)) * 0.5;
            if (r < prec) {
                r = prec;
            }

            return ((Math.abs(coordsScr[1] - x) < r + 2) && (Math.abs(coordsScr[2] - y) < r + 2));
        },

        /**
         * Updates the position of the point.
         */
        update: function (fromParent) {
            if (!this.needsUpdate) {
                return this;
            }

            this.updateCoords(fromParent);

            if (Type.evaluate(this.visProp.trace)) {
                this.cloneToBackground(true);
            }

            return this;
        },

        /**
         * Applies the transformations of the element to {@link JXG.Point#baseElement}.
         * Point transformations are relative to a base element.
         * @param {Boolean} fromParent True if the drag comes from a child element. This is the case if a line
         *    through two points is dragged. Otherwise, the element is the drag element and we apply the
         *    the inverse transformation to the baseElement if is different from the element.
         * @returns {JXG.CoordsElement} Reference to this object.
         */
        updateTransform: function (fromParent) {
            var c, i;

            if (this.transformations.length === 0 || this.baseElement === null) {
                return this;
            }

            if (this === this.baseElement) {
                // Case of bindTo
                c = this.transformations[0].apply(this.baseElement, 'self');
                this.coords.setCoordinates(Const.COORDS_BY_USER, c);
            } else {
                c = this.transformations[0].apply(this.baseElement);
            }
            this.coords.setCoordinates(Const.COORDS_BY_USER, c);

            for (i = 1; i < this.transformations.length; i++) {
                this.coords.setCoordinates(Const.COORDS_BY_USER, this.transformations[i].apply(this));
            }
            return this;
        },

        /**
         * Calls the renderer to update the drawing.
         * @private
         */
        updateRenderer: function () {
            this.updateRendererGeneric('updatePoint');
            return this;
        },

        // documented in JXG.GeometryElement
        bounds: function () {
            return this.coords.usrCoords.slice(1).concat(this.coords.usrCoords.slice(1));
        },

        /**
         * Convert the point to intersection point and update the construction.
         * To move the point visual onto the intersection, a call of board update is necessary.
         *
         * @param {String|Object} el1, el2, i, j The intersecting objects and the numbers.
         **/
        makeIntersection: function (el1, el2, i, j) {
            var func;

            el1 = this.board.select(el1);
            el2 = this.board.select(el2);

            func = Geometry.intersectionFunction(this.board, el1, el2, i, j,
                    Type.evaluate(this.visProp.alwaysintersect));
            this.addConstraint([func]);

            try {
                el1.addChild(this);
                el2.addChild(this);
            } catch (e) {
                throw new Error("JSXGraph: Can't create 'intersection' with parent types '" +
                    (typeof el1) + "' and '" + (typeof el2) + "'.");
            }

            this.type = Const.OBJECT_TYPE_INTERSECTION;
            this.elType = 'intersection';
            this.parents = [el1.id, el2.id, i, j];

            this.generatePolynomial = function () {
                var poly1 = el1.generatePolynomial(this),
                    poly2 = el2.generatePolynomial(this);

                if ((poly1.length === 0) || (poly2.length === 0)) {
                    return [];
                }

                return [poly1[0], poly2[0]];
            };

            this.prepareUpdate().update();
        },

        /**
         * Set the style of a point.
         * Used for GEONExT import and should not be used to set the point's face and size.
         * @param {Number} i Integer to determine the style.
         * @private
         */
        setStyle: function (i) {
            var facemap = [
                // 0-2
                'cross', 'cross', 'cross',
                // 3-6
                'circle', 'circle', 'circle', 'circle',
                // 7-9
                'square', 'square', 'square',
                // 10-12
                'plus', 'plus', 'plus'
            ], sizemap = [
                // 0-2
                2, 3, 4,
                // 3-6
                1, 2, 3, 4,
                // 7-9
                2, 3, 4,
                // 10-12
                2, 3, 4
            ];

            this.visProp.face = facemap[i];
            this.visProp.size = sizemap[i];

            this.board.renderer.changePointStyle(this);
            return this;
        },

        /**
         * @deprecated Use JXG#normalizePointFace instead
         * @param s
         * @returns {*}
         */
        normalizeFace: function (s) {
            JXG.deprecated('Point.normalizeFace()', 'JXG.normalizePointFace()');
            return Options.normalizePointFace(s);
        },

        /**
         * Set the face of a point element.
         * @param {String} f String which determines the face of the point. See {@link JXG.GeometryElement#face} for a list of available faces.
         * @see JXG.GeometryElement#face
         * @deprecated Use setAttribute()
         */
        face: function (f) {
            JXG.deprecated('Point.face()', 'Point.setAttribute()');
            this.setAttribute({face: f});
        },

        /**
         * Set the size of a point element
         * @param {Number} s Integer which determines the size of the point.
         * @see JXG.GeometryElement#size
         * @deprecated Use setAttribute()
         */
        size: function (s) {
            JXG.deprecated('Point.size()', 'Point.setAttribute()');
            this.setAttribute({size: s});
        },

        /**
         * Test if the point is on (is incident with) element "el".
         *
         * @param {JXG.GeometryElement} el
         * @param {Number} tol
         * @returns {Boolean}
         *
         * @example
         * var circ = board.create('circle', [[-2, -2], 1]);
         * var seg = board.create('segment', [[-1, -3], [0,0]]);
         * var line = board.create('line', [[1, 3], [2, -2]]);
         * var po = board.create('point', [-1, 0], {color: 'blue'});
         * var curve = board.create('functiongraph', ['sin(x)'], {strokeColor: 'blue'});
         * var pol = board.create('polygon', [[2,2], [4,2], [4,3]], {strokeColor: 'blue'});
         *
         * var point = board.create('point', [-1, 1], {
         *               attractors: [line, seg, circ, po, curve, pol],
         *               attractorDistance: 0.2
         *             });
         *
         * var txt = board.create('text', [-4, 3, function() {
         *              return 'point on line: ' + point.isOn(line) + '<br>' +
         *                 'point on seg: ' + point.isOn(seg) + '<br>' +
         *                 'point on circ = ' + point.isOn(circ) + '<br>' +
         *                 'point on point = ' + point.isOn(po) + '<br>' +
         *                 'point on curve = ' + point.isOn(curve) + '<br>' +
         *                 'point on polygon = ' + point.isOn(pol) + '<br>';
         * }]);
         *
         * </pre><div id="JXG6c7d7404-758a-44eb-802c-e9644b9fab71" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG6c7d7404-758a-44eb-802c-e9644b9fab71',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var circ = board.create('circle', [[-2, -2], 1]);
         *     var seg = board.create('segment', [[-1, -3], [0,0]]);
         *     var line = board.create('line', [[1, 3], [2, -2]]);
         *     var po = board.create('point', [-1, 0], {color: 'blue'});
         *     var curve = board.create('functiongraph', ['sin(x)'], {strokeColor: 'blue'});
         *     var pol = board.create('polygon', [[2,2], [4,2], [4,3]], {strokeColor: 'blue'});
         *
         *     var point = board.create('point', [-1, 1], {
         *                   attractors: [line, seg, circ, po, curve, pol],
         *                   attractorDistance: 0.2
         *                 });
         *
         *     var txt = board.create('text', [-4, 3, function() {
         *             return 'point on line: ' + point.isOn(line) + '<br>' +
         *                     'point on seg: ' + point.isOn(seg) + '<br>' +
         *                     'point on circ = ' + point.isOn(circ) + '<br>' +
         *                     'point on point = ' + point.isOn(po) + '<br>' +
         *                     'point on curve = ' + point.isOn(curve) + '<br>' +
         *                     'point on polygon = ' + point.isOn(pol) + '<br>';
         *     }]);
         *
         *     })();
         *
         * </script><pre>
         *
         */
        isOn: function(el, tol) {
            var arr, crds;

            tol = tol || Mat.eps;

            if (Type.isPoint(el)) {
                return this.Dist(el) < tol;
            } else if (el.elementClass === Const.OBJECT_CLASS_LINE) {
                if (el.elType === 'segment' && !Type.evaluate(this.visProp.alwaysintersect)) {
                    arr = JXG.Math.Geometry.projectCoordsToSegment(
                                this.coords.usrCoords,
                                el.point1.coords.usrCoords,
                                el.point2.coords.usrCoords);
                    if (arr[1] >= 0 && arr[1] <= 1 &&
                        Geometry.distPointLine(this.coords.usrCoords, el.stdform) < tol) {
                           return true;
                    } else {
                        return false;
                    }
                } else {
                    return Geometry.distPointLine(this.coords.usrCoords, el.stdform) < tol;
                }
            } else if (el.elementClass === Const.OBJECT_CLASS_CIRCLE) {
                if (Type.evaluate(el.visProp.hasinnerpoints)) {
                    return this.Dist(el.center) < el.Radius() + tol;
                }
                return Math.abs(this.Dist(el.center) - el.Radius()) < tol;
            } else if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
                crds = Geometry.projectPointToCurve(this, el, this.board)[0];
                return Geometry.distance(this.coords.usrCoords, crds.usrCoords, 3) < tol;
            } else if (el.type === Const.OBJECT_TYPE_POLYGON) {
                if (Type.evaluate(el.visProp.hasinnerpoints)) {
                    if (el.pnpoly(this.coords.usrCoords[1], this.coords.usrCoords[2], JXG.COORDS_BY_USER)) {
                        return true;
                    }
                }
                arr = Geometry.projectCoordsToPolygon(this.coords.usrCoords, el);
                return Geometry.distance(this.coords.usrCoords, arr, 3) < tol;
            } else if (el.type === Const.OBJECT_TYPE_TURTLE) {
                crds = Geometry.projectPointToTurtle(this, el, this.board);
                return Geometry.distance(this.coords.usrCoords, crds.usrCoords, 3) < tol;
            }

            // TODO: Arc, Sector
            return false;
        },

        // Already documented in GeometryElement
        cloneToBackground: function () {
            var copy = {};

            copy.id = this.id + 'T' + this.numTraces;
            this.numTraces += 1;

            copy.coords = this.coords;
            copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true);
            copy.visProp.layer = this.board.options.layer.trace;
            copy.elementClass = Const.OBJECT_CLASS_POINT;
            copy.board = this.board;
            Type.clearVisPropOld(copy);

            copy.visPropCalc = {
                visible: Type.evaluate(copy.visProp.visible)
            };

            this.board.renderer.drawPoint(copy);
            this.traces[copy.id] = copy.rendNode;

            return this;
        }

    });

    /**
     * @class This element is used to provide a constructor for a general point. A free point is created if the given parent elements are all numbers
     * and the property fixed is not set or set to false. If one or more parent elements is not a number but a string containing a GEONE<sub>x</sub>T
     * constraint or a function the point will be considered as constrained). That means that the user won't be able to change the point's
     * position directly.
     * @pseudo
     * @description
     * @name Point
     * @augments JXG.Point
     * @constructor
     * @type JXG.Point
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {Number,string,function_Number,string,function_Number,string,function} z_,x,y Parent elements can be two or three elements of type number, a string containing a GEONE<sub>x</sub>T
     * constraint, or a function which takes no parameter and returns a number. Every parent element determines one coordinate. If a coordinate is
     * given by a number, the number determines the initial position of a free point. If given by a string or a function that coordinate will be constrained
     * that means the user won't be able to change the point's position directly by mouse because it will be calculated automatically depending on the string
     * or the function's return value. If two parent elements are given the coordinates will be interpreted as 2D affine Euclidean coordinates, if three such
     * parent elements are given they will be interpreted as homogeneous coordinates.
     * @param {JXG.Point_JXG.Transformation_Array} Point,Transformation A point can also be created providing a transformation or an array of transformations.
     * The resulting point is a clone of the base point transformed by the given Transformation. {@see JXG.Transformation}.
     *
     * @example
     * // Create a free point using affine Euclidean coordinates
     * var p1 = board.create('point', [3.5, 2.0]);
     * </pre><div class="jxgbox" id="JXG672f1764-7dfa-4abc-a2c6-81fbbf83e44b" style="width: 200px; height: 200px;"></div>
     * <script type="text/javascript">
     *   var board = JXG.JSXGraph.initBoard('JXG672f1764-7dfa-4abc-a2c6-81fbbf83e44b', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var p1 = board.create('point', [3.5, 2.0]);
     * </script><pre>
     * @example
     * // Create a constrained point using anonymous function
     * var p2 = board.create('point', [3.5, function () { return p1.X(); }]);
     * </pre><div class="jxgbox" id="JXG4fd4410c-3383-4e80-b1bb-961f5eeef224" style="width: 200px; height: 200px;"></div>
     * <script type="text/javascript">
     *   var fpex1_board = JXG.JSXGraph.initBoard('JXG4fd4410c-3383-4e80-b1bb-961f5eeef224', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var fpex1_p1 = fpex1_board.create('point', [3.5, 2.0]);
     *   var fpex1_p2 = fpex1_board.create('point', [3.5, function () { return fpex1_p1.X(); }]);
     * </script><pre>
     * @example
     * // Create a point using transformations
     * var trans = board.create('transform', [2, 0.5], {type:'scale'});
     * var p3 = board.create('point', [p2, trans]);
     * </pre><div class="jxgbox" id="JXG630afdf3-0a64-46e0-8a44-f51bd197bb8d" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var fpex2_board = JXG.JSXGraph.initBoard('JXG630afdf3-0a64-46e0-8a44-f51bd197bb8d', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var fpex2_trans = fpex2_board.create('transform', [2, 0.5], {type:'scale'});
     *   var fpex2_p2 = fpex2_board.create('point', [3.5, 2.0]);
     *   var fpex2_p3 = fpex2_board.create('point', [fpex2_p2, fpex2_trans]);
     * </script><pre>
     */
    JXG.createPoint = function (board, parents, attributes) {
        var el, attr;

        attr = Type.copyAttributes(attributes, board.options, 'point');
        el = CoordsElement.create(JXG.Point, board, parents, attr);
        if (!el) {
            throw new Error("JSXGraph: Can't create point with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                    "\nPossible parent types: [x,y], [z,x,y], [element,transformation]");
        }

        return el;
    };

    /**
     * @class This element is used to provide a constructor for a glider point.
     * @pseudo
     * @description A glider is a point which lives on another geometric element like a line, circle, curve, turtle.
     * @name Glider
     * @augments JXG.Point
     * @constructor
     * @type JXG.Point
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {Number_Number_Number_JXG.GeometryElement} z_,x_,y_,GlideObject Parent elements can be two or three elements of type number and the object the glider lives on.
     * The coordinates are completely optional. If not given the origin is used. If you provide two numbers for coordinates they will be interpreted as affine Euclidean
     * coordinates, otherwise they will be interpreted as homogeneous coordinates. In any case the point will be projected on the glide object.
     * @example
     * // Create a glider with user defined coordinates. If the coordinates are not on
     * // the circle (like in this case) the point will be projected onto the circle.
     * var p1 = board.create('point', [2.0, 2.0]);
     * var c1 = board.create('circle', [p1, 2.0]);
     * var p2 = board.create('glider', [2.0, 1.5, c1]);
     * </pre><div class="jxgbox" id="JXG4f65f32f-e50a-4b50-9b7c-f6ec41652930" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var gpex1_board = JXG.JSXGraph.initBoard('JXG4f65f32f-e50a-4b50-9b7c-f6ec41652930', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var gpex1_p1 = gpex1_board.create('point', [2.0, 2.0]);
     *   var gpex1_c1 = gpex1_board.create('circle', [gpex1_p1, 2.0]);
     *   var gpex1_p2 = gpex1_board.create('glider', [2.0, 1.5, gpex1_c1]);
     * </script><pre>
     * @example
     * // Create a glider with default coordinates (1,0,0). Same premises as above.
     * var p1 = board.create('point', [2.0, 2.0]);
     * var c1 = board.create('circle', [p1, 2.0]);
     * var p2 = board.create('glider', [c1]);
     * </pre><div class="jxgbox" id="JXG4de7f181-631a-44b1-a12f-bc4d995609e8" style="width: 200px; height: 200px;"></div>
     * <script type="text/javascript">
     *   var gpex2_board = JXG.JSXGraph.initBoard('JXG4de7f181-631a-44b1-a12f-bc4d995609e8', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var gpex2_p1 = gpex2_board.create('point', [2.0, 2.0]);
     *   var gpex2_c1 = gpex2_board.create('circle', [gpex2_p1, 2.0]);
     *   var gpex2_p2 = gpex2_board.create('glider', [gpex2_c1]);
     * </script><pre>
     *@example
     * //animate example 2
     * var p1 = board.create('point', [2.0, 2.0]);
     * var c1 = board.create('circle', [p1, 2.0]);
     * var p2 = board.create('glider', [c1]);
     * var button1 = board.create('button', [1, 7, 'start animation',function(){p2.startAnimation(1,4)}]);
     * var button2 = board.create('button', [1, 5, 'stop animation',function(){p2.stopAnimation()}]);
     * </pre><div class="jxgbox" id="JXG4de7f181-631a-44b1-a12f-bc4d133709e8" style="width: 200px; height: 200px;"></div>
     * <script type="text/javascript">
     *   var gpex3_board = JXG.JSXGraph.initBoard('JXG4de7f181-631a-44b1-a12f-bc4d133709e8', {boundingbox: [-1, 10, 10, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var gpex3_p1 = gpex3_board.create('point', [2.0, 2.0]);
     *   var gpex3_c1 = gpex3_board.create('circle', [gpex3_p1, 2.0]);
     *   var gpex3_p2 = gpex3_board.create('glider', [gpex3_c1]);
     *   gpex3_board.create('button', [1, 7, 'start animation',function(){gpex3_p2.startAnimation(1,4)}]);
     *   gpex3_board.create('button', [1, 5, 'stop animation',function(){gpex3_p2.stopAnimation()}]);
     * </script><pre>
     */
    JXG.createGlider = function (board, parents, attributes) {
        var el, coords,
            attr = Type.copyAttributes(attributes, board.options, 'glider');

        if (parents.length === 1) {
            coords = [0, 0];
        } else {
            coords = parents.slice(0, 2);
        }
        el = board.create('point', coords, attr);

        // eltype is set in here
        el.makeGlider(parents[parents.length - 1]);

        return el;
    };


    /**
     * @class An intersection point is a point which lives on two JSXGraph elements, i.e. it is one point of the the set
     * consisting of the intersection points of the two elements. The following element types can be (mutually) intersected: line, circle,
     * curve, polygon, polygonal chain.
     *
     * @pseudo
     * @name Intersection
     * @augments JXG.Point
     * @constructor
     * @type JXG.Point
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_Number} el1,el2,i The result will be a intersection point on el1 and el2. i determines the
     * intersection point if two points are available: <ul>
     *   <li>i==0: use the positive square root,</li>
     *   <li>i==1: use the negative square root.</li></ul>
     * @example
     * // Create an intersection point of circle and line
     * var p1 = board.create('point', [2.0, 2.0]);
     * var c1 = board.create('circle', [p1, 2.0]);
     *
     * var p2 = board.create('point', [2.0, 2.0]);
     * var p3 = board.create('point', [2.0, 2.0]);
     * var l1 = board.create('line', [p2, p3]);
     *
     * var i = board.create('intersection', [c1, l1, 0]);
     * </pre><div class="jxgbox" id="JXGe5b0e190-5200-4bc3-b995-b6cc53dc5dc0" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var ipex1_board = JXG.JSXGraph.initBoard('JXGe5b0e190-5200-4bc3-b995-b6cc53dc5dc0', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var ipex1_p1 = ipex1_board.create('point', [4.0, 4.0]);
     *   var ipex1_c1 = ipex1_board.create('circle', [ipex1_p1, 2.0]);
     *   var ipex1_p2 = ipex1_board.create('point', [1.0, 1.0]);
     *   var ipex1_p3 = ipex1_board.create('point', [5.0, 3.0]);
     *   var ipex1_l1 = ipex1_board.create('line', [ipex1_p2, ipex1_p3]);
     *   var ipex1_i = ipex1_board.create('intersection', [ipex1_c1, ipex1_l1, 0]);
     * </script><pre>
     */
    JXG.createIntersectionPoint = function (board, parents, attributes) {
        var el, el1, el2, func, i, j,
            attr = Type.copyAttributes(attributes, board.options, 'intersection');

        // make sure we definitely have the indices
        parents.push(0, 0);

        el1 = board.select(parents[0]);
        el2 = board.select(parents[1]);

        i = parents[2] || 0;
        j = parents[3] || 0;

        el = board.create('point', [0, 0, 0], attr);

        // el.visProp.alwaysintersect is evaluated as late as in the returned function
        func = Geometry.intersectionFunction(board, el1, el2, i, j, el.visProp.alwaysintersect);
        el.addConstraint([func]);

        try {
            el1.addChild(el);
            el2.addChild(el);
        } catch (e) {
            throw new Error("JSXGraph: Can't create 'intersection' with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'.");
        }

        el.type = Const.OBJECT_TYPE_INTERSECTION;
        el.elType = 'intersection';
        el.setParents([el1.id, el2.id]);

        /**
         * Array of length 2 containing the numbers i and j.
         * The intersection point is i-th intersection point.
         * j is unused.
         * @type Array
         * @private
         */
        el.intersectionNumbers = [i, j];
        el.getParents = function() {
            return this.parents.concat(this.intersectionNumbers);
        };

        el.generatePolynomial = function () {
            var poly1 = el1.generatePolynomial(el),
                poly2 = el2.generatePolynomial(el);

            if ((poly1.length === 0) || (poly2.length === 0)) {
                return [];
            }

            return [poly1[0], poly2[0]];
        };

        return el;
    };

    /**
     * @class This element is used to provide a constructor for the "other" intersection point.
     * @pseudo
     * @description An intersection point is a point which lives on two Lines or Circles or one Line and one Circle at the same time, i.e.
     * an intersection point of the two elements. Additionally, one intersection point is provided. The function returns the other intersection point.
     * @name OtherIntersection
     * @augments JXG.Point
     * @constructor
     * @type JXG.Point
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line,JXG.Circle_JXG.Line,JXG.Circle_JXG.Point} el1,el2,p The result will be a intersection point on el1 and el2. i determines the
     * intersection point different from p:
     * @example
     * // Create an intersection point of circle and line
     * var p1 = board.create('point', [2.0, 2.0]);
     * var c1 = board.create('circle', [p1, 2.0]);
     *
     * var p2 = board.create('point', [2.0, 2.0]);
     * var p3 = board.create('point', [2.0, 2.0]);
     * var l1 = board.create('line', [p2, p3]);
     *
     * var i = board.create('intersection', [c1, l1, 0]);
     * var j = board.create('otherintersection', [c1, l1, i]);
     * </pre><div class="jxgbox" id="JXG45e25f12-a1de-4257-a466-27a2ae73614c" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var ipex2_board = JXG.JSXGraph.initBoard('JXG45e25f12-a1de-4257-a466-27a2ae73614c', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var ipex2_p1 = ipex2_board.create('point', [4.0, 4.0]);
     *   var ipex2_c1 = ipex2_board.create('circle', [ipex2_p1, 2.0]);
     *   var ipex2_p2 = ipex2_board.create('point', [1.0, 1.0]);
     *   var ipex2_p3 = ipex2_board.create('point', [5.0, 3.0]);
     *   var ipex2_l1 = ipex2_board.create('line', [ipex2_p2, ipex2_p3]);
     *   var ipex2_i = ipex2_board.create('intersection', [ipex2_c1, ipex2_l1, 0], {name:'D'});
     *   var ipex2_j = ipex2_board.create('otherintersection', [ipex2_c1, ipex2_l1, ipex2_i], {name:'E'});
     * </script><pre>
     */
    JXG.createOtherIntersectionPoint = function (board, parents, attributes) {
        var el, el1, el2, other;

        if (parents.length !== 3 ||
                !Type.isPoint(parents[2]) ||
                (parents[0].elementClass !== Const.OBJECT_CLASS_LINE && parents[0].elementClass !== Const.OBJECT_CLASS_CIRCLE) ||
                (parents[1].elementClass !== Const.OBJECT_CLASS_LINE && parents[1].elementClass !== Const.OBJECT_CLASS_CIRCLE)) {
            // Failure
            throw new Error("JSXGraph: Can't create 'other intersection point' with parent types '" +
                (typeof parents[0]) + "',  '" + (typeof parents[1]) + "'and  '" + (typeof parents[2]) + "'." +
                "\nPossible parent types: [circle|line,circle|line,point]");
        }

        el1 = board.select(parents[0]);
        el2 = board.select(parents[1]);
        other = board.select(parents[2]);

        el = board.create('point', [function () {
            var c = Geometry.meet(el1.stdform, el2.stdform, 0, el1.board);

            if (Math.abs(other.X() - c.usrCoords[1]) > Mat.eps ||
                    Math.abs(other.Y() - c.usrCoords[2]) > Mat.eps ||
                    Math.abs(other.Z() - c.usrCoords[0]) > Mat.eps) {
                return c;
            }

            return Geometry.meet(el1.stdform, el2.stdform, 1, el1.board);
        }], attributes);

        el.type = Const.OBJECT_TYPE_INTERSECTION;
        el.elType = 'otherintersection';
        el.setParents([el1.id, el2.id, other]);

        el1.addChild(el);
        el2.addChild(el);

        el.generatePolynomial = function () {
            var poly1 = el1.generatePolynomial(el),
                poly2 = el2.generatePolynomial(el);

            if ((poly1.length === 0) || (poly2.length === 0)) {
                return [];
            }

            return [poly1[0], poly2[0]];
        };

        return el;
    };

    /**
     * @class This element is used to provide a constructor for the pole point of a line with respect to a conic or a circle.
     * @pseudo
     * @description The pole point is the unique reciprocal relationship of a line with respect to a conic.
     * The lines tangent to the intersections of a conic and a line intersect at the pole point of that line with respect to that conic.
     * A line tangent to a conic has the pole point of that line with respect to that conic as the tangent point.
     * See {@link http://en.wikipedia.org/wiki/Pole_and_polar} for more information on pole and polar.
     * @name PolePoint
     * @augments JXG.Point
     * @constructor
     * @type JXG.Point
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Conic,JXG.Circle_JXG.Point} el1,el2 or
     * @param {JXG.Point_JXG.Conic,JXG.Circle} el1,el2 The result will be the pole point of the line with respect to the conic or the circle.
     * @example
     * // Create the pole point of a line with respect to a conic
     * var p1 = board.create('point', [-1, 2]);
     * var p2 = board.create('point', [ 1, 4]);
     * var p3 = board.create('point', [-1,-2]);
     * var p4 = board.create('point', [ 0, 0]);
     * var p5 = board.create('point', [ 4,-2]);
     * var c1 = board.create('conic',[p1,p2,p3,p4,p5]);
     * var p6 = board.create('point', [-1, 4]);
     * var p7 = board.create('point', [2, -2]);
     * var l1 = board.create('line', [p6, p7]);
     * var p8 = board.create('polepoint', [c1, l1]);
     * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-8018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div>
     * <script type='text/javascript'>
     * var ppex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-8018d0d17a98', {boundingbox: [-3, 5, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     * var ppex1_p1 = ppex1_board.create('point', [-1, 2]);
     * var ppex1_p2 = ppex1_board.create('point', [ 1, 4]);
     * var ppex1_p3 = ppex1_board.create('point', [-1,-2]);
     * var ppex1_p4 = ppex1_board.create('point', [ 0, 0]);
     * var ppex1_p5 = ppex1_board.create('point', [ 4,-2]);
     * var ppex1_c1 = ppex1_board.create('conic',[ppex1_p1,ppex1_p2,ppex1_p3,ppex1_p4,ppex1_p5]);
     * var ppex1_p6 = ppex1_board.create('point', [-1, 4]);
     * var ppex1_p7 = ppex1_board.create('point', [2, -2]);
     * var ppex1_l1 = ppex1_board.create('line', [ppex1_p6, ppex1_p7]);
     * var ppex1_p8 = ppex1_board.create('polepoint', [ppex1_c1, ppex1_l1]);
     * </script><pre>
     * @example
     * // Create the pole point of a line with respect to a circle
     * var p1 = board.create('point', [1, 1]);
     * var p2 = board.create('point', [2, 3]);
     * var c1 = board.create('circle',[p1,p2]);
     * var p3 = board.create('point', [-1, 4]);
     * var p4 = board.create('point', [4, -1]);
     * var l1 = board.create('line', [p3, p4]);
     * var p5 = board.create('polepoint', [c1, l1]);
     * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-9018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div>
     * <script type='text/javascript'>
     * var ppex2_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-9018d0d17a98', {boundingbox: [-3, 7, 7, -3], axis: true, showcopyright: false, shownavigation: false});
     * var ppex2_p1 = ppex2_board.create('point', [1, 1]);
     * var ppex2_p2 = ppex2_board.create('point', [2, 3]);
     * var ppex2_c1 = ppex2_board.create('circle',[ppex2_p1,ppex2_p2]);
     * var ppex2_p3 = ppex2_board.create('point', [-1, 4]);
     * var ppex2_p4 = ppex2_board.create('point', [4, -1]);
     * var ppex2_l1 = ppex2_board.create('line', [ppex2_p3, ppex2_p4]);
     * var ppex2_p5 = ppex2_board.create('polepoint', [ppex2_c1, ppex2_l1]);
     * </script><pre>
     */
    JXG.createPolePoint = function (board, parents, attributes) {
        var el, el1, el2,
            firstParentIsConic, secondParentIsConic,
            firstParentIsLine, secondParentIsLine;

        if (parents.length > 1) {
            firstParentIsConic = (parents[0].type === Const.OBJECT_TYPE_CONIC ||
                parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE);
            secondParentIsConic = (parents[1].type === Const.OBJECT_TYPE_CONIC ||
                parents[1].elementClass === Const.OBJECT_CLASS_CIRCLE);

            firstParentIsLine = (parents[0].elementClass === Const.OBJECT_CLASS_LINE);
            secondParentIsLine = (parents[1].elementClass === Const.OBJECT_CLASS_LINE);
        }

/*        if (parents.length !== 2 || !((
                parents[0].type === Const.OBJECT_TYPE_CONIC ||
                parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE) &&
                parents[1].elementClass === Const.OBJECT_CLASS_LINE ||
                parents[0].elementClass === Const.OBJECT_CLASS_LINE && (
                parents[1].type === Const.OBJECT_TYPE_CONIC ||
                parents[1].elementClass === Const.OBJECT_CLASS_CIRCLE))) {*/
        if (parents.length !== 2 ||
                !((firstParentIsConic && secondParentIsLine) ||
                    (firstParentIsLine && secondParentIsConic))) {
            // Failure
            throw new Error("JSXGraph: Can't create 'pole point' with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent type: [conic|circle,line], [line,conic|circle]");
        }

        if (secondParentIsLine) {
            el1 = board.select(parents[0]);
            el2 = board.select(parents[1]);
        } else {
            el1 = board.select(parents[1]);
            el2 = board.select(parents[0]);
        }

        el = board.create('point',
            [function () {
                var q = el1.quadraticform,
                    s = el2.stdform.slice(0, 3);

                return [JXG.Math.Numerics.det([s, q[1], q[2]]),
                        JXG.Math.Numerics.det([q[0], s, q[2]]),
                        JXG.Math.Numerics.det([q[0], q[1], s])];
            }], attributes);

        el.elType = 'polepoint';
        el.setParents([el1.id, el2.id]);

        el1.addChild(el);
        el2.addChild(el);

        return el;
    };

    JXG.registerElement('point', JXG.createPoint);
    JXG.registerElement('glider', JXG.createGlider);
    JXG.registerElement('intersection', JXG.createIntersectionPoint);
    JXG.registerElement('otherintersection', JXG.createOtherIntersectionPoint);
    JXG.registerElement('polepoint', JXG.createPolePoint);

    return {
        Point: JXG.Point,
        createPoint: JXG.createPoint,
        createGlider: JXG.createGlider,
        createIntersection: JXG.createIntersectionPoint,
        createOtherIntersection: JXG.createOtherIntersectionPoint,
        createPolePoint: JXG.createPolePoint
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/math
 math/geometry
 math/numerics
 math/statistics
 base/constants
 base/coords
 base/element
 utils/type
  elements:
   transform
   point
   ticks
 */

/**
 * @fileoverview The geometry object Line is defined in this file. Line stores all
 * style and functional properties that are required to draw and move a line on
 * a board.
 */

define('base/line',[
    'jxg', 'math/math', 'math/geometry', 'math/numerics', 'math/statistics', 'base/constants', 'base/coords',
    'base/element', 'utils/type', 'base/point'
], function (JXG, Mat, Geometry, Numerics, Statistics, Const, Coords, GeometryElement, Type, Point) {

    "use strict";

    /**
     * The Line class is a basic class for all kind of line objects, e.g. line, arrow, and axis. It is usually defined by two points and can
     * be intersected with some other geometry elements.
     * @class Creates a new basic line object. Do not use this constructor to create a line.
     * Use {@link JXG.Board#create} with
     * type {@link Line}, {@link Arrow}, or {@link Axis} instead.
     * @constructor
     * @augments JXG.GeometryElement
     * @param {String,JXG.Board} board The board the new line is drawn on.
     * @param {Point} p1 Startpoint of the line.
     * @param {Point} p2 Endpoint of the line.
     * @param {Object} attributes Javascript object containing attributes like name, id and colors.
     */
    JXG.Line = function (board, p1, p2, attributes) {
        this.constructor(board, attributes, Const.OBJECT_TYPE_LINE, Const.OBJECT_CLASS_LINE);

        /**
         * Startpoint of the line. You really should not set this field directly as it may break JSXGraph's
         * update system so your construction won't be updated properly.
         * @type JXG.Point
         */
        this.point1 = this.board.select(p1);

        /**
         * Endpoint of the line. Just like {@link JXG.Line.point1} you shouldn't write this field directly.
         * @type JXG.Point
         */
        this.point2 = this.board.select(p2);

        /**
         * Array of ticks storing all the ticks on this line. Do not set this field directly and use
         * {@link JXG.Line#addTicks} and {@link JXG.Line#removeTicks} to add and remove ticks to and from the line.
         * @type Array
         * @see JXG.Ticks
         */
        this.ticks = [];

        /**
         * Reference of the ticks created automatically when constructing an axis.
         * @type JXG.Ticks
         * @see JXG.Ticks
         */
        this.defaultTicks = null;

        /**
         * If the line is the border of a polygon, the polygon object is stored, otherwise null.
         * @type JXG.Polygon
         * @default null
         * @private
         */
        this.parentPolygon = null;

        /* Register line at board */
        this.id = this.board.setId(this, 'L');
        this.board.renderer.drawLine(this);
        this.board.finalizeAdding(this);

        this.elType = 'line';

        /* Add line as child to defining points */
        if (this.point1._is_new) {
            this.addChild(this.point1);
            delete this.point1._is_new;
        } else {
            this.point1.addChild(this);
        }
        if (this.point2._is_new) {
            this.addChild(this.point2);
            delete this.point2._is_new;
        } else {
            this.point2.addChild(this);
        }

        this.inherits.push(this.point1, this.point2);

        this.updateStdform(); // This is needed in the following situation:
        // * the line is defined by three coordinates
        // * and it will have a glider
        // * and board.suspendUpdate() has been called.

        // create Label
        this.createLabel();

        this.methodMap = JXG.deepCopy(this.methodMap, {
            point1: 'point1',
            point2: 'point2',
            getSlope: 'getSlope',
            getRise: 'getRise',
            getYIntersect: 'getRise',
            getAngle: 'getAngle',
            L: 'L',
            length: 'L'
        });
    };

    JXG.Line.prototype = new GeometryElement();

    JXG.extend(JXG.Line.prototype, /** @lends JXG.Line.prototype */ {
        /**
         * Checks whether (x,y) is near the line.
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @returns {Boolean} True if (x,y) is near the line, False otherwise.
         */
        hasPoint: function (x, y) {
            // Compute the stdform of the line in screen coordinates.
            var c = [], s,
                v = [1, x, y],
                vnew,
                p1c, p2c, d, pos, i,
                prec, type,
                sw = Type.evaluate(this.visProp.strokewidth);

            if (Type.isObject(Type.evaluate(this.visProp.precision))) {
                type = this.board._inputDevice;
                prec = Type.evaluate(this.visProp.precision[type]);
            } else {
                // 'inherit'
                prec = this.board.options.precision.hasPoint;
            }
            prec += sw * 0.5;

            c[0] = this.stdform[0] -
                this.stdform[1] * this.board.origin.scrCoords[1] / this.board.unitX +
                this.stdform[2] * this.board.origin.scrCoords[2] / this.board.unitY;
            c[1] = this.stdform[1] / this.board.unitX;
            c[2] = this.stdform[2] / (-this.board.unitY);

            s = Geometry.distPointLine(v, c);
            if (isNaN(s) || s > prec) {
                return false;
            }

            if (Type.evaluate(this.visProp.straightfirst) &&
                    Type.evaluate(this.visProp.straightlast)) {
                return true;
            }

            // If the line is a ray or segment we have to check if the projected point is between P1 and P2.
            p1c = this.point1.coords;
            p2c = this.point2.coords;

            // Project the point orthogonally onto the line
            vnew = [0, c[1], c[2]];
            // Orthogonal line to c through v
            vnew = Mat.crossProduct(vnew, v);
            // Intersect orthogonal line with line
            vnew = Mat.crossProduct(vnew, c);

            // Normalize the projected point
            vnew[1] /= vnew[0];
            vnew[2] /= vnew[0];
            vnew[0] = 1;

            vnew = (new Coords(Const.COORDS_BY_SCREEN, vnew.slice(1), this.board)).usrCoords;
            d = p1c.distance(Const.COORDS_BY_USER, p2c);
            p1c = p1c.usrCoords.slice(0);
            p2c = p2c.usrCoords.slice(0);

            // The defining points are identical
            if (d < Mat.eps) {
                pos = 0;
            } else {
                /*
                 * Handle the cases, where one of the defining points is an ideal point.
                 * d is set to something close to infinity, namely 1/eps.
                 * The ideal point is (temporarily) replaced by a finite point which has
                 * distance d from the other point.
                 * This is accomplished by extracting the x- and y-coordinates (x,y)=:v of the ideal point.
                 * v determines the direction of the line. v is normalized, i.e. set to length 1 by dividing through its length.
                 * Finally, the new point is the sum of the other point and v*d.
                 *
                 */

                // At least one point is an ideal point
                if (d === Number.POSITIVE_INFINITY) {
                    d = 1 / Mat.eps;

                    // The second point is an ideal point
                    if (Math.abs(p2c[0]) < Mat.eps) {
                        d /= Geometry.distance([0, 0, 0], p2c);
                        p2c = [1, p1c[1] + p2c[1] * d, p1c[2] + p2c[2] * d];
                    // The first point is an ideal point
                    } else {
                        d /= Geometry.distance([0, 0, 0], p1c);
                        p1c = [1, p2c[1] + p1c[1] * d, p2c[2] + p1c[2] * d];
                    }
                }
                i = 1;
                d = p2c[i] - p1c[i];

                if (Math.abs(d) < Mat.eps) {
                    i = 2;
                    d = p2c[i] - p1c[i];
                }
                pos = (vnew[i] - p1c[i]) / d;
            }

            if (!Type.evaluate(this.visProp.straightfirst) && pos < 0) {
                return false;
            }

            return !(!Type.evaluate(this.visProp.straightlast) && pos > 1);

        },

        // documented in base/element
        update: function () {
            var funps;

            if (!this.needsUpdate) {
                return this;
            }

            if (this.constrained) {
                if (Type.isFunction(this.funps)) {
                    funps = this.funps();
                    if (funps && funps.length && funps.length === 2) {
                        this.point1 = funps[0];
                        this.point2 = funps[1];
                    }
                } else {
                    if (Type.isFunction(this.funp1)) {
                        funps = this.funp1();
                        if (Type.isPoint(funps)) {
                            this.point1 = funps;
                        } else if (funps && funps.length && funps.length === 2) {
                            this.point1.setPositionDirectly(Const.COORDS_BY_USER, funps);
                        }
                    }

                    if (Type.isFunction(this.funp2)) {
                        funps = this.funp2();
                        if (Type.isPoint(funps)) {
                            this.point2 = funps;
                        } else if (funps && funps.length && funps.length === 2) {
                            this.point2.setPositionDirectly(Const.COORDS_BY_USER, funps);
                        }
                    }
                }
            }

            this.updateSegmentFixedLength();
            this.updateStdform();

            if (Type.evaluate(this.visProp.trace)) {
                this.cloneToBackground(true);
            }

            return this;
        },

        /**
         * Update segments with fixed length and at least one movable point.
         * @private
         */
        updateSegmentFixedLength: function () {
            var d, dnew, d1, d2, drag1, drag2, x, y;

            if (!this.hasFixedLength) {
                return this;
            }

            // Compute the actual length of the segment
            d = this.point1.Dist(this.point2);
            // Determine the length the segment ought to have
            dnew = this.fixedLength();
            // Distances between the two points and their respective
            // position before the update
            d1 = this.fixedLengthOldCoords[0].distance(Const.COORDS_BY_USER, this.point1.coords);
            d2 = this.fixedLengthOldCoords[1].distance(Const.COORDS_BY_USER, this.point2.coords);

            // If the position of the points or the fixed length function has been changed we have to work.
            if (d1 > Mat.eps || d2 > Mat.eps || d !== dnew) {
                drag1 = this.point1.isDraggable &&
                            (this.point1.type !== Const.OBJECT_TYPE_GLIDER) &&
                            !Type.evaluate(this.point1.visProp.fixed);
                drag2 = this.point2.isDraggable &&
                            (this.point2.type !== Const.OBJECT_TYPE_GLIDER) &&
                            !Type.evaluate(this.point2.visProp.fixed);

                // First case: the two points are different
                // Then we try to adapt the point that was not dragged
                // If this point can not be moved (e.g. because it is a glider)
                // we try move the other point
                if (d > Mat.eps) {
                    if ((d1 > d2 && drag2) ||
                            (d1 <= d2 && drag2 && !drag1)) {
                        this.point2.setPositionDirectly(Const.COORDS_BY_USER, [
                            this.point1.X() + (this.point2.X() - this.point1.X()) * dnew / d,
                            this.point1.Y() + (this.point2.Y() - this.point1.Y()) * dnew / d
                        ]);
                        this.point2.fullUpdate();
                    } else if ((d1 <= d2 && drag1) ||
                            (d1 > d2 && drag1 && !drag2)) {
                        this.point1.setPositionDirectly(Const.COORDS_BY_USER, [
                            this.point2.X() + (this.point1.X() - this.point2.X()) * dnew / d,
                            this.point2.Y() + (this.point1.Y() - this.point2.Y()) * dnew / d
                        ]);
                        this.point1.fullUpdate();
                    }
                    // Second case: the two points are identical. In this situation
                    // we choose a random direction.
                } else {
                    x = Math.random() - 0.5;
                    y = Math.random() - 0.5;
                    d = Math.sqrt(x * x + y * y);

                    if (drag2) {
                        this.point2.setPositionDirectly(Const.COORDS_BY_USER, [
                            this.point1.X() + x * dnew / d,
                            this.point1.Y() + y * dnew / d
                        ]);
                        this.point2.fullUpdate();
                    } else if (drag1) {
                        this.point1.setPositionDirectly(Const.COORDS_BY_USER, [
                            this.point2.X() + x * dnew / d,
                            this.point2.Y() + y * dnew / d
                        ]);
                        this.point1.fullUpdate();
                    }
                }
                // Finally, we save the position of the two points.
                this.fixedLengthOldCoords[0].setCoordinates(Const.COORDS_BY_USER, this.point1.coords.usrCoords);
                this.fixedLengthOldCoords[1].setCoordinates(Const.COORDS_BY_USER, this.point2.coords.usrCoords);
            }
            return this;
        },

        /**
         * Updates the stdform derived from the parent point positions.
         * @private
         */
        updateStdform: function () {
            var v = Mat.crossProduct(this.point1.coords.usrCoords, this.point2.coords.usrCoords);

            this.stdform[0] = v[0];
            this.stdform[1] = v[1];
            this.stdform[2] = v[2];
            this.stdform[3] = 0;

            this.normalize();
        },

        /**
         * Uses the boards renderer to update the line.
         * @private
         */
        updateRenderer: function () {
            //var wasReal;

            if (!this.needsUpdate) {
                return this;
            }

            if (this.visPropCalc.visible) {
                // wasReal = this.isReal;
                this.isReal = (!isNaN(this.point1.coords.usrCoords[1] + this.point1.coords.usrCoords[2] +
                        this.point2.coords.usrCoords[1] + this.point2.coords.usrCoords[2]) &&
                        (Mat.innerProduct(this.stdform, this.stdform, 3) >= Mat.eps * Mat.eps));

                if (//wasReal &&
                    !this.isReal) {
                    this.updateVisibility(false);
                }
            }

            if (this.visPropCalc.visible) {
                this.board.renderer.updateLine(this);
            }

            /* Update the label if visible. */
            if (this.hasLabel && this.visPropCalc.visible && this.label &&
                this.label.visPropCalc.visible && this.isReal) {

                this.label.update();
                this.board.renderer.updateText(this.label);
            }

            // Update rendNode display
            this.setDisplayRendNode();
            // if (this.visPropCalc.visible !== this.visPropOld.visible) {
            //     this.setDisplayRendNode(this.visPropCalc.visible);
            //     if (this.hasLabel) {
            //         this.board.renderer.display(this.label, this.label.visPropCalc.visible);
            //     }
            // }

            this.needsUpdate = false;
            return this;
        },

        /**
         * Used to generate a polynomial for a point p that lies on this line, i.e. p is collinear to
         * {@link JXG.Line#point1} and {@link JXG.Line#point2}.
         *
         * @param {JXG.Point} p The point for that the polynomial is generated.
         * @returns {Array} An array containing the generated polynomial.
         * @private
         */
        generatePolynomial: function (p) {
            var u1 = this.point1.symbolic.x,
                u2 = this.point1.symbolic.y,
                v1 = this.point2.symbolic.x,
                v2 = this.point2.symbolic.y,
                w1 = p.symbolic.x,
                w2 = p.symbolic.y;

            /*
             * The polynomial in this case is determined by three points being collinear:
             *
             *      U (u1,u2)      W (w1,w2)                V (v1,v2)
             *  ----x--------------x------------------------x----------------
             *
             *  The collinearity condition is
             *
             *      u2-w2       w2-v2
             *     -------  =  -------           (1)
             *      u1-w1       w1-v1
             *
             * Multiplying (1) with denominators and simplifying is
             *
             *    u2w1 - u2v1 + w2v1 - u1w2 + u1v2 - w1v2 = 0
             */

            return [['(', u2, ')*(', w1, ')-(', u2, ')*(', v1, ')+(', w2, ')*(', v1, ')-(', u1, ')*(', w2, ')+(', u1, ')*(', v2, ')-(', w1, ')*(', v2, ')'].join('')];
        },

        /**
         * Calculates the y intersect of the line.
         * @returns {Number} The y intersect.
         */
        getRise: function () {
            if (Math.abs(this.stdform[2]) >= Mat.eps) {
                return -this.stdform[0] / this.stdform[2];
            }

            return Infinity;
        },

        /**
         * Calculates the slope of the line.
         * @returns {Number} The slope of the line or Infinity if the line is parallel to the y-axis.
         */
        getSlope: function () {
            if (Math.abs(this.stdform[2]) >= Mat.eps) {
                return -this.stdform[1] / this.stdform[2];
            }

            return Infinity;
        },

        /**
         * Determines the angle between the positive x axis and the line.
         * @returns {Number}
         */
        getAngle: function () {
            return Math.atan2(-this.stdform[1], this.stdform[2]);
        },

        /**
         * Determines whether the line is drawn beyond {@link JXG.Line#point1} and
         * {@link JXG.Line#point2} and updates the line.
         * @param {Boolean} straightFirst True if the Line shall be drawn beyond
         * {@link JXG.Line#point1}, false otherwise.
         * @param {Boolean} straightLast True if the Line shall be drawn beyond
         * {@link JXG.Line#point2}, false otherwise.
         * @see #straightFirst
         * @see #straightLast
         * @private
         */
        setStraight: function (straightFirst, straightLast) {
            this.visProp.straightfirst = straightFirst;
            this.visProp.straightlast = straightLast;

            this.board.renderer.updateLine(this);
            return this;
        },

        // documented in geometry element
        getTextAnchor: function () {
            return new Coords(Const.COORDS_BY_USER, [0.5 * (this.point2.X() + this.point1.X()), 0.5 * (this.point2.Y() + this.point1.Y())], this.board);
        },

        /**
         * Adjusts Label coords relative to Anchor. DESCRIPTION
         * @private
         */
        setLabelRelativeCoords: function (relCoords) {
            if (Type.exists(this.label)) {
                this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [relCoords[0], -relCoords[1]], this.board);
            }
        },

        // documented in geometry element
        getLabelAnchor: function () {
            var x, y,
                fs = 0,
                c1 = new Coords(Const.COORDS_BY_USER, this.point1.coords.usrCoords, this.board),
                c2 = new Coords(Const.COORDS_BY_USER, this.point2.coords.usrCoords, this.board),
                ev_sf = Type.evaluate(this.visProp.straightfirst),
                ev_sl = Type.evaluate(this.visProp.straightlast);

            if (ev_sf || ev_sl) {
                Geometry.calcStraight(this, c1, c2, 0);
            }

            c1 = c1.scrCoords;
            c2 = c2.scrCoords;

            if (!Type.exists(this.label)) {
                return new Coords(Const.COORDS_BY_SCREEN, [NaN, NaN], this.board);
            }

            switch (Type.evaluate(this.label.visProp.position)) {
            case 'lft':
            case 'llft':
            case 'ulft':
                if (c1[1] <= c2[1]) {
                    x = c1[1];
                    y = c1[2];
                } else {
                    x = c2[1];
                    y = c2[2];
                }
                break;
            case 'rt':
            case 'lrt':
            case 'urt':
                if (c1[1] > c2[1]) {
                    x = c1[1];
                    y = c1[2];
                } else {
                    x = c2[1];
                    y = c2[2];
                }
                break;
            default:
                x = 0.5 * (c1[1] + c2[1]);
                y = 0.5 * (c1[2] + c2[2]);
            }

            // Correct coordinates if the label seems to be outside of canvas.
            if (ev_sf || ev_sl) {
                if (Type.exists(this.label)) {  // Does not exist during createLabel
                    fs = Type.evaluate(this.label.visProp.fontsize);
                }

                if (Math.abs(x) < Mat.eps) {
                    x = fs;
                } else if (this.board.canvasWidth + Mat.eps > x &&
                            x > this.board.canvasWidth - fs - Mat.eps) {
                    x = this.board.canvasWidth - fs;
                }

                if (Mat.eps + fs > y && y > -Mat.eps) {
                    y = fs;
                } else if (this.board.canvasHeight + Mat.eps > y &&
                            y > this.board.canvasHeight - fs - Mat.eps) {
                    y = this.board.canvasHeight - fs;
                }
            }

            return new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);
        },

        // documented in geometry element
        cloneToBackground: function () {
            var copy = {}, r, s, er;

            copy.id = this.id + 'T' + this.numTraces;
            copy.elementClass = Const.OBJECT_CLASS_LINE;
            this.numTraces++;
            copy.point1 = this.point1;
            copy.point2 = this.point2;

            copy.stdform = this.stdform;

            copy.board = this.board;

            copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true);
            copy.visProp.layer = this.board.options.layer.trace;
            Type.clearVisPropOld(copy);
            copy.visPropCalc = {
                visible: Type.evaluate(copy.visProp.visible)
            };

            s = this.getSlope();
            r = this.getRise();
            copy.getSlope = function () {
                return s;
            };
            copy.getRise = function () {
                return r;
            };

            er = this.board.renderer.enhancedRendering;
            this.board.renderer.enhancedRendering = true;
            this.board.renderer.drawLine(copy);
            this.board.renderer.enhancedRendering = er;
            this.traces[copy.id] = copy.rendNode;

            return this;
        },

        /**
         * Add transformations to this line.
         * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of
         * {@link JXG.Transformation}s.
         * @returns {JXG.Line} Reference to this line object.
         */
        addTransform: function (transform) {
            var i,
                list = Type.isArray(transform) ? transform : [transform],
                len = list.length;

            for (i = 0; i < len; i++) {
                this.point1.transformations.push(list[i]);
                this.point2.transformations.push(list[i]);
            }

            return this;
        },

        // see GeometryElement.js
        snapToGrid: function (pos) {
            var c1, c2, dc, t, ticks,
                x, y, sX, sY;

            if (Type.evaluate(this.visProp.snaptogrid)) {
                if (this.parents.length < 3) {    // Line through two points
                    this.point1.handleSnapToGrid(true, true);
                    this.point2.handleSnapToGrid(true, true);
            } else if (Type.exists(pos)) {       // Free line
                    sX = Type.evaluate(this.visProp.snapsizex);
                    sY = Type.evaluate(this.visProp.snapsizey);

                    c1 = new Coords(Const.COORDS_BY_SCREEN, [pos.Xprev, pos.Yprev], this.board);

                    x = c1.usrCoords[1];
                    y = c1.usrCoords[2];

                    if (sX <= 0 && this.board.defaultAxes && this.board.defaultAxes.x.defaultTicks) {
                        ticks = this.board.defaultAxes.x.defaultTicks;
                        sX = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1);
                    }
                    if (sY <= 0 && this.board.defaultAxes && this.board.defaultAxes.y.defaultTicks) {
                        ticks = this.board.defaultAxes.y.defaultTicks;
                        sY = ticks.ticksDelta * (Type.evaluate(ticks.visProp.minorticks) + 1);
                    }

                    // if no valid snap sizes are available, don't change the coords.
                    if (sX > 0 && sY > 0) {
                        // projectCoordsToLine
                        /*
                        v = [0, this.stdform[1], this.stdform[2]];
                        v = Mat.crossProduct(v, c1.usrCoords);
                        c2 = Geometry.meetLineLine(v, this.stdform, 0, this.board);
                        */
                        c2 = Geometry.projectPointToLine({coords: c1}, this, this.board);

                        dc = Statistics.subtract([1, Math.round(x / sX) * sX, Math.round(y / sY) * sY], c2.usrCoords);
                        t = this.board.create('transform', dc.slice(1), {type: 'translate'});
                        t.applyOnce([this.point1, this.point2]);
                    }
                }
            } else {
                this.point1.handleSnapToGrid(false, true);
                this.point2.handleSnapToGrid(false, true);
            }

            return this;
        },

        // see element.js
        snapToPoints: function () {
            var forceIt = Type.evaluate(this.visProp.snaptopoints);

            if (this.parents.length < 3) {    // Line through two points
                this.point1.handleSnapToPoints(forceIt);
                this.point2.handleSnapToPoints(forceIt);
            }

            return this;
        },

        /**
         * Treat the line as parametric curve in homogeneous coordinates, where the parameter t runs from 0 to 1.
         * First we transform the interval [0,1] to [-1,1].
         * If the line has homogeneous coordinates [c, a, b] = stdform[] then the direction of the line is [b, -a].
         * Now, we take one finite point that defines the line, i.e. we take either point1 or point2
         * (in case the line is not the ideal line).
         * Let the coordinates of that point be [z, x, y].
         * Then, the curve runs linearly from
         * [0, b, -a] (t=-1) to [z, x, y] (t=0)
         * and
         * [z, x, y] (t=0) to [0, -b, a] (t=1)
         *
         * @param {Number} t Parameter running from 0 to 1.
         * @returns {Number} X(t) x-coordinate of the line treated as parametric curve.
         * */
        X: function (t) {
            var x,
                b = this.stdform[2];

            x = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ?
                    this.point1.coords.usrCoords[1] :
                    this.point2.coords.usrCoords[1];

            t = (t - 0.5) * 2;

            return (1 - Math.abs(t)) * x - t * b;
        },

        /**
         * Treat the line as parametric curve in homogeneous coordinates.
         * See {@link JXG.Line#X} for a detailed description.
         * @param {Number} t Parameter running from 0 to 1.
         * @returns {Number} Y(t) y-coordinate of the line treated as parametric curve.
         */
        Y: function (t) {
            var y,
                a = this.stdform[1];

            y = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ?
                    this.point1.coords.usrCoords[2] :
                    this.point2.coords.usrCoords[2];

            t = (t - 0.5) * 2;

            return (1 - Math.abs(t)) * y + t * a;
        },

        /**
         * Treat the line as parametric curve in homogeneous coordinates.
         * See {@link JXG.Line#X} for a detailed description.
         *
         * @param {Number} t Parameter running from 0 to 1.
         * @returns {Number} Z(t) z-coordinate of the line treated as parametric curve.
         */
        Z: function (t) {
            var z = (Math.abs(this.point1.coords.usrCoords[0]) > Mat.eps) ?
                    this.point1.coords.usrCoords[0] :
                    this.point2.coords.usrCoords[0];

            t = (t - 0.5) * 2;

            return (1 - Math.abs(t)) * z;
        },

        /**
         * The distance between the two points defining the line.
         * @returns {Number}
         */
        L: function () {
            return this.point1.Dist(this.point2);
        },

        /**
         * Treat the element  as a parametric curve
         * @private
         */
        minX: function () {
            return 0.0;
        },

        /**
         * Treat the element as parametric curve
         * @private
         */
        maxX: function () {
            return 1.0;
        },

        // documented in geometry element
        bounds: function () {
            var p1c = this.point1.coords.usrCoords,
                p2c = this.point2.coords.usrCoords;

            return [Math.min(p1c[1], p2c[1]), Math.max(p1c[2], p2c[2]), Math.max(p1c[1], p2c[1]), Math.min(p1c[2], p2c[2])];
        },

        // documented in GeometryElement.js
        remove: function () {
            this.removeAllTicks();
            GeometryElement.prototype.remove.call(this);
        },

        // hideElement: function () {
        //     var i;
        //
        //     GeometryElement.prototype.hideElement.call(this);
        //
        //     for (i = 0; i < this.ticks.length; i++) {
        //         this.ticks[i].hideElement();
        //     }
        // },
        //
        // showElement: function () {
        //     var i;
        //     GeometryElement.prototype.showElement.call(this);
        //
        //     for (i = 0; i < this.ticks.length; i++) {
        //         this.ticks[i].showElement();
        //     }
        // }
    });

    /**
     * @class This element is used to provide a constructor for a general line. A general line is given by two points. By setting additional properties
     * a line can be used as an arrow and/or axis.
     * @pseudo
     * @description
     * @name Line
     * @augments JXG.Line
     * @constructor
     * @type JXG.Line
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point,array,function_JXG.Point,array,function} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} or array of
     * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
     * It is possible to provide a function returning an array or a point, instead of providing an array or a point.
     * @param {Number,function_Number,function_Number,function} a,b,c A line can also be created providing three numbers. The line is then described by
     * the set of solutions of the equation <tt>a*z+b*x+c*y = 0</tt>. For all finite points, z is normalized to the value 1.
     * It is possible to provide three functions returning numbers, too.
     * @param {function} f This function must return an array containing three numbers forming the line's homogeneous coordinates.
     * <p>
     * Additionally, a line can be created by providing a line and a transformation (or an array of transformations).
     * Then, the result is a line which is the transformation of the supplied line.
     * @example
     * // Create a line using point and coordinates/
     * // The second point will be fixed and invisible.
     * var p1 = board.create('point', [4.5, 2.0]);
     * var l1 = board.create('line', [p1, [1.0, 1.0]]);
     * </pre><div class="jxgbox" id="JXGc0ae3461-10c4-4d39-b9be-81d74759d122" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var glex1_board = JXG.JSXGraph.initBoard('JXGc0ae3461-10c4-4d39-b9be-81d74759d122', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var glex1_p1 = glex1_board.create('point', [4.5, 2.0]);
     *   var glex1_l1 = glex1_board.create('line', [glex1_p1, [1.0, 1.0]]);
     * </script><pre>
     * @example
     * // Create a line using three coordinates
     * var l1 = board.create('line', [1.0, -2.0, 3.0]);
     * </pre><div class="jxgbox" id="JXGcf45e462-f964-4ba4-be3a-c9db94e2593f" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var glex2_board = JXG.JSXGraph.initBoard('JXGcf45e462-f964-4ba4-be3a-c9db94e2593f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var glex2_l1 = glex2_board.create('line', [1.0, -2.0, 3.0]);
     * </script><pre>
     * @example
     *         // Create a line (l2) as reflection of another line (l1)
     *         // reflection line
     *         var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
     *         var reflect = board.create('transform', [li], {type: 'reflect'});
     *
     *         var l1 = board.create('line', [1,-5,1]);
     *         var l2 = board.create('line', [l1, reflect]);
     *
     * </pre><div id="JXGJXGa00d7dd6-d38c-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGJXGa00d7dd6-d38c-11e7-93b3-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *             // reflection line
     *             var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
     *             var reflect = board.create('transform', [li], {type: 'reflect'});
     *
     *             var l1 = board.create('line', [1,-5,1]);
     *             var l2 = board.create('line', [l1, reflect]);
     *     })();
     *
     * </script><pre>
     *
     * @example
     * var t = board.create('transform', [2, 1.5], {type: 'scale'});
     * var l1 = board.create('line', [1, -5, 1]);
     * var l2 = board.create('line', [l1, t]);
     *
     * </pre><div id="d16d5b58-6338-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('d16d5b58-6338-11e8-9fb9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
     *     var l1 = board.create('line', [1, -5, 1]);
     *     var l2 = board.create('line', [l1, t]);
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * //create line between two points
     * var p1 = board.create('point', [0,0]);
     * var p2 = board.create('point', [2,2]);
     * var l1 = board.create('line', [p1,p2], {straightFirst:false, straightLast:false});
     * </pre><div id="d21d5b58-6338-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('d21d5b58-6338-11e8-9fb9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *             var ex5p1 = board.create('point', [0,0]);
     *             var ex5p2 = board.create('point', [2,2]);
     *             var ex5l1 = board.create('line', [ex5p1,ex5p2], {straightFirst:false, straightLast:false});
     *     })();
     *
     * </script><pre>
     */
    JXG.createLine = function (board, parents, attributes) {
        var ps, el, p1, p2, i, attr,
            c = [],
            doTransform = false,
            constrained = false,
            isDraggable;

        /**
         * The line is defined by two points or coordinates of two points.
         * In the latter case, the points are created.
         */
        if (parents.length === 2) {
            // point 1 given by coordinates
            if (Type.isArray(parents[0]) && parents[0].length > 1) {
                attr = Type.copyAttributes(attributes, board.options, 'line', 'point1');
                p1 = board.create('point', parents[0], attr);
            } else if (Type.isString(parents[0]) || Type.isPoint(parents[0])) {
                p1 =  board.select(parents[0]);
            } else if (Type.isFunction(parents[0]) && Type.isPoint(parents[0]())) {
                p1 = parents[0]();
                constrained = true;
            } else if (Type.isFunction(parents[0]) && parents[0]().length && parents[0]().length >= 2) {
                attr = Type.copyAttributes(attributes, board.options, 'line', 'point1');
                p1 = Point.createPoint(board, parents[0](), attr);
                constrained = true;
            } else if (Type.isObject(parents[0]) && Type.isTransformationOrArray(parents[1])) {
                doTransform = true;
                attr = Type.copyAttributes(attributes, board.options, 'line', 'point1');
                p1 = board.create('point', [parents[0].point1, parents[1]], attr);
            } else {
                throw new Error("JSXGraph: Can't create line with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                    "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
            }

            // point 2 given by coordinates
            if (doTransform) {
                attr = Type.copyAttributes(attributes, board.options, 'line', 'point2');
                p2 = board.create('point', [parents[0].point2, parents[1]], attr);
            } else if (Type.isArray(parents[1]) && parents[1].length > 1) {
                attr = Type.copyAttributes(attributes, board.options, 'line', 'point2');
                p2 = board.create('point', parents[1], attr);
            } else if (Type.isString(parents[1]) || Type.isPoint(parents[1])) {
                p2 =  board.select(parents[1]);
            } else if (Type.isFunction(parents[1]) &&  Type.isPoint(parents[1]()) ) {
                p2 = parents[1]();
                constrained = true;
            } else if (Type.isFunction(parents[1]) && parents[1]().length && parents[1]().length >= 2) {
                attr = Type.copyAttributes(attributes, board.options, 'line', 'point2');
                p2 = Point.createPoint(board, parents[1](), attr);
                constrained = true;
            } else {
                throw new Error("JSXGraph: Can't create line with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                    "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
            }

            attr = Type.copyAttributes(attributes, board.options, 'line');

            el = new JXG.Line(board, p1, p2, attr);

            if (constrained) {
                el.constrained = true;
                el.funp1 = parents[0];
                el.funp2 = parents[1];
            } else if (!doTransform) {
                el.isDraggable = true;
            }

            //if (!el.constrained) {
            el.setParents([p1.id, p2.id]);
            //}

         // Line is defined by three homogeneous coordinates.
         // Also in this case points are created.
        } else if (parents.length === 3) {
            // free line
            isDraggable = true;
            for (i = 0; i < 3; i++) {
                if (Type.isNumber(parents[i])) {
                    // createFunction will just wrap a function around our constant number
                    // that does nothing else but to return that number.
                    c[i] = Type.createFunction(parents[i]);
                } else if (Type.isFunction(parents[i])) {
                    c[i] = parents[i];
                    isDraggable = false;
                } else {
                    throw new Error("JSXGraph: Can't create line with parent types '" +
                        (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
                        "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
                }
            }

            // point 1 is the midpoint between (0,c,-b) and point 2. => point1 is finite.
            attr = Type.copyAttributes(attributes, board.options, 'line', 'point1');
            if (isDraggable) {
                p1 = board.create('point', [
                    c[2]() * c[2]() + c[1]() * c[1](),
                    c[2]() - c[1]() * c[0]() + c[2](),
                    -c[1]() - c[2]() * c[0]() - c[1]()
                ], attr);
            } else {
                p1 = board.create('point', [
                    function () {
                        return (c[2]() * c[2]() + c[1]() * c[1]()) * 0.5;
                    },
                    function () {
                        return (c[2]() - c[1]() * c[0]() + c[2]()) * 0.5;
                    },
                    function () {
                        return (-c[1]() - c[2]() * c[0]() - c[1]()) * 0.5;
                    }], attr);
            }

            // point 2: (b^2+c^2,-ba+c,-ca-b)
            attr = Type.copyAttributes(attributes, board.options, 'line', 'point2');
            if (isDraggable) {
                p2 = board.create('point', [
                    c[2]() * c[2]() + c[1]() * c[1](),
                    -c[1]() * c[0]() + c[2](),
                    -c[2]() * c[0]() - c[1]()
                ], attr);
            } else {
                p2 = board.create('point', [
                    function () {
                        return c[2]() * c[2]() + c[1]() * c[1]();
                    },
                    function () {
                        return -c[1]() * c[0]() + c[2]();
                    },
                    function () {
                        return -c[2]() * c[0]() - c[1]();
                    }], attr);
            }

            // If the line will have a glider and board.suspendUpdate() has been called, we
            // need to compute the initial position of the two points p1 and p2.
            p1.prepareUpdate().update();
            p2.prepareUpdate().update();
            attr = Type.copyAttributes(attributes, board.options, 'line');
            el = new JXG.Line(board, p1, p2, attr);
            // Not yet working, because the points are not draggable.
            el.isDraggable = isDraggable;
            el.setParents([p1, p2]);

        // The parent array contains a function which returns two points.
        } else if (parents.length === 1 && Type.isFunction(parents[0]) && parents[0]().length === 2 &&
                Type.isPoint(parents[0]()[0]) &&
                Type.isPoint(parents[0]()[1])) {
            ps = parents[0]();
            attr = Type.copyAttributes(attributes, board.options, 'line');
            el = new JXG.Line(board, ps[0], ps[1], attr);
            el.constrained = true;
            el.funps = parents[0];
            el.setParents(ps);

        } else if (parents.length === 1 && Type.isFunction(parents[0]) && parents[0]().length === 3 &&
                Type.isNumber(parents[0]()[0]) &&
                Type.isNumber(parents[0]()[1]) &&
                Type.isNumber(parents[0]()[2])) {
            ps = parents[0];

            attr = Type.copyAttributes(attributes, board.options, 'line', 'point1');
            p1 = board.create('point', [
                function () {
                    var c = ps();

                    return [
                        (c[2] * c[2] + c[1] * c[1]) * 0.5,
                        (c[2] - c[1] * c[0] + c[2]) * 0.5,
                        (-c[1] - c[2] * c[0] - c[1]) * 0.5
                    ];
                }], attr);

            attr = Type.copyAttributes(attributes, board.options, 'line', 'point2');
            p2 = board.create('point', [
                function () {
                    var c = ps();

                    return [
                        c[2] * c[2] + c[1] * c[1],
                        -c[1] * c[0] + c[2],
                        -c[2] * c[0] - c[1]
                    ];
                }], attr);

            attr = Type.copyAttributes(attributes, board.options, 'line');
            el = new JXG.Line(board, p1, p2, attr);

            el.constrained = true;
            el.funps = parents[0];
            el.setParents([p1, p2]);

        } else {
            throw new Error("JSXGraph: Can't create line with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]], [a,b,c]");
        }

        return el;
    };

    JXG.registerElement('line', JXG.createLine);

    /**
     * @class This element is used to provide a constructor for a segment.
     * It's strictly spoken just a wrapper for element {@link Line} with {@link Line#straightFirst}
     * and {@link Line#straightLast} properties set to false. If there is a third variable then the
     * segment has a fixed length (which may be a function, too).
     * @pseudo
     * @description
     * @name Segment
     * @augments JXG.Line
     * @constructor
     * @type JXG.Line
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point}
     * or array of numbers describing the
     * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
     * @param {number,function} length (optional) The points are adapted - if possible - such that their distance
     * has this value.
     * @see Line
     * @example
     * // Create a segment providing two points.
     *   var p1 = board.create('point', [4.5, 2.0]);
     *   var p2 = board.create('point', [1.0, 1.0]);
     *   var l1 = board.create('segment', [p1, p2]);
     * </pre><div class="jxgbox" id="JXGd70e6aac-7c93-4525-a94c-a1820fa38e2f" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var slex1_board = JXG.JSXGraph.initBoard('JXGd70e6aac-7c93-4525-a94c-a1820fa38e2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var slex1_p1 = slex1_board.create('point', [4.5, 2.0]);
     *   var slex1_p2 = slex1_board.create('point', [1.0, 1.0]);
     *   var slex1_l1 = slex1_board.create('segment', [slex1_p1, slex1_p2]);
     * </script><pre>
     *
     * @example
     * // Create a segment providing two points.
     *   var p1 = board.create('point', [4.0, 1.0]);
     *   var p2 = board.create('point', [1.0, 1.0]);
     *   var l1 = board.create('segment', [p1, p2]);
     *   var p3 = board.create('point', [4.0, 2.0]);
     *   var p4 = board.create('point', [1.0, 2.0]);
     *   var l2 = board.create('segment', [p3, p4, 3]);
     *   var p5 = board.create('point', [4.0, 3.0]);
     *   var p6 = board.create('point', [1.0, 4.0]);
     *   var l3 = board.create('segment', [p5, p6, function(){ return l1.L();} ]);
     * </pre><div class="jxgbox" id="JXG617336ba-0705-4b2b-a236-c87c28ef25be" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var slex2_board = JXG.JSXGraph.initBoard('JXG617336ba-0705-4b2b-a236-c87c28ef25be', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var slex2_p1 = slex2_board.create('point', [4.0, 1.0]);
     *   var slex2_p2 = slex2_board.create('point', [1.0, 1.0]);
     *   var slex2_l1 = slex2_board.create('segment', [slex2_p1, slex2_p2]);
     *   var slex2_p3 = slex2_board.create('point', [4.0, 2.0]);
     *   var slex2_p4 = slex2_board.create('point', [1.0, 2.0]);
     *   var slex2_l2 = slex2_board.create('segment', [slex2_p3, slex2_p4, 3]);
     *   var slex2_p5 = slex2_board.create('point', [4.0, 2.0]);
     *   var slex2_p6 = slex2_board.create('point', [1.0, 2.0]);
     *   var slex2_l3 = slex2_board.create('segment', [slex2_p5, slex2_p6, function(){ return slex2_l1.L();}]);
     * </script><pre>
     *
     */
    JXG.createSegment = function (board, parents, attributes) {
        var el, attr;

        attributes.straightFirst = false;
        attributes.straightLast = false;
        attr = Type.copyAttributes(attributes, board.options, 'segment');

        el = board.create('line', parents.slice(0, 2), attr);

        if (parents.length === 3) {
            el.hasFixedLength = true;

            if (Type.isNumber(parents[2])) {
                el.fixedLength = function () {
                    return parents[2];
                };
            } else if (Type.isFunction(parents[2])) {
                el.fixedLength = parents[2];
            } else {
                throw new Error("JSXGraph: Can't create segment with third parent type '" +
                    (typeof parents[2]) + "'." +
                    "\nPossible third parent types: number or function");
            }

            el.getParents = function() {
                return this.parents.concat(this.fixedLength());
            };

            el.fixedLengthOldCoords = [];
            el.fixedLengthOldCoords[0] = new Coords(Const.COORDS_BY_USER, el.point1.coords.usrCoords.slice(1, 3), board);
            el.fixedLengthOldCoords[1] = new Coords(Const.COORDS_BY_USER, el.point2.coords.usrCoords.slice(1, 3), board);
        }

        el.elType = 'segment';

        return el;
    };

    JXG.registerElement('segment', JXG.createSegment);

    /**
     * @class This element is used to provide a constructor for arrow, which is just a wrapper for element
     * {@link Line} with {@link Line#straightFirst}
     * and {@link Line#straightLast} properties set to false and {@link Line#lastArrow} set to true.
     * @pseudo
     * @description
     * @name Arrow
     * @augments JXG.Line
     * @constructor
     * @type JXG.Line
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} or array of numbers describing the
     * coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
     * @param {Number_Number_Number} a,b,c A line can also be created providing three numbers. The line is then described by the set of solutions
     * of the equation <tt>a*x+b*y+c*z = 0</tt>.
     * @see Line
     * @example
     * // Create an arrow providing two points.
     *   var p1 = board.create('point', [4.5, 2.0]);
     *   var p2 = board.create('point', [1.0, 1.0]);
     *   var l1 = board.create('arrow', [p1, p2]);
     * </pre><div class="jxgbox" id="JXG1d26bd22-7d6d-4018-b164-4c8bc8d22ccf" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var alex1_board = JXG.JSXGraph.initBoard('JXG1d26bd22-7d6d-4018-b164-4c8bc8d22ccf', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var alex1_p1 = alex1_board.create('point', [4.5, 2.0]);
     *   var alex1_p2 = alex1_board.create('point', [1.0, 1.0]);
     *   var alex1_l1 = alex1_board.create('arrow', [alex1_p1, alex1_p2]);
     * </script><pre>
     */
    JXG.createArrow = function (board, parents, attributes) {
        var el, attr;

        attributes.straightFirst = false;
        attributes.straightLast = false;
        attr = Type.copyAttributes(attributes, board.options, 'arrow');
        el = board.create('line', parents, attr);
        //el.setArrow(false, true);
        el.type = Const.OBJECT_TYPE_VECTOR;
        el.elType = 'arrow';

        return el;
    };

    JXG.registerElement('arrow', JXG.createArrow);

    /**
     * @class This element is used to provide a constructor for an axis. It's strictly spoken just a wrapper for element {@link Line} with {@link Line#straightFirst}
     * and {@link Line#straightLast} properties set to true. Additionally {@link Line#lastArrow} is set to true and default {@link Ticks} will be created.
     * @pseudo
     * @description
     * @name Axis
     * @augments JXG.Line
     * @constructor
     * @type JXG.Line
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point,array_JXG.Point,array} point1,point2 Parent elements can be two elements either of type {@link JXG.Point} or array of numbers describing the
     * coordinates of a point. In the latter case, the point will be constructed automatically as a fixed invisible point.
     * @param {Number_Number_Number} a,b,c A line can also be created providing three numbers. The line is then described by the set of solutions
     * of the equation <tt>a*x+b*y+c*z = 0</tt>.
     * @example
     * // Create an axis providing two coord pairs.
     *   var l1 = board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
     * </pre><div class="jxgbox" id="JXG4f414733-624c-42e4-855c-11f5530383ae" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var axex1_board = JXG.JSXGraph.initBoard('JXG4f414733-624c-42e4-855c-11f5530383ae', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var axex1_l1 = axex1_board.create('axis', [[0.0, 1.0], [1.0, 1.3]]);
     * </script><pre>
     */
    JXG.createAxis = function (board, parents, attributes) {
        var attr, attr_ticks, el, els, dist;

        // Arrays or points, that is all we need.
        if ((Type.isArray(parents[0]) || Type.isPoint(parents[0])) && (Type.isArray(parents[1]) || Type.isPoint(parents[1]))) {

            // Create line
            attr = Type.copyAttributes(attributes, board.options, 'axis');
            el = board.create('line', parents, attr);
            el.type = Const.OBJECT_TYPE_AXIS;
            el.isDraggable = false;
            el.point1.isDraggable = false;
            el.point2.isDraggable = false;

            for (els in el.ancestors) {
                if (el.ancestors.hasOwnProperty(els)) {
                    el.ancestors[els].type = Const.OBJECT_TYPE_AXISPOINT;
                }
            }

            // Create ticks
            attr_ticks = Type.copyAttributes(attributes, board.options, 'axis', 'ticks');
            if (Type.exists(attr_ticks.ticksdistance)) {
                dist = attr_ticks.ticksdistance;
            } else if (Type.isArray(attr_ticks.ticks)) {
                dist = attr_ticks.ticks;
            } else {
                dist = 1.0;
            }

            /**
             * The ticks attached to the axis.
             * @memberOf Axis.prototype
             * @name defaultTicks
             * @type JXG.Ticks
             */
            el.defaultTicks = board.create('ticks', [el, dist], attr_ticks);
            el.defaultTicks.dump = false;
            el.elType = 'axis';
            el.subs = {
                ticks: el.defaultTicks
            };
            el.inherits.push(el.defaultTicks);

        } else {
            throw new Error("JSXGraph: Can't create axis with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]");
        }

        return el;
    };

    JXG.registerElement('axis', JXG.createAxis);

    /**
     * @class With the element tangent the slope of a line, circle, or curve in a certain point can be visualized. A tangent is always constructed
     * by a glider on a line, circle, or curve and describes the tangent in the glider point on that line, circle, or curve.
     * @pseudo
     * @description
     * @name Tangent
     * @augments JXG.Line
     * @constructor
     * @type JXG.Line
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {Glider} g A glider on a line, circle, or curve.
     * @example
     * // Create a tangent providing a glider on a function graph
     *   var c1 = board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
     *   var g1 = board.create('glider', [0.6, 1.2, c1]);
     *   var t1 = board.create('tangent', [g1]);
     * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-4018d0d17a98" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var tlex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-4018d0d17a98', {boundingbox: [-6, 6, 6, -6], axis: true, showcopyright: false, shownavigation: false});
     *   var tlex1_c1 = tlex1_board.create('curve', [function(t){return t},function(t){return t*t*t;}]);
     *   var tlex1_g1 = tlex1_board.create('glider', [0.6, 1.2, tlex1_c1]);
     *   var tlex1_t1 = tlex1_board.create('tangent', [tlex1_g1]);
     * </script><pre>
     */
    JXG.createTangent = function (board, parents, attributes) {
        var p, c, j, el, tangent;

        // One argument: glider on line, circle or curve
        if (parents.length === 1) {
            p = parents[0];
            c = p.slideObject;
        // Two arguments: (point,F"|conic) or (line|curve|circle|conic,point). // Not yet: curve!
        } else if (parents.length === 2) {
            // In fact, for circles and conics it is the polar
            if (Type.isPoint(parents[0])) {
                p = parents[0];
                c = parents[1];
            } else if (Type.isPoint(parents[1])) {
                c = parents[0];
                p = parents[1];
            } else {
                throw new Error("JSXGraph: Can't create tangent with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                    "\nPossible parent types: [glider], [point,line|curve|circle|conic]");
            }
        } else {
            throw new Error("JSXGraph: Can't create tangent with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [glider], [point,line|curve|circle|conic]");
        }

        if (c.elementClass === Const.OBJECT_CLASS_LINE) {
            tangent = board.create('line', [c.point1, c.point2], attributes);
            tangent.glider = p;
        } else if (c.elementClass === Const.OBJECT_CLASS_CURVE && c.type !== Const.OBJECT_TYPE_CONIC) {
            if (Type.evaluate(c.visProp.curvetype) !== 'plot') {
                tangent = board.create('line', [
                    function () {
                        var g = c.X,
                            f = c.Y;
                        return -p.X() * Numerics.D(f)(p.position) + p.Y() * Numerics.D(g)(p.position);
                    },
                    function () {
                        return Numerics.D(c.Y)(p.position);
                    },
                    function () {
                        return -Numerics.D(c.X)(p.position);
                    }
                ], attributes);

                p.addChild(tangent);
                // this is required for the geogebra reader to display a slope
                tangent.glider = p;
            } else {  // curveType 'plot'
                // In case of bezierDegree == 1:
                // Find two points p1, p2 enclosing the glider.
                // Then the equation of the line segment is: 0 = y*(x1-x2) + x*(y2-y1) + y1*x2-x1*y2,
                // which is the cross product of p1 and p2.
                //
                // In case of bezieDegree === 3:
                // The slope dy / dx of the tangent is determined. Then the
                // tangent is computed as cross product between
                // the glider p and [1, p.X() + dx, p.Y() + dy]
                //
                tangent = board.create('line', [
                    function () {
                        var i = Math.floor(p.position),
                            p1, p2, t, A, B, C, D, dx, dy, d;

                        if (c.bezierDegree === 1) {
                            if (i === c.numberPoints - 1) {
                                i--;
                            }
                        } else if (c.bezierDegree === 3) {
                            // i is start of the Bezier segment
                            // t is the position in the Bezier segment
                            i = Math.floor(p.position * (c.numberPoints - 1) / 3) * 3;
                            t = (p.position * (c.numberPoints - 1) - i) / 3;
                            if (i >= c.numberPoints - 1) {
                                i = c.numberPoints - 4;
                                t = 1;
                            }
                        } else {
                            return 0;
                        }

                        if (i < 0) {
                            return 1;
                        }

                        // The curve points are transformed (if there is a transformation)
                        // c.X(i) is not transformed.
                        if (c.bezierDegree === 1) {
                            p1 = c.points[i].usrCoords;
                            p2 = c.points[i + 1].usrCoords;
                        } else {
                            A = c.points[i].usrCoords;
                            B = c.points[i + 1].usrCoords;
                            C = c.points[i + 2].usrCoords;
                            D = c.points[i + 3].usrCoords;
                            dx = (1 - t) * (1 - t) * (B[1] - A[1]) + 2 * (1 - t) * t * (C[1] - B[1]) + t * t * (D[1]- C[1]);
                            dy = (1 - t) * (1 - t) * (B[2] - A[2]) + 2 * (1 - t) * t * (C[2] - B[2]) + t * t * (D[2]- C[2]);
                            d = Math.sqrt(dx * dx + dy * dy);
                            dx /= d;
                            dy /= d;
                            p1 = p.coords.usrCoords;
                            p2 = [1, p1[1] + dx, p1[2] + dy];
                      }
                      return p1[2] * p2[1] - p1[1] * p2[2];
                    },
                    function () {
                        var i = Math.floor(p.position),
                            p1, p2, t, A, B, C, D, dx, dy, d;

                        if (c.bezierDegree === 1) {
                            if (i === c.numberPoints - 1) {
                                i--;
                            }
                        } else if (c.bezierDegree === 3) {
                            // i is start of the Bezier segment
                            // t is the position in the Bezier segment
                            i = Math.floor(p.position * (c.numberPoints - 1) / 3) * 3;
                            t = (p.position * (c.numberPoints - 1) - i) / 3;
                            if (i >= c.numberPoints - 1) {
                                i = c.numberPoints - 4;
                                t = 1;
                            }
                        } else {
                            return 0;
                        }

                        if (i < 0) {
                            return 0;
                        }

                        // The curve points are transformed (if there is a transformation)
                        // c.X(i) is not transformed.
                        if (c.bezierDegree === 1) {
                            p1 = c.points[i].usrCoords;
                            p2 = c.points[i + 1].usrCoords;
                        } else {
                            A = c.points[i].usrCoords;
                            B = c.points[i + 1].usrCoords;
                            C = c.points[i + 2].usrCoords;
                            D = c.points[i + 3].usrCoords;
                            dx = (1 - t) * (1 - t) * (B[1] - A[1]) + 2 * (1 - t) * t * (C[1] - B[1]) + t * t * (D[1]- C[1]);
                            dy = (1 - t) * (1 - t) * (B[2] - A[2]) + 2 * (1 - t) * t * (C[2] - B[2]) + t * t * (D[2]- C[2]);
                            d = Math.sqrt(dx * dx + dy * dy);
                            dx /= d;
                            dy /= d;
                            p1 = p.coords.usrCoords;
                            p2 = [1, p1[1] + dx, p1[2] + dy];
                        }
                        return p2[2] - p1[2];
                    },
                    function () {
                        var i = Math.floor(p.position),
                            p1, p2, t, A, B, C, D, dx, dy, d;

                        if (c.bezierDegree === 1) {
                            if (i === c.numberPoints - 1) {
                                i--;
                            }
                        } else if (c.bezierDegree === 3) {
                            // i is start of the Bezier segment
                            // t is the position in the Bezier segment
                            i = Math.floor(p.position * (c.numberPoints - 1) / 3) * 3;
                            t = (p.position * (c.numberPoints - 1) - i) / 3;
                            if (i >= c.numberPoints - 1) {
                                i = c.numberPoints - 4;
                                t = 1;
                            }
                        } else {
                            return 0;
                        }

                        if (i < 0) {
                            return 0.0;
                        }

                        // The curve points are transformed (if there is a transformation)
                        // c.X(i) is not transformed.
                        if (c.bezierDegree === 1) {
                            p1 = c.points[i].usrCoords;
                            p2 = c.points[i + 1].usrCoords;
                        } else {
                            A = c.points[i].usrCoords;
                            B = c.points[i + 1].usrCoords;
                            C = c.points[i + 2].usrCoords;
                            D = c.points[i + 3].usrCoords;
                            dx = (1 - t) * (1 - t) * (B[1] - A[1]) + 2 * (1 - t) * t * (C[1] - B[1]) + t * t * (D[1]- C[1]);
                            dy = (1 - t) * (1 - t) * (B[2] - A[2]) + 2 * (1 - t) * t * (C[2] - B[2]) + t * t * (D[2]- C[2]);
                            d = Math.sqrt(dx * dx + dy * dy);
                            dx /= d;
                            dy /= d;
                            p1 = p.coords.usrCoords;
                            p2 = [1, p1[1] + dx, p1[2] + dy];
                        }
                        return p1[1] - p2[1];
                    }], attributes);

                p.addChild(tangent);
                // this is required for the geogebra reader to display a slope
                tangent.glider = p;
            }
        } else if (c.type === Const.OBJECT_TYPE_TURTLE) {
            tangent = board.create('line', [
                function () {
                    var i = Math.floor(p.position);

                    // run through all curves of this turtle
                    for (j = 0; j < c.objects.length; j++) {
                        el = c.objects[j];

                        if (el.type === Const.OBJECT_TYPE_CURVE) {
                            if (i < el.numberPoints) {
                                break;
                            }

                            i -= el.numberPoints;
                        }
                    }

                    if (i === el.numberPoints - 1) {
                        i--;
                    }

                    if (i < 0) {
                        return 1;
                    }

                    return el.Y(i) * el.X(i + 1) - el.X(i) * el.Y(i + 1);
                },
                function () {
                    var i = Math.floor(p.position);

                    // run through all curves of this turtle
                    for (j = 0; j < c.objects.length; j++) {
                        el = c.objects[j];

                        if (el.type === Const.OBJECT_TYPE_CURVE) {
                            if (i < el.numberPoints) {
                                break;
                            }

                            i -= el.numberPoints;
                        }
                    }

                    if (i === el.numberPoints - 1) {
                        i--;
                    }
                    if (i < 0) {
                        return 0;
                    }

                    return el.Y(i + 1) - el.Y(i);
                },
                function () {
                    var i = Math.floor(p.position);

                    // run through all curves of this turtle
                    for (j = 0; j < c.objects.length; j++) {
                        el = c.objects[j];
                        if (el.type === Const.OBJECT_TYPE_CURVE) {
                            if (i < el.numberPoints) {
                                break;
                            }
                            i -= el.numberPoints;
                        }
                    }
                    if (i === el.numberPoints - 1) {
                        i--;
                    }

                    if (i < 0) {
                        return 0;
                    }

                    return el.X(i) - el.X(i + 1);
                }], attributes);
            p.addChild(tangent);

            // this is required for the geogebra reader to display a slope
            tangent.glider = p;
        } else if (c.elementClass === Const.OBJECT_CLASS_CIRCLE || c.type === Const.OBJECT_TYPE_CONIC) {
            // If p is not on c, the tangent is the polar.
            // This construction should work on conics, too. p has to lie on c.
            tangent = board.create('line', [
                function () {
                    return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[0];
                },
                function () {
                    return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[1];
                },
                function () {
                    return Mat.matVecMult(c.quadraticform, p.coords.usrCoords)[2];
                }], attributes);

            p.addChild(tangent);
            // this is required for the geogebra reader to display a slope
            tangent.glider = p;
        }

        if (!Type.exists(tangent)) {
            throw new Error('JSXGraph: Couldn\'t create tangent with the given parents.');
        }

        tangent.elType = 'tangent';
        tangent.type = Const.OBJECT_TYPE_TANGENT;
        tangent.setParents(parents);

        return tangent;
    };

    /**
     * @class This element is used to provide a constructor for the radical axis with respect to two circles with distinct centers.
     * The angular bisector of the polar lines of the circle centers with respect to the other circle is always the radical axis.
     * The radical axis passes through the intersection points when the circles intersect.
     * When a circle about the midpoint of circle centers, passing through the circle centers, intersects the circles, the polar lines pass through those intersection points.
     * @pseudo
     * @description
     * @name RadicalAxis
     * @augments JXG.Line
     * @constructor
     * @type JXG.Line
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Circle} circle Circle one of the two respective circles.
     * @param {JXG.Circle} circle Circle the other of the two respective circles.
     * @example
     * // Create the radical axis line with respect to two circles
     *   var board = JXG.JSXGraph.initBoard('7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var p1 = board.create('point', [2, 3]);
     *   var p2 = board.create('point', [1, 4]);
     *   var c1 = board.create('circle', [p1, p2]);
     *   var p3 = board.create('point', [6, 5]);
     *   var p4 = board.create('point', [8, 6]);
     *   var c2 = board.create('circle', [p3, p4]);
     *   var r1 = board.create('radicalaxis', [c1, c2]);
     * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-5018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div>
     * <script type='text/javascript'>
     *   var rlex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-5018d0d17a98', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var rlex1_p1 = rlex1_board.create('point', [2, 3]);
     *   var rlex1_p2 = rlex1_board.create('point', [1, 4]);
     *   var rlex1_c1 = rlex1_board.create('circle', [rlex1_p1, rlex1_p2]);
     *   var rlex1_p3 = rlex1_board.create('point', [6, 5]);
     *   var rlex1_p4 = rlex1_board.create('point', [8, 6]);
     *   var rlex1_c2 = rlex1_board.create('circle', [rlex1_p3, rlex1_p4]);
     *   var rlex1_r1 = rlex1_board.create('radicalaxis', [rlex1_c1, rlex1_c2]);
     * </script><pre>
     */
    JXG.createRadicalAxis = function (board, parents, attributes) {
        var el, el1, el2;

        if (parents.length !== 2 ||
                parents[0].elementClass !== Const.OBJECT_CLASS_CIRCLE ||
                parents[1].elementClass !== Const.OBJECT_CLASS_CIRCLE) {
            // Failure
            throw new Error("JSXGraph: Can't create 'radical axis' with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent type: [circle,circle]");
        }

        el1 = board.select(parents[0]);
        el2 = board.select(parents[1]);

        el = board.create('line', [function () {
            var a = el1.stdform,
                b = el2.stdform;

            return Mat.matVecMult(Mat.transpose([a.slice(0, 3), b.slice(0, 3)]), [b[3], -a[3]]);
        }], attributes);

        el.elType = 'radicalaxis';
        el.setParents([el1.id, el2.id]);

        el1.addChild(el);
        el2.addChild(el);

        return el;
    };

    /**
     * @class This element is used to provide a constructor for the polar line of a point with respect to a conic or a circle.
     * @pseudo
     * @description The polar line is the unique reciprocal relationship of a point with respect to a conic.
     * The lines through the intersections of a conic and the polar line of a point with respect to that conic and through that point are tangent to the conic.
     * A point on a conic has the polar line of that point with respect to that conic as the tangent line to that conic at that point.
     * See {@link http://en.wikipedia.org/wiki/Pole_and_polar} for more information on pole and polar.
     * @name PolarLine
     * @augments JXG.Line
     * @constructor
     * @type JXG.Line
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Conic,JXG.Circle_JXG.Point} el1,el2 or
     * @param {JXG.Point_JXG.Conic,JXG.Circle} el1,el2 The result will be the polar line of the point with respect to the conic or the circle.
     * @example
     * // Create the polar line of a point with respect to a conic
     * var p1 = board.create('point', [-1, 2]);
     * var p2 = board.create('point', [ 1, 4]);
     * var p3 = board.create('point', [-1,-2]);
     * var p4 = board.create('point', [ 0, 0]);
     * var p5 = board.create('point', [ 4,-2]);
     * var c1 = board.create('conic',[p1,p2,p3,p4,p5]);
     * var p6 = board.create('point', [-1, 1]);
     * var l1 = board.create('polarline', [c1, p6]);
     * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-6018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div>
     * <script type='text/javascript'>
     * var plex1_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-6018d0d17a98', {boundingbox: [-3, 5, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     * var plex1_p1 = plex1_board.create('point', [-1, 2]);
     * var plex1_p2 = plex1_board.create('point', [ 1, 4]);
     * var plex1_p3 = plex1_board.create('point', [-1,-2]);
     * var plex1_p4 = plex1_board.create('point', [ 0, 0]);
     * var plex1_p5 = plex1_board.create('point', [ 4,-2]);
     * var plex1_c1 = plex1_board.create('conic',[plex1_p1,plex1_p2,plex1_p3,plex1_p4,plex1_p5]);
     * var plex1_p6 = plex1_board.create('point', [-1, 1]);
     * var plex1_l1 = plex1_board.create('polarline', [plex1_c1, plex1_p6]);
     * </script><pre>
     * @example
     * // Create the polar line of a point with respect to a circle.
     * var p1 = board.create('point', [ 1, 1]);
     * var p2 = board.create('point', [ 2, 3]);
     * var c1 = board.create('circle',[p1,p2]);
     * var p3 = board.create('point', [ 6, 6]);
     * var l1 = board.create('polarline', [c1, p3]);
     * </pre><div class="jxgbox" id="JXG7b7233a0-f363-47dd-9df5-7018d0d17a98" class="jxgbox" style="width:400px; height:400px;"></div>
     * <script type='text/javascript'>
     * var plex2_board = JXG.JSXGraph.initBoard('JXG7b7233a0-f363-47dd-9df5-7018d0d17a98', {boundingbox: [-3, 7, 7, -3], axis: true, showcopyright: false, shownavigation: false});
     * var plex2_p1 = plex2_board.create('point', [ 1, 1]);
     * var plex2_p2 = plex2_board.create('point', [ 2, 3]);
     * var plex2_c1 = plex2_board.create('circle',[plex2_p1,plex2_p2]);
     * var plex2_p3 = plex2_board.create('point', [ 6, 6]);
     * var plex2_l1 = plex2_board.create('polarline', [plex2_c1, plex2_p3]);
     * </script><pre>
     */
    JXG.createPolarLine = function (board, parents, attributes) {
        var el, el1, el2,
            firstParentIsConic, secondParentIsConic,
            firstParentIsPoint, secondParentIsPoint;

        if (parents.length > 1) {
            firstParentIsConic = (parents[0].type === Const.OBJECT_TYPE_CONIC ||
                parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE);
            secondParentIsConic = (parents[1].type === Const.OBJECT_TYPE_CONIC ||
                parents[1].elementClass === Const.OBJECT_CLASS_CIRCLE);

            firstParentIsPoint = (Type.isPoint(parents[0]));
            secondParentIsPoint = (Type.isPoint(parents[1]));
        }

        if (parents.length !== 2 ||
                !((firstParentIsConic && secondParentIsPoint) ||
                    (firstParentIsPoint && secondParentIsConic))) {
            // Failure
            throw new Error("JSXGraph: Can't create 'polar line' with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent type: [conic|circle,point], [point,conic|circle]");
        }

        if (secondParentIsPoint) {
            el1 = board.select(parents[0]);
            el2 = board.select(parents[1]);
        } else {
            el1 = board.select(parents[1]);
            el2 = board.select(parents[0]);
        }

        // Polar lines have been already provided in the tangent element.
        el = board.create('tangent', [el1, el2], attributes);

        el.elType = 'polarline';
        return el;
    };

    /**
     * Register the element type tangent at JSXGraph
     * @private
     */
    JXG.registerElement('tangent', JXG.createTangent);
    JXG.registerElement('polar', JXG.createTangent);
    JXG.registerElement('radicalaxis', JXG.createRadicalAxis);
    JXG.registerElement('polarline', JXG.createPolarLine);

    return {
        Line: JXG.Line,
        createLine: JXG.createLine,
        createTangent: JXG.createTangent,
        createPolar: JXG.createTangent,
        createSegment: JXG.createSegment,
        createAxis: JXG.createAxis,
        createArrow: JXG.createArrow,
        createRadicalAxis: JXG.createRadicalAxis,
        createPolarLine: JXG.createPolarLine
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 utils/type
 */

/**
 * @fileoverview In this file the class Group is defined, a class for
 * managing grouping of points.
 */

define('base/group',[
    'jxg', 'base/constants', 'math/math', 'math/geometry', 'utils/type'
], function (JXG, Const, Mat, Geometry, Type) {

    "use strict";

    /**
     * Creates a new instance of Group.
     * @class In this class all group management is done.
     * @param {JXG.Board} board
     * @param {String} id Unique identifier for this object.  If null or an empty string is given,
     * an unique id will be generated by Board
     * @param {String} name Not necessarily unique name, displayed on the board.  If null or an
     * empty string is given, an unique name will be generated.
     * @param {Array} objects Array of points to add to this group.
     * @param {Object} attributes Defines the visual appearance of the group.
     * @constructor
     */
    JXG.Group = function (board, id, name, objects, attributes) {
        var number, objArray, i, obj;

        this.board = board;
        this.objects = {};
        number = this.board.numObjects;
        this.board.numObjects += 1;

        if ((id === '') || !Type.exists(id)) {
            this.id = this.board.id + 'Group' + number;
        } else {
            this.id = id;
        }
        this.board.groups[this.id] = this;

        this.type = Const.OBJECT_TYPE_POINT;
        this.elementClass = Const.OBJECT_CLASS_POINT;

        if ((name === '') || !Type.exists(name)) {
            this.name = 'group_' + this.board.generateName(this);
        } else {
            this.name = name;
        }
        delete this.type;

        /**
         * Cache coordinates of points. From this and the actual position
         * of the points, the translation is determined.
         * It has to be kept updated in this class "by hand"-
         *
         * @private
         * @type Object
         * @see JXG.Group#_updateCoordsCache
         */
        this.coords = {};
        this.needsRegularUpdate = attributes.needsregularupdate;

        this.rotationCenter = 'centroid';
        this.scaleCenter = null;
        this.rotationPoints = [];
        this.translationPoints = [];
        this.scalePoints = [];
        this.scaleDirections = {};

        this.parents = [];

        if (Type.isArray(objects)) {
            objArray = objects;
        } else {
            objArray = Array.prototype.slice.call(arguments, 3);
        }

        for (i = 0; i < objArray.length; i++) {
            obj = this.board.select(objArray[i]);

            if ((!Type.evaluate(obj.visProp.fixed)) && Type.exists(obj.coords)) {
                this.addPoint(obj);
            }
        }

        this.methodMap = {
            ungroup: 'ungroup',
            add: 'addPoint',
            addPoint: 'addPoint',
            addPoints: 'addPoints',
            addGroup: 'addGroup',
            remove: 'removePoint',
            removePoint: 'removePoint',
            setAttribute: 'setAttribute',
            setProperty: 'setAttribute'
        };
    };

    JXG.extend(JXG.Group.prototype, /** @lends JXG.Group.prototype */ {
        /**
         * Releases all elements of this group.
         * @returns {JXG.Group} returns this (empty) group
         */
        ungroup: function () {
            var el, p, i;
            for (el in this.objects) {
                if (this.objects.hasOwnProperty(el)) {
                    p = this.objects[el].point;
                    if (Type.isArray(p.groups)) {
                        i = Type.indexOf(p.groups, this.id);
                        if (i >= 0) {
                            delete p.groups[i];
                        }
                    }
                }
            }

            this.objects = {};
            return this;
        },

        /**
         * Adds ids of elements to the array this.parents. This is a copy
         * of {@link Element.addParents}.
         * @param {Array} parents Array of elements or ids of elements.
         * Alternatively, one can give a list of objects as parameters.
         * @returns {JXG.Object} reference to the object itself.
         **/
        addParents: function (parents) {
            var i, len, par;

            if (Type.isArray(parents)) {
                par = parents;
            } else {
                par = arguments;
            }

            len = par.length;
            for (i = 0; i < len; ++i) {
                if (Type.isId(this.board, par[i])) {
                    this.parents.push(par[i]);
                } else if (Type.exists(par[i].id)) {
                    this.parents.push(par[i].id);
                }
            }

            this.parents = Type.uniqueArray(this.parents);
        },

        /**
         * Sets ids of elements to the array this.parents. This is a copy
         * of {@link Element.setParents}
         * First, this.parents is cleared. See {@link Group#addParents}.
         * @param {Array} parents Array of elements or ids of elements.
         * Alternatively, one can give a list of objects as parameters.
         * @returns {JXG.Object} reference to the object itself.
         **/
        setParents: function(parents) {
            this.parents = [];
            this.addParents(parents);
            return this;
        },

        /**
         * List of the element ids resp. values used as parents in {@link JXG.Board#create}.
         * @returns {Array}
         */
        getParents: function () {
            return Type.isArray(this.parents) ? this.parents : [];
        },

        /**
         * Update the cached coordinates of a group element.
         * @param  {String} el element id of the group element whose cached coordinates
         * are going to be updated.
         * @return null
         */
        _updateCoordsCache: function(el) {
            var obj;
            if (el !== "" && Type.exists(this.objects[el])) {
                obj = this.objects[el].point;
                this.coords[obj.id] = {usrCoords: obj.coords.usrCoords.slice(0)};
            }
        },

        /**
         * Sends an update to all group members.
         * This method is called from the points' coords object event listeners
         * and not by the board.
         * @returns {JXG.Group} returns this group
         */
        update: function () {
            var drag, el, actionCenter, desc, s, sx, sy, alpha, t, center, obj = null;

            if (!this.needsUpdate) {
                return this;
            }

            drag = this._update_find_drag_type();
            if (drag.action === 'nothing') {
                this._updateCoordsCache(drag.id);
                return this;
            }

            obj = this.objects[drag.id].point;

            // Prepare translation, scaling or rotation
            if (drag.action === 'translation') {
                t = [
                    obj.coords.usrCoords[1] - this.coords[drag.id].usrCoords[1],
                    obj.coords.usrCoords[2] - this.coords[drag.id].usrCoords[2]
                ];

            } else if (drag.action === 'rotation' || drag.action === 'scaling') {
                if (drag.action === 'rotation') {
                    actionCenter = 'rotationCenter';
                } else {
                    actionCenter = 'scaleCenter';
                }

                if (Type.isPoint(this[actionCenter])) {
                    center = this[actionCenter].coords.usrCoords.slice(1);
                } else if (this[actionCenter] === 'centroid') {
                    center = this._update_centroid_center();
                } else if (Type.isArray(this[actionCenter])) {
                    center = this[actionCenter];
                } else if (Type.isFunction(this[actionCenter])) {
                    center = this[actionCenter]();
                } else {
                    return this;
                }

                if (drag.action === 'rotation') {
                    alpha = Geometry.rad(this.coords[drag.id].usrCoords.slice(1), center, this.objects[drag.id].point);
                    t = this.board.create('transform', [alpha, center[0], center[1]], {type: 'rotate'});
                    t.update();  // This initializes t.matrix, which is needed if the action element is the first group element.
                } else if (drag.action === 'scaling') {
                    s = Geometry.distance(this.coords[drag.id].usrCoords.slice(1), center);
                    if (Math.abs(s) < Mat.eps) {
                        return this;
                    }
                    s = Geometry.distance(obj.coords.usrCoords.slice(1), center) / s;
                    sx = (this.scaleDirections[drag.id].indexOf('x') >= 0) ? s : 1.0;
                    sy = (this.scaleDirections[drag.id].indexOf('y') >= 0) ? s : 1.0;

                    // Shift scale center to origin, scale and shift the scale center back.
                    t = this.board.create('transform',
                            [1, 0, 0,
                             center[0] * (1 -  sx), sx, 0,
                             center[1] * (1 -  sy), 0, sy], {type: 'generic'});
                    t.update();  // This initializes t.matrix, which is needed if the action element is the first group element.
                } else {
                    return this;
                }
            }

            this._update_apply_transformation(drag, t);

            this.needsUpdate = false;  // This is needed here to prevent infinite recursion because
                                       // of the board.updateElements call below,

            // Prepare dependent objects for update
            for (el in this.objects) {
                if (this.objects.hasOwnProperty(el)) {
                    for (desc in this.objects[el].descendants) {
                        if (this.objects[el].descendants.hasOwnProperty(desc)) {
                            this.objects[el].descendants.needsUpdate = this.objects[el].descendants.needsRegularUpdate || this.board.needsFullUpdate;
                        }
                    }
                }
            }
            this.board.updateElements(drag);

            // Now, all group elements have their new position and
            // we can update the bookkeeping of the coordinates of the group elements.
            for (el in this.objects) {
                if (this.objects.hasOwnProperty(el)) {
                    this._updateCoordsCache(el);
                }
            }

            return this;
        },

        /**
         * @private
         * Determine what the dragging of a group element should do:
         * rotation, translation, scaling or nothing.
         */
        _update_find_drag_type: function () {
            var el, obj,
                action = 'nothing',
                changed = [],
                dragObjId;

            // Determine how many elements have changed their position
            // If more than one element changed its position, it is a translation.
            // If exactly one element changed its position we have to find the type of the point.
            for (el in this.objects) {
                if (this.objects.hasOwnProperty(el)) {
                    obj = this.objects[el].point;

                    if (obj.coords.distance(Const.COORDS_BY_USER, this.coords[el]) > Mat.eps) {
                        changed.push(obj.id);
                    }
                }
            }

            // Determine type of action: translation, scaling or rotation
            if (changed.length === 0) {
                return {
                    'action': action,
                    'id': '',
                    'changed': changed
                };
            }

            dragObjId = changed[0];
            obj = this.objects[dragObjId].point;

            if (changed.length > 1) { // More than one point moved => translation
                action = 'translation';
            } else {                        // One point moved => we have to determine the type
                if (Type.isInArray(this.rotationPoints, obj) && Type.exists(this.rotationCenter)) {
                    action = 'rotation';
                } else if (Type.isInArray(this.scalePoints, obj) && Type.exists(this.scaleCenter)) {
                    action = 'scaling';
                } else if (Type.isInArray(this.translationPoints, obj)) {
                    action = 'translation';
                }
            }

            return {
                'action': action,
                'id': dragObjId,
                'changed': changed
            };
        },

        /**
         * @private
         * Determine the Euclidean coordinates of the centroid of the group.
         * @returns {Array} array of length two,
         */
        _update_centroid_center: function () {
            var center, len, el;

            center = [0, 0];
            len = 0;
            for (el in this.coords) {
                if (this.coords.hasOwnProperty(el)) {
                    center[0] += this.coords[el].usrCoords[1];
                    center[1] += this.coords[el].usrCoords[2];
                    ++len;
                }
            }
            if (len > 0) {
                center[0] /= len;
                center[1] /= len;
            }

            return center;
        },

        /**
         * @private
         * Apply the transformation to all elements of the group
         */
        _update_apply_transformation: function (drag, t) {
            var el, obj;

            for (el in this.objects) {
                if (this.objects.hasOwnProperty(el)) {
                    if (Type.exists(this.board.objects[el])) {
                        obj = this.objects[el].point;

                        // Here, it is important that we change the position
                        // of elements by using setCoordinates.
                        // Thus, we avoid the call of snapToGrid().
                        // This is done in the subsequent call of board.updateElements()
                        // in Group.update() above.
                        if (obj.id !== drag.id) {
                            if (drag.action === 'translation') {
                                if (!Type.isInArray(drag.changed, obj.id)) {
                                    obj.coords.setCoordinates(Const.COORDS_BY_USER,
                                        [this.coords[el].usrCoords[1] + t[0],
                                         this.coords[el].usrCoords[2] + t[1]]);
                                }
                            } else if (drag.action === 'rotation' || drag.action === 'scaling') {
                                t.applyOnce([obj]);
                            }
                        } else {
                            if (drag.action === 'rotation' || drag.action === 'scaling') {
                                obj.coords.setCoordinates(Const.COORDS_BY_USER,
                                    Mat.matVecMult(t.matrix, this.coords[obj.id].usrCoords));
                            }
                        }
                    } else {
                        delete this.objects[el];
                    }
                }
            }
        },

        /**
         * Adds an Point to this group.
         * @param {JXG.Point} object The point added to the group.
         * @returns {JXG.Group} returns this group
         */
        addPoint: function (object) {
            this.objects[object.id] = {point: this.board.select(object)};
            this._updateCoordsCache(object.id);
            //this.coords[object.id] = {usrCoords: object.coords.usrCoords.slice(0) };
            this.translationPoints.push(object);

            object.groups.push(this.id);
            object.groups = Type.uniqueArray(object.groups);

            return this;
        },

        /**
         * Adds multiple points to this group.
         * @param {Array} objects An array of points to add to the group.
         * @returns {JXG.Group} returns this group
         */
        addPoints: function (objects) {
            var p;

            for (p = 0; p < objects.length; p++) {
                this.addPoint(objects[p]);
            }

            return this;
        },

        /**
         * Adds all points in a group to this group.
         * @param {JXG.Group} group The group added to this group.
         * @returns {JXG.Group} returns this group
         */
        addGroup: function (group) {
            var el;

            for (el in group.objects) {
                if (group.objects.hasOwnProperty(el)) {
                    this.addPoint(group.objects[el].point);
                }
            }

            return this;
        },

        /**
         * Removes a point from the group.
         * @param {JXG.Point} point
         * @returns {JXG.Group} returns this group
         */
        removePoint: function (point) {
            delete this.objects[point.id];

            return this;
        },

        /**
         * Sets the center of rotation for the group. This is either a point or the centroid of the group.
         * @param {JXG.Point|String} object A point which will be the center of rotation, the string "centroid", or
         * an array of length two, or a function returning an array of length two.
         * @default 'centroid'
         * @returns {JXG.Group} returns this group
         */
        setRotationCenter: function (object) {
            this.rotationCenter = object;

            return this;
        },

        /**
         * Sets the rotation points of the group. Dragging at one of these points results into a rotation of the whole group around
         * the rotation center of the group {@see JXG.Group#setRotationCenter}.
         * @param {Array|JXG.Point} objects Array of {@link JXG.Point} or arbitrary number of {@link JXG.Point} elements.
         * @returns {JXG.Group} returns this group
         */
        setRotationPoints: function (objects) {
            return this._setActionPoints('rotation', objects);
        },

        /**
         * Adds a point to the set of rotation points of the group. Dragging at one of these points results into a rotation of the whole group around
         * the rotation center of the group {@see JXG.Group#setRotationCenter}.
         * @param {JXG.Point} point {@link JXG.Point} element.
         * @returns {JXG.Group} returns this group
         */
        addRotationPoint: function (point) {
            return this._addActionPoint('rotation', point);
        },

        /**
         * Removes the rotation property from a point of the group.
         * @param {JXG.Point} point {@link JXG.Point} element.
         * @returns {JXG.Group} returns this group
         */
        removeRotationPoint: function (point) {
            return this._removeActionPoint('rotation', point);
        },

        /**
         * Sets the translation points of the group. Dragging at one of these points results into a translation of the whole group.
         * @param {Array|JXG.Point} objects Array of {@link JXG.Point} or arbitrary number of {@link JXG.Point} elements.
         *
         * By default, all points of the group are translation points.
         * @returns {JXG.Group} returns this group
         */
        setTranslationPoints: function (objects) {
            return this._setActionPoints('translation', objects);
        },

        /**
         * Adds a point to the set of the translation points of the group.
         * Dragging one of these points results into a translation of the whole group.
         * @param {JXG.Point} point {@link JXG.Point} element.
         * @returns {JXG.Group} returns this group
         */
        addTranslationPoint: function (point) {
            return this._addActionPoint('translation', point);
        },

        /**
         * Removes the translation property from a point of the group.
         * @param {JXG.Point} point {@link JXG.Point} element.
         * @returns {JXG.Group} returns this group
         */
        removeTranslationPoint: function (point) {
            return this._removeActionPoint('translation', point);
        },

        /**
         * Sets the center of scaling for the group. This is either a point or the centroid of the group.
         * @param {JXG.Point|String} object A point which will be the center of scaling, the string "centroid", or
         * an array of length two, or a function returning an array of length two.
         * @returns {JXG.Group} returns this group
         */
        setScaleCenter: function (object) {
            this.scaleCenter = object;

            return this;
        },

        /**
         * Sets the scale points of the group. Dragging at one of these points results into a scaling of the whole group.
         * @param {Array|JXG.Point} objects Array of {@link JXG.Point} or arbitrary number of {@link JXG.Point} elements.
         * @param {String} direction Restricts the directions to be scaled. Possible values are 'x', 'y', 'xy'. Default value is 'xy'.
         *
         * By default, all points of the group are translation points.
         * @returns {JXG.Group} returns this group
         */
        setScalePoints: function (objects, direction) {
            var objs, i, len;
            if (Type.isArray(objects)) {
                objs = objects;
            } else {
                objs = arguments;
            }

            len = objs.length;
            for (i = 0; i < len; ++i) {
                this.scaleDirections[this.board.select(objs[i]).id] = direction || 'xy';
            }

            return this._setActionPoints('scale', objects);
        },

        /**
         * Adds a point to the set of the scale points of the group. Dragging at one of these points results into a scaling of the whole group.
         * @param {JXG.Point} point {@link JXG.Point} element.
         * @param {String} direction Restricts the directions to be scaled. Possible values are 'x', 'y', 'xy'. Default value is 'xy'.
         * @returns {JXG.Group} returns this group
         */
        addScalePoint: function (point, direction) {
            this._addActionPoint('scale', point);
            this.scaleDirections[this.board.select(point).id] = direction || 'xy';

            return this;
        },

        /**
         * Removes the scaling property from a point of the group.
         * @param {JXG.Point} point {@link JXG.Point} element.
         * @returns {JXG.Group} returns this group
         */
        removeScalePoint: function (point) {
            return this._removeActionPoint('scale', point);
        },

        /**
         * Generic method for {@link JXG.Group@setTranslationPoints} and {@link JXG.Group@setRotationPoints}
         * @private
         */
        _setActionPoints: function (action, objects) {
            var objs, i, len;
            if (Type.isArray(objects)) {
                objs = objects;
            } else {
                objs = arguments;
            }

            len = objs.length;
            this[action + 'Points'] = [];
            for (i = 0; i < len; ++i) {
                this._addActionPoint(action, objs[i]);
            }

            return this;
        },

        /**
         * Generic method for {@link JXG.Group@addTranslationPoint} and {@link JXG.Group@addRotationPoint}
         * @private
         */
        _addActionPoint: function (action, point) {
            this[action + 'Points'].push(this.board.select(point));

            return this;
        },

        /**
         * Generic method for {@link JXG.Group@removeTranslationPoint} and {@link JXG.Group@removeRotationPoint}
         * @private
         */
        _removeActionPoint: function (action, point) {
            var idx = this[action + 'Points'].indexOf(this.board.select(point));
            if (idx > -1) {
                this[action + 'Points'].splice(idx, 1);
            }

            return this;
        },

        /**
         * @deprecated
         * Use setAttribute
         */
        setProperty: function () {
            JXG.deprecated('Group.setProperty', 'Group.setAttribute()');
            this.setAttribute.apply(this, arguments);
        },

        setAttribute: function () {
            var el;

            for (el in this.objects) {
                if (this.objects.hasOwnProperty(el)) {
                    this.objects[el].point.setAttribute.apply(this.objects[el].point, arguments);
                }
            }

            return this;
        }
    });

    /**
     * @class This element combines a given set of {@link JXG.Point} elements to a
     *  group. The elements of the group and dependent elements can be translated, rotated and scaled by
     *  dragging one of the group elements.
     *
     *
     * @pseudo
     * @description
     * @name Group
     * @augments JXG.Group
     * @constructor
     * @type JXG.Group
     * @param {JXG.Board} board The board the points are on.
     * @param {Array} parents Array of points to group.
     * @param {Object} attributes Visual properties (unused).
     * @returns {JXG.Group}
     *
     * @example
     *
     *  // Create some free points. e.g. A, B, C, D
     *  // Create a group
     *
     *  var p, col, g;
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *  g = board.create('group', p);
     *
     * </pre><div class="jxgbox" id="JXGa2204533-db91-4af9-b720-70394de4d367" style="width: 400px; height: 300px;"></div>
     * <script type="text/javascript">
     *  (function () {
     *  var board, p, col, g;
     *  board = JXG.JSXGraph.initBoard('JXGa2204533-db91-4af9-b720-70394de4d367', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *  g = board.create('group', p);
     *  })();
     * </script><pre>
     *
     *
     * @example
     *
     *  // Create some free points. e.g. A, B, C, D
     *  // Create a group
     *  // If the points define a polygon and the polygon has the attribute hasInnerPoints:true,
     *  // the polygon can be dragged around.
     *
     *  var p, col, pol, g;
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *
     *  pol = board.create('polygon', p, {hasInnerPoints: true});
     *  g = board.create('group', p);
     *
     * </pre><div class="jxgbox" id="JXG781b5564-a671-4327-81c6-de915c8f924e" style="width: 400px; height: 300px;"></div>
     * <script type="text/javascript">
     *  (function () {
     *  var board, p, col, pol, g;
     *  board = JXG.JSXGraph.initBoard('JXG781b5564-a671-4327-81c6-de915c8f924e', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *  pol = board.create('polygon', p, {hasInnerPoints: true});
     *  g = board.create('group', p);
     *  })();
     * </script><pre>
     *
     *  @example
     *
     *  // Allow rotations:
     *  // Define a center of rotation and declare points of the group as "rotation points".
     *
     *  var p, col, pol, g;
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *
     *  pol = board.create('polygon', p, {hasInnerPoints: true});
     *  g = board.create('group', p);
     *  g.setRotationCenter(p[0]);
     *  g.setRotationPoints([p[1], p[2]]);
     *
     * </pre><div class="jxgbox" id="JXGf0491b62-b377-42cb-b55c-4ef5374b39fc" style="width: 400px; height: 300px;"></div>
     * <script type="text/javascript">
     *  (function () {
     *  var board, p, col, pol, g;
     *  board = JXG.JSXGraph.initBoard('JXGf0491b62-b377-42cb-b55c-4ef5374b39fc', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *  pol = board.create('polygon', p, {hasInnerPoints: true});
     *  g = board.create('group', p);
     *  g.setRotationCenter(p[0]);
     *  g.setRotationPoints([p[1], p[2]]);
     *  })();
     * </script><pre>
     *
     *  @example
     *
     *  // Allow rotations:
     *  // As rotation center, arbitrary points, coordinate arrays,
     *  // or functions returning coordinate arrays can be given.
     *  // Another possibility is to use the predefined string 'centroid'.
     *
     *  // The methods to define the rotation points can be chained.
     *
     *  var p, col, pol, g;
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *
     *  pol = board.create('polygon', p, {hasInnerPoints: true});
     *  g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[1], p[2]]);
     *
     * </pre><div class="jxgbox" id="JXG8785b099-a75e-4769-bfd8-47dd4376fe27" style="width: 400px; height: 300px;"></div>
     * <script type="text/javascript">
     *  (function () {
     *  var board, p, col, pol, g;
     *  board = JXG.JSXGraph.initBoard('JXG8785b099-a75e-4769-bfd8-47dd4376fe27', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *  pol = board.create('polygon', p, {hasInnerPoints: true});
     *  g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[1], p[2]]);
     *  })();
     * </script><pre>
     *
     *  @example
     *
     *  // Allow scaling:
     *  // As for rotation one can declare points of the group to trigger a scaling operation.
     *  // For this, one has to define a scaleCenter, in analogy to rotations.
     *
     *  // Here, the yellow  point enables scaling, the red point a rotation.
     *
     *  var p, col, pol, g;
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *
     *  pol = board.create('polygon', p, {hasInnerPoints: true});
     *  g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[2]]);
     *  g.setScaleCenter(p[0]).setScalePoints(p[1]);
     *
     * </pre><div class="jxgbox" id="JXGc3ca436b-e4fc-4de5-bab4-09790140c675" style="width: 400px; height: 300px;"></div>
     * <script type="text/javascript">
     *  (function () {
     *  var board, p, col, pol, g;
     *  board = JXG.JSXGraph.initBoard('JXGc3ca436b-e4fc-4de5-bab4-09790140c675', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *  pol = board.create('polygon', p, {hasInnerPoints: true});
     *  g = board.create('group', p).setRotationCenter('centroid').setRotationPoints([p[2]]);
     *  g.setScaleCenter(p[0]).setScalePoints(p[1]);
     *  })();
     * </script><pre>
     *
     *  @example
     *
     *  // Allow Translations:
     *  // By default, every point of a group triggers a translation.
     *  // There may be situations, when this is not wanted.
     *
     *  // In this example, E triggers nothing, but itself is rotation center
     *  // and is translated, if other points are moved around.
     *
     *  var p, q, col, pol, g;
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *  q = board.create('point',[0, 0], {size: 5, strokeColor:col, fillColor:col});
     *
     *  pol = board.create('polygon', p, {hasInnerPoints: true});
     *  g = board.create('group', p.concat(q)).setRotationCenter('centroid').setRotationPoints([p[2]]);
     *  g.setScaleCenter(p[0]).setScalePoints(p[1]);
     *  g.removeTranslationPoint(q);
     *
     * </pre><div class="jxgbox" id="JXGd19b800a-57a9-4303-b49a-8f5b7a5488f0" style="width: 400px; height: 300px;"></div>
     * <script type="text/javascript">
     *  (function () {
     *  var board, p, q, col, pol, g;
     *  board = JXG.JSXGraph.initBoard('JXGd19b800a-57a9-4303-b49a-8f5b7a5488f0', {boundingbox:[-5,5,5,-5], keepaspectratio:true, axis:true, showcopyright: false});
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -1 ], {size: 5, strokeColor:'yellow', fillColor:'yellow'}));
     *  p.push(board.create('point',[2, 1 ], {size: 5, strokeColor:'red', fillColor:'red'}));
     *  p.push(board.create('point',[-2, 1], {size: 5, strokeColor:col, fillColor:col}));
     *  q = board.create('point',[0, 0], {size: 5, strokeColor:col, fillColor:col});
     *
     *  pol = board.create('polygon', p, {hasInnerPoints: true});
     *  g = board.create('group', p.concat(q)).setRotationCenter('centroid').setRotationPoints([p[2]]);
     *  g.setScaleCenter(p[0]).setScalePoints(p[1]);
     *  g.removeTranslationPoint(q);
     *  })();
     * </script><pre>
     *
     *
     */
    JXG.createGroup = function (board, parents, attributes) {
        var attr = Type.copyAttributes(attributes, board.options, 'group'),
            g = new JXG.Group(board, attr.id, attr.name, parents, attr);

        g.elType = 'group';
        g.setParents(parents);

        return g;
    };

    JXG.registerElement('group', JXG.createGroup);

    return {
        Group: JXG.Group,
        createGroup: JXG.createGroup
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 math/math
 math/geometry
 math/numerics
 utils/type
  elements:
   point
   curve
 */

/**
 * @fileoverview In this file the conic sections defined.
 */

define('element/conic',[
    'jxg', 'base/constants', 'base/coords', 'math/math', 'math/numerics', 'math/geometry', 'utils/type'
], function (JXG, Const, Coords, Mat, Numerics, Geometry, Type) {

    "use strict";

    /**
     * @class This element is used to provide a constructor for an ellipse. An ellipse is given by two points (the foci) and a third point on the the ellipse or
     * the length of the major axis.
     * @pseudo
     * @description
     * @name Ellipse
     * @augments Conic
     * @constructor
     * @type JXG.Curve
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point,array_JXG.Point,array_JXG.Point,array} point1,point2,point3 Parent elements can be three elements either of type {@link JXG.Point} or array of
     * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
     * @param {JXG.Point,array_JXG.Point,array_number,function} point1,point2,number Parent elements can be two elements either of type {@link JXG.Point} or array of
     * numbers describing the coordinates of a point. The third parameter is a number/function which defines the length of the major axis
     * @param {Number} start (Optional) parameter of the curve start, default: 0.
     * @param {Number} end (Optional) parameter for the curve end, default: 2&pi;.
     * @example
     * // Create an Ellipse by three points
     * var A = board.create('point', [-1,4]);
     * var B = board.create('point', [-1,-4]);
     * var C = board.create('point', [1,1]);
     * var el = board.create('ellipse',[A,B,C]);
     * </pre><div class="jxgbox" id="JXGa4d7fb6f-8708-4e45-87f2-2379ae2bd2c0" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   (function() {
     *   var glex1_board = JXG.JSXGraph.initBoard('JXGa4d7fb6f-8708-4e45-87f2-2379ae2bd2c0', {boundingbox:[-6,6,6,-6], keepaspectratio:true, showcopyright: false, shownavigation: false});
     *   var A = glex1_board.create('point', [-1,4]);
     *   var B = glex1_board.create('point', [-1,-4]);
     *   var C = glex1_board.create('point', [1,1]);
     *   var el = glex1_board.create('ellipse',[A,B,C]);
     * })();
     * </script><pre>
     *
     * @example
     * // Create an elliptical arc
     * var p1 = board.create('point', [-1, 2]);
     * var p2 = board.create('point', [ 1, 2]);
     * var p3 = board.create('point', [0, 3]);
     * 
     * var ell = board.create('ellipse', [
     *   p1, p2, p3, 0, Math.PI], {
     *   lastArrow: {type: 7}
     * });
     * 
     * </pre><div id="JXG950f7c07-27a4-4c67-9505-c73c22ce9345" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG950f7c07-27a4-4c67-9505-c73c22ce9345',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var p1 = board.create('point', [-1, 2]);
     *     var p2 = board.create('point', [ 1, 2]);
     *     var p3 = board.create('point', [0, 3]);
     *     
     *     var ell = board.create('ellipse', [
     *       p1, p2, p3, 0, Math.PI], {
     *       lastArrow: {type: 7}
     *     });
     * 
     *     })();
     * 
     * </script><pre>
     * 
*
     */
    JXG.createEllipse = function (board, parents, attributes) {
        var polarForm, curve, M, C, majorAxis, i,
            hasPointOrg,
            // focus 1 and focus 2
            F = [],
            attr_foci = Type.copyAttributes(attributes, board.options, 'conic', 'foci'),
            attr_center = Type.copyAttributes(attributes, board.options, 'conic', 'center'),
            attr_curve = Type.copyAttributes(attributes, board.options, 'conic');

        // The foci and the third point are either points or coordinate arrays.
        for (i = 0; i < 2; i++) {
            // focus i given by coordinates
            if (parents[i].length > 1) {
                F[i] = board.create('point', parents[i], attr_foci);
            // focus i given by point
            } else if (Type.isPoint(parents[i])) {
                F[i] = board.select(parents[i]);
            // given by function
            } else if (Type.isFunction(parents[i]) && Type.isPoint(parents[i]()) ) {
                F[i] = parents[i]();
            // focus i given by point name
            } else if (Type.isString(parents[i])) {
                F[i] = board.select(parents[i]);
            } else {
                throw new Error("JSXGraph: Can't create Ellipse with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                    "\nPossible parent types: [point,point,point], [point,point,number|function]");
            }
        }

        // length of major axis
        if (Type.isNumber(parents[2])) {
            majorAxis = Type.createFunction(parents[2], board);
        } else if (Type.isFunction(parents[2]) && Type.isNumber(parents[2]())) {
            majorAxis = parents[2];
        } else {
            // point on ellipse
            if (Type.isPoint(parents[2])) {
                C = board.select(parents[2]);
            // point on ellipse given by coordinates
            } else if (parents[2].length > 1) {
                C = board.create('point', parents[2], attr_foci);
            // given by function
            } else if (Type.isFunction(parents[2]) && Type.isPoint(parents[2]()) ) {
                C = parents[2]();
            // focus i given by point name
            } else if (Type.isString(parents[2])) {
                C = board.select(parents[2]);
            } else {
                throw new Error("JSXGraph: Can't create Ellipse with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
                    "\nPossible parent types: [point,point,point], [point,point,number|function]");
            }
            /** @ignore */
            majorAxis = function () {
                return C.Dist(F[0]) + C.Dist(F[1]);
            };
        }

        // to
        if (!Type.exists(parents[4])) {
            parents[4] = 2 * Math.PI;
        }

        // from
        if (!Type.exists(parents[3])) {
            parents[3] = 0.0;
        }

        M = board.create('point', [
            function () {
                return (F[0].X() + F[1].X()) * 0.5;
            },
            function () {
                return (F[0].Y() + F[1].Y()) * 0.5;
            }
        ], attr_center);

        curve = board.create('curve', [
            function (x) {
                return 0;
            },
            function (x) {
                return 0;
            },
            parents[3],
            parents[4]], attr_curve);

        curve.majorAxis = majorAxis;

        // Save the original hasPoint method. It will be called inside of the new hasPoint method.
        hasPointOrg = curve.hasPoint;

        /** @ignore */
        polarForm = function (phi, suspendUpdate) {
            var r, rr, ax, ay, bx, by, axbx, ayby, f;

            if (!suspendUpdate) {
                r = majorAxis();
                rr = r * r;
                ax = F[0].X();
                ay = F[0].Y();
                bx = F[1].X();
                by = F[1].Y();
                axbx = ax - bx;
                ayby = ay - by;
                f = (rr - ax * ax - ay * ay + bx * bx + by * by) / (2 * r);

                curve.quadraticform = [
                    [f * f - bx * bx - by * by, f * axbx / r + bx,      f * ayby / r + by],
                    [f * axbx / r + bx,         (axbx * axbx) / rr - 1, axbx * ayby / rr ],
                    [f * ayby / r + by,         axbx * ayby / rr,       (ayby * ayby) / rr - 1]
                ];
            }
        };

        /** @ignore */
        curve.X = function (phi, suspendUpdate) {
            var r = majorAxis(),
                c = F[1].Dist(F[0]),
                b = 0.5 * (c * c - r * r) / (c * Math.cos(phi) - r),
                beta = Math.atan2(F[1].Y() - F[0].Y(), F[1].X() - F[0].X());

            if (!suspendUpdate) {
                polarForm(phi, suspendUpdate);
            }

            return F[0].X() + Math.cos(beta + phi) * b;
        };

        /** @ignore */
        curve.Y = function (phi, suspendUpdate) {
            var r = majorAxis(),
                c = F[1].Dist(F[0]),
                b = 0.5 * (c * c - r * r) / (c * Math.cos(phi) - r),
                beta = Math.atan2(F[1].Y() - F[0].Y(), F[1].X() - F[0].X());

            return F[0].Y() + Math.sin(beta + phi) * b;
        };

        curve.midpoint = curve.center = M;
        curve.type = Const.OBJECT_TYPE_CONIC;
        curve.subs = {
                center: curve.center
            };
        curve.inherits.push(curve.center, F[0], F[1]);
        if (Type.isPoint(C)) {
            curve.inherits.push(C);
        }

        /**
         * Checks whether (x,y) is near the ellipse line or inside of the ellipse
         * (in case JXG.Options.conic#hasInnerPoints is true).
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @returns {Boolean} True if (x,y) is near the ellipse, False otherwise.
         * @private
         */
        curve.hasPoint =  function (x, y) {
            var ac, bc, r, p, dist;

            if (Type.evaluate(this.visProp.hasinnerpoints)) {
                ac = F[0].coords;
                bc = F[1].coords;
                r = this.majorAxis();
                p = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);
                dist = p.distance(Const.COORDS_BY_USER, ac) + p.distance(Const.COORDS_BY_USER, bc);

                return (dist <= r);
            }

            return hasPointOrg.apply(this, arguments);
        };

        M.addChild(curve);
        for (i = 0; i < 2; i++) {
            if (Type.isPoint(F[i])) {
                F[i].addChild(curve);
            }
        }
        if (Type.isPoint(C)) {
            C.addChild(curve);
        }
        curve.setParents(parents);

        return curve;
    };

    /**
     * @class This element is used to provide a constructor for an hyperbola. An hyperbola is given by two points (the foci) and a third point on the the hyperbola or
     * the length of the major axis.
     * @pseudo
     * @description
     * @name Hyperbola
     * @augments Conic
     * @constructor
     * @type JXG.Curve
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point,array_JXG.Point,array_JXG.Point,array} point1,point2,point3 Parent elements can be three elements either of type {@link JXG.Point} or array of
     * numbers describing the coordinates of a point. In the latter case the point will be constructed automatically as a fixed invisible point.
     * @param {JXG.Point,array_JXG.Point,array_number,function} point1,point2,number Parent elements can be two elements either of type {@link JXG.Point} or array of
     * numbers describing the coordinates of a point. The third parameter is a number/function which defines the length of the major axis
     * @param {Number} start (Optional) parameter of the curve start, default: -&pi;.
     * @param {Number} end (Optional) parameter for the curve end, default: &pi;.
     * @example
     * // Create an Hyperbola by three points
     * var A = board.create('point', [-1,4]);
     * var B = board.create('point', [-1,-4]);
     * var C = board.create('point', [1,1]);
     * var el = board.create('hyperbola',[A,B,C]);
     * </pre><div class="jxgbox" id="JXGcf99049d-a3fe-407f-b936-27d76550f8c4" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   (function(){
     *   var glex1_board = JXG.JSXGraph.initBoard('JXGcf99049d-a3fe-407f-b936-27d76550f8c4', {boundingbox:[-6,6,6,-6], keepaspectratio:true, showcopyright: false, shownavigation: false});
     *   var A = glex1_board.create('point', [-1,4]);
     *   var B = glex1_board.create('point', [-1,-4]);
     *   var C = glex1_board.create('point', [1,1]);
     *   var el = glex1_board.create('hyperbola',[A,B,C]);
     * })();
     * </script><pre>
     */
    JXG.createHyperbola = function (board, parents, attributes) {
        var polarForm, curve, M, C, majorAxis, i,
            // focus 1 and focus 2
            F = [],
            attr_foci = Type.copyAttributes(attributes, board.options, 'conic', 'foci'),
            attr_center = Type.copyAttributes(attributes, board.options, 'conic', 'center'),
            attr_curve = Type.copyAttributes(attributes, board.options, 'conic');

        // The foci and the third point are either points or coordinate arrays.
        for (i = 0; i < 2; i++) {
            // focus i given by coordinates
            if (parents[i].length > 1) {
                F[i] = board.create('point', parents[i], attr_foci);
            // focus i given by point
            } else if (Type.isPoint(parents[i])) {
                F[i] = board.select(parents[i]);
            // given by function
            } else if (Type.isFunction(parents[i]) && Type.isPoint(parents[i]()) ) {
                F[i] = parents[i]();
            // focus i given by point name
            } else if (Type.isString(parents[i])) {
                F[i] = board.select(parents[i]);
            } else {
                throw new Error("JSXGraph: Can't create Hyperbola with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                    "\nPossible parent types: [point,point,point], [point,point,number|function]");
            }
        }

        // length of major axis
        if (Type.isNumber(parents[2])) {
            majorAxis = Type.createFunction(parents[2], board);
        } else if (Type.isFunction(parents[2]) && Type.isNumber(parents[2]())) {
            majorAxis = parents[2];
        } else {
            // point on ellipse
            if (Type.isPoint(parents[2])) {
                C = board.select(parents[2]);
            // point on ellipse given by coordinates
            } else if (parents[2].length > 1) {
                C = board.create('point', parents[2], attr_foci);
            // given by function
            } else if (Type.isFunction(parents[2]) && Type.isPoint(parents[2]())) {
                C = parents[2]();
            // focus i given by point name
            } else if (Type.isString(parents[2])) {
                C = board.select(parents[2]);
            } else {
                throw new Error("JSXGraph: Can't create Hyperbola with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
                    "\nPossible parent types: [point,point,point], [point,point,number|function]");
            }
            /** @ignore */
            majorAxis = function () {
                return C.Dist(F[0]) - C.Dist(F[1]);
            };
        }

        // to
        if (!Type.exists(parents[4])) {
            parents[4] = 1.0001 * Math.PI;
        }

        // from
        if (!Type.exists(parents[3])) {
            parents[3] = -1.0001 * Math.PI;
        }

        M = board.create('point', [
            function () {
                return (F[0].X() + F[1].X()) * 0.5;
            },
            function () {
                return (F[0].Y() + F[1].Y()) * 0.5;
            }
        ], attr_center);

        curve = board.create('curve', [
            function (x) {
                return 0;
            },
            function (x) {
                return 0;
            }, parents[3], parents[4]], attr_curve);

        curve.majorAxis = majorAxis;

        // Hyperbola is defined by (a*sec(t),b*tan(t)) and sec(t) = 1/cos(t)
        /** @ignore */
        polarForm = function (phi, suspendUpdate) {
            var r, rr, ax, ay, bx, by, axbx, ayby, f;

            if (!suspendUpdate) {
                r = majorAxis();
                rr = r * r;
                ax = F[0].X();
                ay = F[0].Y();
                bx = F[1].X();
                by = F[1].Y();
                axbx = ax - bx;
                ayby = ay - by;
                f = (rr - ax * ax - ay * ay + bx * bx + by * by) / (2 * r);

                curve.quadraticform = [
                    [f * f - bx * bx - by * by, f * axbx / r + bx,      f * ayby / r + by],
                    [f * axbx / r + bx,         (axbx * axbx) / rr - 1, axbx * ayby / rr ],
                    [f * ayby / r + by,         axbx * ayby / rr,       (ayby * ayby) / rr - 1]
                ];
            }
        };

        /** @ignore */
        curve.X = function (phi, suspendUpdate) {
            var r = majorAxis(),
                c = F[1].Dist(F[0]),
                b = 0.5 * (c * c - r * r) / (c * Math.cos(phi) + r),
                beta = Math.atan2(F[1].Y() - F[0].Y(), F[1].X() - F[0].X());

            if (!suspendUpdate) {
                polarForm(phi, suspendUpdate);
            }

            return F[0].X() + Math.cos(beta + phi) * b;
        };

        /** @ignore */
        curve.Y = function (phi, suspendUpdate) {
            var r = majorAxis(),
                c = F[1].Dist(F[0]),
                b = 0.5 * (c * c - r * r) / (c * Math.cos(phi) + r),
                beta = Math.atan2(F[1].Y() - F[0].Y(), F[1].X() - F[0].X());

            return F[0].Y() + Math.sin(beta + phi) * b;
        };

        curve.midpoint = curve.center = M;
        curve.subs = {
            center: curve.center
        };
        curve.inherits.push(curve.center, F[0], F[1]);
        if (Type.isPoint(C)) {
            curve.inherits.push(C);
        }
        curve.type = Const.OBJECT_TYPE_CONIC;

        M.addChild(curve);
        for (i = 0; i < 2; i++) {
            if (Type.isPoint(F[i])) {
                F[i].addChild(curve);
            }
        }
        if (Type.isPoint(C)) {
            C.addChild(curve);
        }
        curve.setParents(parents);

        return curve;
    };

    /**
     * @class This element is used to provide a constructor for a parabola. A parabola is given by one point (the focus) and a line (the directrix).
     * @pseudo
     * @description
     * @name Parabola
     * @augments Conic
     * @constructor
     * @type JXG.Curve
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point,array_JXG.Line} point,line Parent elements are a point and a line or a pair of coordinates.
     * Optional parameters three and four are numbers which define the curve length (e.g. start/end). Default values are -pi and pi.
     * @example
     * // Create a parabola by a point C and a line l.
     * var A = board.create('point', [-1,4]);
     * var B = board.create('point', [-1,-4]);
     * var l = board.create('line', [A,B]);
     * var C = board.create('point', [1,1]);
     * var el = board.create('parabola',[C,l]);
     * </pre><div class="jxgbox" id="JXG524d1aae-217d-44d4-ac58-a19c7ab1de36" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function() {
     *   var glex1_board = JXG.JSXGraph.initBoard('JXG524d1aae-217d-44d4-ac58-a19c7ab1de36', {boundingbox:[-6,6,6,-6], keepaspectratio:true, showcopyright: false, shownavigation: false});
     *   var A = glex1_board.create('point', [-1,4]);
     *   var B = glex1_board.create('point', [-1,-4]);
     *   var l = glex1_board.create('line', [A,B]);
     *   var C = glex1_board.create('point', [1,1]);
     *   var el = glex1_board.create('parabola',[C,l]);
     * })();
     * </script><pre>
     *
     * @example
     * var par = board.create('parabola',[[3.25, 0], [[0.25, 1],[0.25, 0]]]);
     *
     * </pre><div id="JXG09252542-b77a-4990-a109-66ffb649a472" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG09252542-b77a-4990-a109-66ffb649a472',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var par = board.create('parabola',[[3.25, 0], [[0.25, 1],[0.25, 0]]]);
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createParabola = function (board, parents, attributes) {
        var polarForm, curve, M,
            // focus
            F1 = parents[0],
            // directrix
            l = parents[1],
            attr_foci = Type.copyAttributes(attributes, board.options, 'conic', 'foci'),
            attr_center = Type.copyAttributes(attributes, board.options, 'conic', 'center'),
            attr_curve = Type.copyAttributes(attributes, board.options, 'conic'),
            attr_line;

        // focus 1 given by coordinates
        if (parents[0].length > 1) {
            F1 = board.create('point', parents[0], attr_foci);
        // focus 1 given by point
        } else if (Type.isPoint(parents[0])) {
            F1 = board.select(parents[0]);
        // given by function
        } else if (Type.isFunction(parents[0]) && Type.isPoint(parents[0]()) ) {
            F1 = parents[0]();
        // focus 1 given by point name
        } else if (Type.isString(parents[0])) {
            F1 = board.select(parents[0]);
        } else {
            throw new Error("JSXGraph: Can't create Parabola with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [point,line]");
        }

        // Create line if given as array of two points.
        if (Type.isArray(l) && l.length === 2) {
            attr_line = Type.copyAttributes(attributes, board.options, 'conic', 'line');
            l = board.create('line', l, attr_line);
        }

        // to
        if (!Type.exists(parents[3])) {
            parents[3] = 2 * Math.PI;
        }

        // from
        if (!Type.exists(parents[2])) {
            parents[2] = 0;
        }

        M = board.create('point', [
            function () {
                /*
                var v = [0, l.stdform[1], l.stdform[2]];
                v = Mat.crossProduct(v, F1.coords.usrCoords);
                return Geometry.meetLineLine(v, l.stdform, 0, board).usrCoords;
                */
                return Geometry.projectPointToLine(F1, l, board).usrCoords;
            }
        ], attr_center);

        /** @ignore */
        curve = board.create('curve', [
            function (x) {
                return 0;
            },
            function (x) {
                return 0;
            }, parents[2], parents[3]], attr_curve);

        curve.midpoint = curve.center = M;
        curve.subs = {
            center: curve.center
        };
        curve.inherits.push(curve.center);

        /** @ignore */
        polarForm = function (t, suspendUpdate) {
            var a, b, c, ab, px, py;

            if (!suspendUpdate) {
                a = l.stdform[1];
                b = l.stdform[2];
                c = l.stdform[0];
                ab = a * a + b * b;
                px = F1.X();
                py = F1.Y();

                curve.quadraticform = [
                    [(c * c - ab * (px * px + py * py)), c * a + ab * px, c * b + ab * py],
                    [c * a + ab * px,                  -b * b,          a * b],
                    [c * b + ab * py,                  a * b,           -a * a]
                ];
            }
        };

        /** @ignore */
        curve.X = function (phi, suspendUpdate) {
            var a, det,
                beta = l.getAngle(),
                d = Geometry.distPointLine(F1.coords.usrCoords, l.stdform),
                A = l.point1.coords.usrCoords,
                B = l.point2.coords.usrCoords,
                M = F1.coords.usrCoords;

            // Handle the case if one of the two defining points of the line is an ideal point
            if (A[0] === 0) {
                A = [1, B[1] + l.stdform[2], B[2] - l.stdform[1]];
            } else if (B[0] === 0) {
                B = [1, A[1] + l.stdform[2], A[2] - l.stdform[1]];
            }
            det = ((B[1] - A[1]) * (M[2] - A[2]) - (B[2] - A[2]) * (M[1] - A[1]) >= 0) ? 1 : -1;
            a = det * d / (1 - Math.sin(phi));

            if (!suspendUpdate) {
                polarForm(phi, suspendUpdate);
            }

            return F1.X() + Math.cos(phi + beta) * a;
        };

        /** @ignore */
        curve.Y = function (phi, suspendUpdate) {
            var a, det,
                beta = l.getAngle(),
                d = Geometry.distPointLine(F1.coords.usrCoords, l.stdform),
                A = l.point1.coords.usrCoords,
                B = l.point2.coords.usrCoords,
                M = F1.coords.usrCoords;

            // Handle the case if one of the two defining points of the line is an ideal point
            if (A[0] === 0) {
                A = [1, B[1] + l.stdform[2], B[2] - l.stdform[1]];
            } else if (B[0] === 0) {
                B = [1, A[1] + l.stdform[2], A[2] - l.stdform[1]];
            }
            det = ((B[1] - A[1]) * (M[2] - A[2]) - (B[2] - A[2]) * (M[1] - A[1]) >= 0) ? 1 : -1;
            a = det * d / (1 - Math.sin(phi));

            return F1.Y() + Math.sin(phi + beta) * a;
        };

        curve.type = Const.OBJECT_TYPE_CONIC;
        M.addChild(curve);

        if (Type.isPoint(F1)) {
            F1.addChild(curve);
            curve.inherits.push(F1);
        }

        l.addChild(curve);
        curve.setParents(parents);

        return curve;
    };

    /**
     *
     * @class This element is used to provide a constructor for a generic conic section uniquely defined by five points or
     * a conic defined by the coefficients of the equation
     * <p><i>Ax<sup>2</sup>+ Bxy+Cy<sup>2</sup> + Dx + Ey + F = 0</i></p>.
     * Then the parameters are as follows:
     * <pre>
     *     board.create('conic', [A, C, F, B/2, D/2, E/2]);
     * </pre>
     * @pseudo
     * @description
     * @name Conic
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Conic
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point,Array_JXG.Point,Array_JXG.Point,Array_JXG.Point,Array_JXG.Point,Array} a,b,c,d,e Parent elements are five points.
     * @param {Number_Number_Number_Number_Number_Number} a_00,a_11,a_22,a_01,a_02,a_12 6 numbers, i.e. A, C, F, B/2, D/2, E/2
     * @example
     * // Create a conic section through the points A, B, C, D, and E.
     *  var A = board.create('point', [1,5]);
     *  var B = board.create('point', [1,2]);
     *  var C = board.create('point', [2,0]);
     *  var D = board.create('point', [0,0]);
     *  var E = board.create('point', [-1,5]);
     *  var conic = board.create('conic',[A,B,C,D,E]);
     * </pre><div class="jxgbox" id="JXG2d79bd6a-db9b-423c-9cba-2497f0b06320" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function(){
     *   var glex1_board = JXG.JSXGraph.initBoard('JXG2d79bd6a-db9b-423c-9cba-2497f0b06320', {boundingbox:[-6,6,6,-6], keepaspectratio:true, showcopyright: false, shownavigation: false});
     *   var A = glex1_board.create('point', [1,5]);
     *   var B = glex1_board.create('point', [1,2]);
     *   var C = glex1_board.create('point', [2,0]);
     *   var D = glex1_board.create('point', [0,0]);
     *   var E = glex1_board.create('point', [-1,5]);
     *   var conic = glex1_board.create('conic',[A,B,C,D,E]);
     * })();
     * </script><pre>
     *
     * @example
     * // Parameters: A, C, F, B/2, D/2, E/2
     * var conic = board.create('conic', [1, 2, -4, 0, 0, 0]s);
     *
     * </pre><div id="JXG8576a04a-52d8-4a7e-8d54-e32443910b97" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG8576a04a-52d8-4a7e-8d54-e32443910b97',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     // Parameters: A, C, F, B/2, D/2, E/2
     *     var conic = board.create('conic', [1, 2, -4, 0, 0, 0]s);
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createConic = function (board, parents, attributes) {
        var polarForm, curve, fitConic, degconic, sym,
            eigen, a, b, c, c1, c2,
            i, definingMat, givenByPoints,
            rotationMatrix = [
                [1, 0, 0],
                [0, 1, 0],
                [0, 0, 1]
            ],
            M = [
                [1, 0, 0],
                [0, 1, 0],
                [0, 0, 1]
            ],
            points = [],
            p = [],
            attr_point = Type.copyAttributes(attributes, board.options, 'conic', 'point'),
            attr_center = Type.copyAttributes(attributes, board.options, 'conic', 'center'),
            attr_curve = Type.copyAttributes(attributes, board.options, 'conic');

        if (parents.length === 5) {
            givenByPoints = true;
        } else if (parents.length === 6) {
            givenByPoints = false;
        } else {
            throw new Error("JSXGraph: Can't create generic Conic with " + parents.length + " parameters.");
        }

        if (givenByPoints) {
            for (i = 0; i < 5; i++) {
                // point i given by coordinates
                if (parents[i].length > 1) {
                    points[i] = board.create('point', parents[i], attr_point);
                // point i given by point
                } else if (Type.isPoint(parents[i])) {
                    points[i] = board.select(parents[i]);
                // given by function
                } else if (Type.isFunction(parents[i]) && Type.isPoint(parents[i]()) ) {
                    points[i] = parents[i]();
                // point i given by point name
                } else if (Type.isString(parents[i])) {
                    points[i] = board.select(parents[i]);
                } else {
                    throw new Error("JSXGraph: Can't create Conic section with parent types '" + (typeof parents[i]) + "'." +
                        "\nPossible parent types: [point,point,point,point,point], [a00,a11,a22,a01,a02,a12]");
                }
            }
        } else {
            /* Usual notation (x,y,z):
             *  [[A0,A3,A4],
             *   [A3,A1,A5],
             *   [A4,A5,A2]].
             * Our notation (z,x,y):
             *  [[A2, A4, A5],
             *   [A4, A0, A3],
             *   [A5, A3, A1]]
             */
            definingMat = [
                [0, 0, 0],
                [0, 0, 0],
                [0, 0, 0]
            ];
            definingMat[0][0] = (Type.isFunction(parents[2])) ? function () { return parents[2](); } : function () { return parents[2]; };
            definingMat[0][1] = (Type.isFunction(parents[4])) ? function () { return parents[4](); } : function () { return parents[4]; };
            definingMat[0][2] = (Type.isFunction(parents[5])) ? function () { return parents[5](); } : function () { return parents[5]; };
            definingMat[1][1] = (Type.isFunction(parents[0])) ? function () { return parents[0](); } : function () { return parents[0]; };
            definingMat[1][2] = (Type.isFunction(parents[3])) ? function () { return parents[3](); } : function () { return parents[3]; };
            definingMat[2][2] = (Type.isFunction(parents[1])) ? function () { return parents[1](); } : function () { return parents[1]; };
        }

        // sym(A) = A + A^t . Manipulates A in place.
        sym = function (A) {
            var i, j;
            for (i = 0; i < 3; i++) {
                for (j = i; j < 3; j++) {
                    A[i][j] += A[j][i];
                }
            }
            for (i = 0; i < 3; i++) {
                for (j = 0; j < i; j++) {
                    A[i][j] = A[j][i];
                }
            }
            return A;
        };

        // degconic(v,w) = sym(v*w^t)
        degconic = function (v, w) {
            var i, j, mat = [
                [0, 0, 0],
                [0, 0, 0],
                [0, 0, 0]
            ];

            for (i = 0; i < 3; i++) {
                for (j = 0; j < 3; j++) {
                    mat[i][j] = v[i] * w[j];
                }
            }

            return sym(mat);
        };

        // (p^t*B*p)*A-(p^t*A*p)*B
        fitConic = function (A, B, p) {
            var i, j, pBp, pAp, Mv,
                mat = [
                    [0, 0, 0],
                    [0, 0, 0],
                    [0, 0, 0]
                ];

            Mv = Mat.matVecMult(B, p);
            pBp = Mat.innerProduct(p, Mv);
            Mv = Mat.matVecMult(A, p);
            pAp = Mat.innerProduct(p, Mv);

            for (i = 0; i < 3; i++) {
                for (j = 0; j < 3; j++) {
                    mat[i][j] = pBp * A[i][j] - pAp * B[i][j];
                }
            }
            return mat;
        };

        // Here, the defining functions for the curve are just dummy functions.
        // In polarForm there is a reference to curve.quadraticform.
        curve = board.create('curve', [
            function (x) {
                return 0;
            },
            function (x) {
                return 0;
            }, 0, 2 * Math.PI], attr_curve);

        /** @ignore */
        polarForm = function (phi, suspendUpdate) {
            var i, j, len, v;

            if (!suspendUpdate) {
                if (givenByPoints) {
                    // Copy the point coordinate vectors
                    for (i = 0; i < 5; i++) {
                        p[i] = points[i].coords.usrCoords;
                    }

                    // Compute the quadratic form
                    c1 = degconic(Mat.crossProduct(p[0], p[1]), Mat.crossProduct(p[2], p[3]));
                    c2 = degconic(Mat.crossProduct(p[0], p[2]), Mat.crossProduct(p[1], p[3]));
                    M = fitConic(c1, c2, p[4]);
                } else {
                    for (i = 0; i < 3; i++) {
                        for (j = i; j < 3; j++) {
                            M[i][j] = definingMat[i][j]();
                            if (j > i) {
                                M[j][i] = M[i][j];
                            }
                        }
                    }
                }

                // Here is the reference back to the curve.
                curve.quadraticform = M;

                // Compute Eigenvalues and Eigenvectors
                eigen = Numerics.Jacobi(M);

                // Scale the Eigenvalues such that the first Eigenvalue is positive
                if (eigen[0][0][0] < 0) {
                    eigen[0][0][0] *= (-1);
                    eigen[0][1][1] *= (-1);
                    eigen[0][2][2] *= (-1);
                }

                // Normalize the Eigenvectors
                for (i = 0; i < 3; i++) {
                    len = 0.0;
                    for (j = 0; j < 3; j++) {
                        len += eigen[1][j][i] * eigen[1][j][i];
                    }
                    len = Math.sqrt(len);
                    /*for (j = 0; j < 3; j++) {
                        //eigen[1][j][i] /= len;
                    }*/
                }
                rotationMatrix = eigen[1];
                c = Math.sqrt(Math.abs(eigen[0][0][0]));
                a = Math.sqrt(Math.abs(eigen[0][1][1]));
                b = Math.sqrt(Math.abs(eigen[0][2][2]));

            }

            // The degenerate cases with eigen[0][i][i]==0 are not handled correct yet.
            if (eigen[0][1][1] <= 0.0 && eigen[0][2][2] <= 0.0) {
                v = Mat.matVecMult(rotationMatrix, [1 / c, Math.cos(phi) / a, Math.sin(phi) / b]);
            } else if (eigen[0][1][1] <= 0.0 && eigen[0][2][2] > 0.0) {
                v = Mat.matVecMult(rotationMatrix, [Math.cos(phi) / c, 1 / a, Math.sin(phi) / b]);
            } else if (eigen[0][2][2] < 0.0) {
                v = Mat.matVecMult(rotationMatrix, [Math.sin(phi) / c, Math.cos(phi) / a, 1 / b]);
            }

            if (Type.exists(v)) {
                // Normalize
                v[1] /= v[0];
                v[2] /= v[0];
                v[0] = 1.0;
            } else {
                v = [1, NaN, NaN];
            }

            return v;
        };

        /** @ignore */
        curve.X = function (phi, suspendUpdate) {
            return polarForm(phi, suspendUpdate)[1];
        };

        /** @ignore */
        curve.Y = function (phi, suspendUpdate) {
            return polarForm(phi, suspendUpdate)[2];
        };

        // Center coordinates see http://en.wikipedia.org/wiki/Matrix_representation_of_conic_sections
        curve.midpoint = board.create('point', [
            function () {
                var m = curve.quadraticform;

                return [
                    m[1][1] * m[2][2] - m[1][2] * m[1][2],
                    m[1][2] * m[0][2] - m[2][2] * m[0][1],
                    m[0][1] * m[1][2] - m[1][1] * m[0][2]
                ];
            }
        ], attr_center);

        curve.type = Const.OBJECT_TYPE_CONIC;
        curve.center = curve.midpoint;
        curve.subs = {
            center: curve.center
        };
        curve.inherits.push(curve.center);
        curve.inherits = curve.inherits.concat(points);

        if (givenByPoints) {
            for (i = 0; i < 5; i++) {
                if (Type.isPoint(points[i])) {
                    points[i].addChild(curve);
                }
            }
            curve.setParents(parents);
        }
        curve.addChild(curve.center);

        return curve;
    };

    JXG.registerElement('ellipse', JXG.createEllipse);
    JXG.registerElement('hyperbola', JXG.createHyperbola);
    JXG.registerElement('parabola', JXG.createParabola);
    JXG.registerElement('conic', JXG.createConic);

    return {
        createEllipse: JXG.createEllipse,
        createHyperbola: JXG.createHyperbola,
        createParabola: JXG.createParabola,
        createConic: JXG.createConic
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/element
 base/constants
 base/coords
 parser/geonext
 math/geometry
 math/statistics
 utils/type
  elements:
   transform
   point
 */

/**
 * @fileoverview The geometry object Circle is defined in this file. Circle stores all
 * style and functional properties that are required to draw and move a circle on
 * a board.
 */

define('base/circle',[
    'jxg', 'base/element', 'base/coords', 'base/constants', 'element/conic', 'parser/geonext', 'utils/type'
], function (JXG, GeometryElement, Coords, Const, Conic, GeonextParser, Type) {

    "use strict";

    /**
     * A circle consists of all points with a given distance from one point. This point is called center, the distance is called radius.
     * A circle can be constructed by providing a center and a point on the circle or a center and a radius (given as a number, function,
     * line, or circle).
     * @class Creates a new circle object. Do not use this constructor to create a circle. Use {@link JXG.Board#create} with
     * type {@link Circle} instead.
     * @constructor
     * @augments JXG.GeometryElement
     * @param {JXG.Board} board The board the new circle is drawn on.
     * @param {String} method Can be
     * <ul><li> <b>'twoPoints'</b> which means the circle is defined by its center and a point on the circle.</li>
     * <li><b>'pointRadius'</b> which means the circle is defined by its center and its radius in user units</li>
     * <li><b>'pointLine'</b> which means the circle is defined by its center and its radius given by the distance from the startpoint and the endpoint of the line</li>
     * <li><b>'pointCircle'</b> which means the circle is defined by its center and its radius given by the radius of another circle</li></ul>
     * The parameters p1, p2 and radius must be set according to this method parameter.
     * @param {JXG.Point} par1 center of the circle.
     * @param {JXG.Point|JXG.Line|JXG.Circle} par2 Can be
     * <ul><li>a point on the circle if method is 'twoPoints'</li>
     * <li>a line if the method is 'pointLine'</li>
     * <li>a circle if the method is 'pointCircle'</li></ul>
     * @param {Object} attributes
     * @see JXG.Board#generateName
     */
    JXG.Circle = function (board, method, par1, par2, attributes) {
        // Call the constructor of GeometryElement
        this.constructor(board, attributes, Const.OBJECT_TYPE_CIRCLE, Const.OBJECT_CLASS_CIRCLE);

        /**
         * Stores the given method.
         * Can be
         * <ul><li><b>'twoPoints'</b> which means the circle is defined by its center and a point on the circle.</li>
         * <li><b>'pointRadius'</b> which means the circle is defined by its center and its radius given in user units or as term.</li>
         * <li><b>'pointLine'</b> which means the circle is defined by its center and its radius given by the distance from the startpoint and the endpoint of the line.</li>
         * <li><b>'pointCircle'</b> which means the circle is defined by its center and its radius given by the radius of another circle.</li></ul>
         * @type String
         * @see #center
         * @see #point2
         * @see #radius
         * @see #line
         * @see #circle
         */
        this.method = method;

        // this is kept so existing code won't ne broken
        this.midpoint = this.board.select(par1);

        /**
         * The circles center. Do not set this parameter directly as it will break JSXGraph's update system.
         * @type JXG.Point
         */
        this.center = this.board.select(par1);

        /** Point on the circle only set if method equals 'twoPoints'. Do not set this parameter directly as it will break JSXGraph's update system.
         * @type JXG.Point
         * @see #method
         */
        this.point2 = null;

        /** Radius of the circle
         * only set if method equals 'pointRadius'
         * @type Number
         * @default null
         * @see #method
         */
        this.radius = 0;

        /** Line defining the radius of the circle given by the distance from the startpoint and the endpoint of the line
         * only set if method equals 'pointLine'. Do not set this parameter directly as it will break JSXGraph's update system.
         * @type JXG.Line
         * @default null
         * @see #method
         */
        this.line = null;

        /** Circle defining the radius of the circle given by the radius of the other circle
         * only set if method equals 'pointLine'. Do not set this parameter directly as it will break JSXGraph's update system.
         * @type JXG.Circle
         * @default null
         * @see #method
         */
        this.circle = null;

        this.points = [];

        if (method === 'twoPoints') {
            this.point2 = board.select(par2);
            this.radius = this.Radius();
        } else if (method === 'pointRadius') {
            this.gxtterm = par2;
            // Converts GEONExT syntax into JavaScript syntax and generally ensures that the radius is a function
            this.updateRadius = Type.createFunction(par2, this.board, null, true);
            // First evaluation of the radius function
            this.updateRadius();
        } else if (method === 'pointLine') {
            // dann ist p2 die Id eines Objekts vom Typ Line!
            this.line = board.select(par2);
            this.radius = this.line.point1.coords.distance(Const.COORDS_BY_USER, this.line.point2.coords);
        } else if (method === 'pointCircle') {
            // dann ist p2 die Id eines Objekts vom Typ Circle!
            this.circle = board.select(par2);
            this.radius = this.circle.Radius();
        }

        // create Label
        this.id = this.board.setId(this, 'C');
        this.board.renderer.drawEllipse(this);
        this.board.finalizeAdding(this);

        this.createGradient();
        this.elType = 'circle';
        this.createLabel();

        if (Type.exists(this.center._is_new)) {
            this.addChild(this.center);
            delete this.center._is_new;
        } else {
            this.center.addChild(this);
        }

        if (method === 'pointRadius') {
            this.notifyParents(par2);
        } else if (method === 'pointLine') {
            this.line.addChild(this);
        } else if (method === 'pointCircle') {
            this.circle.addChild(this);
        } else if (method === 'twoPoints') {
            if (Type.exists(this.point2._is_new)) {
                this.addChild(this.point2);
                delete this.point2._is_new;
            } else {
                this.point2.addChild(this);
            }
        }

        this.methodMap = Type.deepCopy(this.methodMap, {
            setRadius: 'setRadius',
            getRadius: 'getRadius',
            Area: 'Area',
            area: 'Area',
            radius: 'Radius',
            center: 'center',
            line: 'line',
            point2: 'point2'
        });
    };

    JXG.Circle.prototype = new GeometryElement();

    JXG.extend(JXG.Circle.prototype, /** @lends JXG.Circle.prototype */ {
        /**
         * Checks whether (x,y) is near the circle line or inside of the ellipse
         * (in case JXG.Options.conic#hasInnerPoints is true).
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @returns {Boolean} True if (x,y) is near the circle, False otherwise.
         * @private
         */
        hasPoint: function (x, y) {
            var prec, type,
                mp = this.center.coords.usrCoords,
                p = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
                r = this.Radius(),
                dx, dy, dist;


            if (Type.isObject(Type.evaluate(this.visProp.precision))) {
                type = this.board._inputDevice;
                prec = Type.evaluate(this.visProp.precision[type]);
            } else {
                // 'inherit'
                prec = this.board.options.precision.hasPoint;
            }
            dx = mp[1] - p.usrCoords[1];
            dy = mp[2] - p.usrCoords[2];
            dist = Math.sqrt(dx * dx + dy * dy);
            // We have to use usrCoords, since Radius is available in usrCoords only.
            prec += Type.evaluate(this.visProp.strokewidth) * 0.5;
            prec /= Math.sqrt(this.board.unitX * this.board.unitY);

            if (Type.evaluate(this.visProp.hasinnerpoints)) {
                return (dist < r + prec);
            }

            return (Math.abs(dist - r) < prec);
        },

        /**
         * Used to generate a polynomial for a point p that lies on this circle.
         * @param {JXG.Point} p The point for which the polynomial is generated.
         * @returns {Array} An array containing the generated polynomial.
         * @private
         */
        generatePolynomial: function (p) {
            /*
             * We have four methods to construct a circle:
             *   (a) Two points
             *   (b) center and radius
             *   (c) center and radius given by length of a segment
             *   (d) center and radius given by another circle
             *
             * In case (b) we have to distinguish two cases:
             *  (i)  radius is given as a number
             *  (ii) radius is given as a function
             * In the latter case there's no guarantee the radius depends on other geometry elements
             * in a polynomial way so this case has to be omitted.
             *
             * Another tricky case is case (d):
             * The radius depends on another circle so we have to cycle through the ancestors of each circle
             * until we reach one that's radius does not depend on another circles radius.
             *
             *
             * All cases (a) to (d) vary only in calculation of the radius. So the basic formulae for
             * a glider G (g1,g2) on a circle with center M (m1,m2) and radius r is just:
             *
             *     (g1-m1)^2 + (g2-m2)^2 - r^2 = 0
             *
             * So the easiest case is (b) with a fixed radius given as a number. The other two cases (a)
             * and (c) are quite the same: Euclidean distance between two points A (a1,a2) and B (b1,b2),
             * squared:
             *
             *     r^2 = (a1-b1)^2 + (a2-b2)^2
             *
             * For case (d) we have to cycle recursively through all defining circles and finally return the
             * formulae for calculating r^2. For that we use JXG.Circle.symbolic.generateRadiusSquared().
             */
            var m1 = this.center.symbolic.x,
                m2 = this.center.symbolic.y,
                g1 = p.symbolic.x,
                g2 = p.symbolic.y,
                rsq = this.generateRadiusSquared();

            /* No radius can be calculated (Case b.ii) */
            if (rsq === '') {
                return [];
            }

            return ['((' + g1 + ')-(' + m1 + '))^2 + ((' + g2 + ')-(' + m2 + '))^2 - (' + rsq + ')'];
        },

        /**
         * Generate symbolic radius calculation for loci determination with Groebner-Basis algorithm.
         * @returns {String} String containing symbolic calculation of the circle's radius or an empty string
         * if the radius can't be expressed in a polynomial equation.
         * @private
         */
        generateRadiusSquared: function () {
            /*
             * Four cases:
             *
             *   (a) Two points
             *   (b) center and radius
             *   (c) center and radius given by length of a segment
             *   (d) center and radius given by another circle
             */
            var m1, m2, p1, p2, q1, q2,
                rsq = '';

            if (this.method === "twoPoints") {
                m1 = this.center.symbolic.x;
                m2 = this.center.symbolic.y;
                p1 = this.point2.symbolic.x;
                p2 = this.point2.symbolic.y;

                rsq = '((' + p1 + ')-(' + m1 + '))^2 + ((' + p2 + ')-(' + m2 + '))^2';
            } else if (this.method === "pointRadius") {
                if (Type.isNumber(this.radius)) {
                    rsq = (this.radius * this.radius).toString();
                }
            } else if (this.method === "pointLine") {
                p1 = this.line.point1.symbolic.x;
                p2 = this.line.point1.symbolic.y;

                q1 = this.line.point2.symbolic.x;
                q2 = this.line.point2.symbolic.y;

                rsq = '((' + p1 + ')-(' + q1 + '))^2 + ((' + p2 + ')-(' + q2 + '))^2';
            } else if (this.method === "pointCircle") {
                rsq = this.circle.Radius();
            }

            return rsq;
        },

        /**
         * Uses the boards renderer to update the circle.
         */
        update: function () {
            var x, y, z, r, c, i;

            if (this.needsUpdate) {
                if (Type.evaluate(this.visProp.trace)) {
                    this.cloneToBackground(true);
                }

                if (this.method === 'pointLine') {
                    this.radius = this.line.point1.coords.distance(Const.COORDS_BY_USER, this.line.point2.coords);
                } else if (this.method === 'pointCircle') {
                    this.radius = this.circle.Radius();
                } else if (this.method === 'pointRadius') {
                    this.radius = this.updateRadius();
                }

                this.updateStdform();
                this.updateQuadraticform();

                // Approximate the circle by 4 Bezier segments
                // This will be used for intersections of type curve / circle.
                // See https://spencermortensen.com/articles/bezier-circle/
                z = this.center.coords.usrCoords[0];
                x = this.center.coords.usrCoords[1] / z;
                y = this.center.coords.usrCoords[2] / z;
                z /= z;
                r = this.Radius();
                c = 0.551915024494;

                this.numberPoints = 13;
                this.dataX = [x + r, x + r, x + r * c, x, x - r * c, x - r, x - r, x - r, x - r * c, x, x + r * c, x + r, x + r];
                this.dataY = [y, y + r * c, y + r, y + r, y + r, y + r * c, y, y - r * c, y - r, y - r, y - r, y - r * c, y];
                this.bezierDegree = 3;
                for (i = 0; i < this.numberPoints; i++) {
                    this.points[i] = new Coords(Const.COORDS_BY_USER, [this.dataX[i], this.dataY[i]], this.board);
                }
            }

            return this;
        },

        /**
         * Updates this circle's {@link JXG.Circle#quadraticform}.
         * @private
         */
        updateQuadraticform: function () {
            var m = this.center,
                mX = m.X(),
                mY = m.Y(),
                r = this.Radius();

            this.quadraticform = [
                [mX * mX + mY * mY - r * r, -mX, -mY],
                [-mX, 1, 0],
                [-mY, 0, 1]
            ];
        },

        /**
         * Updates the stdform derived from the position of the center and the circle's radius.
         * @private
         */
        updateStdform: function () {
            this.stdform[3] = 0.5;
            this.stdform[4] = this.Radius();
            this.stdform[1] = -this.center.coords.usrCoords[1];
            this.stdform[2] = -this.center.coords.usrCoords[2];
            if (!isFinite(this.stdform[4])) {
                this.stdform[0] = Type.exists(this.point2) ? -(
                    this.stdform[1] * this.point2.coords.usrCoords[1] +
                    this.stdform[2] * this.point2.coords.usrCoords[2]
                ) : 0;
            }
            this.normalize();
        },

        /**
         * Uses the boards renderer to update the circle.
         * @private
         */
        updateRenderer: function () {
            // var wasReal;

            if (!this.needsUpdate) {
                return this;
            }

            if (this.visPropCalc.visible) {
                // wasReal = this.isReal;
                this.isReal = (!isNaN(this.center.coords.usrCoords[1] + this.center.coords.usrCoords[2] + this.Radius())) && this.center.isReal;

                if (//wasReal &&
                    !this.isReal) {
                    this.updateVisibility(false);
                }
            }

            // Update the position
            if (this.visPropCalc.visible) {
                this.board.renderer.updateEllipse(this);
            }

            // Update the label if visible.
            if (this.hasLabel && this.visPropCalc.visible && this.label &&
                this.label.visPropCalc.visible && this.isReal) {

                this.label.update();
                this.board.renderer.updateText(this.label);
            }

            // Update rendNode display
            this.setDisplayRendNode();
            // if (this.visPropCalc.visible !== this.visPropOld.visible) {
            //     this.board.renderer.display(this, this.visPropCalc.visible);
            //     this.visPropOld.visible = this.visPropCalc.visible;
            //
            //     if (this.hasLabel) {
            //         this.board.renderer.display(this.label, this.label.visPropCalc.visible);
            //     }
            // }

            this.needsUpdate = false;
            return this;
        },

        /**
         * Finds dependencies in a given term and resolves them by adding the elements referenced in this
         * string to the circle's list of ancestors.
         * @param {String} contentStr
         * @private
         */
        notifyParents: function (contentStr) {
            if (Type.isString(contentStr)) {
                GeonextParser.findDependencies(this, contentStr, this.board);
            }
        },

        /**
         * Set a new radius, then update the board.
         * @param {String|Number|function} r A string, function or number describing the new radius.
         * @returns {JXG.Circle} Reference to this circle
         */
        setRadius: function (r) {
            this.updateRadius = Type.createFunction(r, this.board, null, true);
            this.board.update();

            return this;
        },

        /**
         * Calculates the radius of the circle.
         * @param {String|Number|function} [value] Set new radius
         * @returns {Number} The radius of the circle
         */
        Radius: function (value) {
            if (Type.exists(value)) {
                this.setRadius(value);
                return this.Radius();
            }

            if (this.method === 'twoPoints') {
                if (Type.cmpArrays(this.point2.coords.usrCoords, [0, 0, 0]) ||
                        Type.cmpArrays(this.center.coords.usrCoords, [0, 0, 0])) {

                    return NaN;
                }

                return this.center.Dist(this.point2);
            }

            if (this.method === 'pointLine' || this.method === 'pointCircle') {
                return this.radius;
            }

            if (this.method === 'pointRadius') {
                return this.updateRadius();
            }

            return NaN;
        },

        /**
         * Use {@link JXG.Circle#Radius}.
         * @deprecated
         */
        getRadius: function () {
            JXG.deprecated('Circle.getRadius()', 'Circle.Radius()');
            return this.Radius();
        },

        // documented in geometry element
        getTextAnchor: function () {
            return this.center.coords;
        },

        // documented in geometry element
        getLabelAnchor: function () {
            var x, y,
                r = this.Radius(),
                c = this.center.coords.usrCoords,
                SQRTH = 7.07106781186547524401E-1;      // sqrt(2)/2

            switch (Type.evaluate(this.visProp.label.position)) {
            case 'lft':
                x = c[1] - r;
                y = c[2];
                break;
            case 'llft':
                x = c[1] - SQRTH * r;
                y = c[2] - SQRTH * r;
                break;
            case 'rt':
                x = c[1] + r;
                y = c[2];
                break;
            case 'lrt':
                x = c[1] + SQRTH * r;
                y = c[2] - SQRTH * r;
                break;
            case 'urt':
                x = c[1] + SQRTH * r;
                y = c[2] + SQRTH * r;
                break;
            case 'top':
                x = c[1];
                y = c[2] + r;
                break;
            case 'bot':
                x = c[1];
                y = c[2] - r;
                break;
            default:
                // includes case 'ulft'
                x = c[1] - SQRTH * r;
                y = c[2] + SQRTH * r;
                break;
            }

            return new Coords(Const.COORDS_BY_USER, [x, y], this.board);
        },

        // documented in geometry element
        cloneToBackground: function () {
            var er,
                r = this.Radius(),
                copy = {
                    id: this.id + 'T' + this.numTraces,
                    elementClass: Const.OBJECT_CLASS_CIRCLE,
                    center: {
                        coords: this.center.coords
                    },
                    Radius: function () {
                        return r;
                    },
                    getRadius: function () {
                        return r;
                    },
                    board: this.board,
                    visProp: Type.deepCopy(this.visProp, this.visProp.traceattributes, true)
                };

            copy.visProp.layer = this.board.options.layer.trace;

            this.numTraces++;
            Type.clearVisPropOld(copy);
            copy.visPropCalc = {
                visible: Type.evaluate(copy.visProp.visible)
            };

            er = this.board.renderer.enhancedRendering;
            this.board.renderer.enhancedRendering = true;
            this.board.renderer.drawEllipse(copy);
            this.board.renderer.enhancedRendering = er;
            this.traces[copy.id] = copy.rendNode;

            return this;
        },

        /**
         * Add transformations to this circle.
         * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s.
         * @returns {JXG.Circle} Reference to this circle object.
         */
        addTransform: function (transform) {
            var i,
                list = Type.isArray(transform) ? transform : [transform],
                len = list.length;

            for (i = 0; i < len; i++) {
                this.center.transformations.push(list[i]);

                if (this.method === 'twoPoints') {
                    this.point2.transformations.push(list[i]);
                }
            }

            return this;
        },

        // see element.js
        snapToGrid: function () {
            var forceIt = Type.evaluate(this.visProp.snaptogrid);

            this.center.handleSnapToGrid(forceIt, true);
            if (this.method === 'twoPoints') {
                this.point2.handleSnapToGrid(forceIt, true);
            }

            return this;
        },

        // see element.js
        snapToPoints: function () {
            var forceIt = Type.evaluate(this.visProp.snaptopoints);

            this.center.handleSnapToPoints(forceIt);
            if (this.method === 'twoPoints') {
                this.point2.handleSnapToPoints(forceIt);
            }

            return this;
        },

        /**
         * Treats the circle as parametric curve and calculates its X coordinate.
         * @param {Number} t Number between 0 and 1.
         * @returns {Number} <tt>X(t)= radius*cos(t)+centerX</tt>.
         */
        X: function (t) {
            return this.Radius() * Math.cos(t * 2 * Math.PI) + this.center.coords.usrCoords[1];
        },

        /**
         * Treats the circle as parametric curve and calculates its Y coordinate.
         * @param {Number} t Number between 0 and 1.
         * @returns {Number} <tt>X(t)= radius*sin(t)+centerY</tt>.
         */
        Y: function (t) {
            return this.Radius() * Math.sin(t * 2 * Math.PI) + this.center.coords.usrCoords[2];
        },

        /**
         * Treat the circle as parametric curve and calculates its Z coordinate.
         * @param {Number} t ignored
         * @returns {Number} 1.0
         */
        Z: function (t) {
            return 1.0;
        },

        /**
         * Returns 0.
         * @private
         */
        minX: function () {
            return 0.0;
        },

        /**
         * Returns 1.
         * @private
         */
        maxX: function () {
            return 1.0;
        },

        /**
         * Circle area
         * @returns {Number} area of the circle.
         */
        Area: function () {
            var r = this.Radius();

            return r * r * Math.PI;
        },

        /**
         * Get bounding box of the circle.
         * @returns {Array} [x1, y1, x2, y2]
         */
        bounds: function () {
            var uc = this.center.coords.usrCoords,
                r = this.Radius();

            return [uc[1] - r, uc[2] + r, uc[1] + r, uc[2] - r];
        },

        /**
         * Get data to construct this element. Data consists of the parent elements
         * and static data like radius.
         * @returns {Array} data necessary to construct this element
         */
        getParents: function() {
            if (this.parents.length === 1) {  // i.e. this.method === 'pointRadius'
                return this.parents.concat(this.radius);
            }
            return this.parents;
        }
    });

    /**
     * @class This element is used to provide a constructor for a circle.
     * @pseudo
     * @description  A circle consists of all points with a given distance from one point. This point is called center, the distance is called radius.
     * A circle can be constructed by providing a center and a point on the circle or a center and a radius (given as a number, function,
     * line, or circle).
     * @name Circle
     * @augments JXG.Circle
     * @constructor
     * @type JXG.Circle
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_number,JXG.Point,JXG.Line,JXG.Circle} center,radius The center must be given as a {@link JXG.Point}, see {@link JXG.providePoints}, but the radius can be given
     * as a number (which will create a circle with a fixed radius), another {@link JXG.Point}, a {@link JXG.Line} (the distance of start and end point of the
     * line will determine the radius), or another {@link JXG.Circle}.
     * @example
     * // Create a circle providing two points
     * var p1 = board.create('point', [2.0, 2.0]),
     *     p2 = board.create('point', [2.0, 0.0]),
     *     c1 = board.create('circle', [p1, p2]);
     *
     * // Create another circle using the above circle
     * var p3 = board.create('point', [3.0, 2.0]),
     *     c2 = board.create('circle', [p3, c1]);
     * </pre><div class="jxgbox" id="JXG5f304d31-ef20-4a8e-9c0e-ea1a2b6c79e0" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     * (function() {
     *   var cex1_board = JXG.JSXGraph.initBoard('JXG5f304d31-ef20-4a8e-9c0e-ea1a2b6c79e0', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *       cex1_p1 = cex1_board.create('point', [2.0, 2.0]),
     *       cex1_p2 = cex1_board.create('point', [2.0, 0.0]),
     *       cex1_c1 = cex1_board.create('circle', [cex1_p1, cex1_p2]),
     *       cex1_p3 = cex1_board.create('point', [3.0, 2.0]),
     *       cex1_c2 = cex1_board.create('circle', [cex1_p3, cex1_c1]);
     * })();
     * </script><pre>
     * @example
     * // Create a circle providing two points
     * var p1 = board.create('point', [2.0, 2.0]),
     *     c1 = board.create('circle', [p1, 3]);
     *
     * // Create another circle using the above circle
     * var c2 = board.create('circle', [function() { return [p1.X(), p1.Y() + 1];}, function() { return c1.Radius(); }]);
     * </pre><div class="jxgbox" id="JXG54165f60-93b9-441d-8979-ac5d0f193020" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     * (function() {
     * var board = JXG.JSXGraph.initBoard('JXG54165f60-93b9-441d-8979-ac5d0f193020', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     * var p1 = board.create('point', [2.0, 2.0]);
     * var c1 = board.create('circle', [p1, 3]);
     *
     * // Create another circle using the above circle
     * var c2 = board.create('circle', [function() { return [p1.X(), p1.Y() + 1];}, function() { return c1.Radius(); }]);
     * })();
     * </script><pre>
     * @example
     * var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
     * var reflect = board.create('transform', [li], {type: 'reflect'});
     *
     * var c1 = board.create('circle', [[-2,-2], [-2, -1]], {center: {visible:true}});
     * var c2 = board.create('circle', [c1, reflect]);
     *      * </pre><div id="JXGa2a5a870-5dbb-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGa2a5a870-5dbb-11e8-9fb9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *             var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
     *             var reflect = board.create('transform', [li], {type: 'reflect'});
     *
     *             var c1 = board.create('circle', [[-2,-2], [-2, -1]], {center: {visible:true}});
     *             var c2 = board.create('circle', [c1, reflect]);
     *     })();
     *
     * </script><pre>
     *
     * @example
     * var t = board.create('transform', [2, 1.5], {type: 'scale'});
     * var c1 = board.create('circle', [[1.3, 1.3], [0, 1.3]], {strokeColor: 'black', center: {visible:true}});
     * var c2 = board.create('circle', [c1, t], {strokeColor: 'black'});
     *
     * </pre><div id="JXG0686a222-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG0686a222-6339-11e8-9fb9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
     *     var c1 = board.create('circle', [[1.3, 1.3], [0, 1.3]], {strokeColor: 'black', center: {visible:true}});
     *     var c2 = board.create('circle', [c1, t], {strokeColor: 'black'});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createCircle = function (board, parents, attributes) {
        var el, p, i, attr, obj,
            isDraggable = true,
            point_style = ['center', 'point2'];

        p = [];
        obj = board.select(parents[0]);
        if (Type.isObject(obj) && obj.elementClass === Const.OBJECT_CLASS_CIRCLE &&
            Type.isTransformationOrArray(parents[1])) {

            attr = Type.copyAttributes(attributes, board.options, 'circle');
            // if (!Type.exists(attr.type) || attr.type.toLowerCase() !== 'euclidean') {
            //     // Create a circle element from a circle and a Euclidean transformation
            //     el = JXG.createCircle(board, [obj.center, function() { return obj.Radius(); }], attr);
            // } else {
                // Create a conic element from a circle and a projective transformation
                el = Conic.createEllipse(board, [obj.center, obj.center, function() { return 2 * obj.Radius(); }], attr);
            // }
            el.addTransform(parents[1]);
            return el;

        }
        // Circle defined by points
        for (i = 0; i < parents.length; i++) {
            if (Type.isPointType(board, parents[i])) {
                p = p.concat(Type.providePoints(board, [parents[i]], attributes, 'circle', [point_style[i]]));
                if (p[p.length - 1] === false) {
                    throw new Error('JSXGraph: Can\'t create circle from this type. Please provide a point type.');
                }
            } else {
                p.push(parents[i]);
            }
        }

        attr = Type.copyAttributes(attributes, board.options, 'circle');

        if (p.length === 2 && Type.isPoint(p[0]) && Type.isPoint(p[1])) {
            // Point/Point
            el = new JXG.Circle(board, 'twoPoints', p[0], p[1], attr);
        } else if ((Type.isNumber(p[0]) || Type.isFunction(p[0]) || Type.isString(p[0])) &&
                    Type.isPoint(p[1])) {
            // Number/Point
            el = new JXG.Circle(board, 'pointRadius', p[1], p[0], attr);
        } else if ((Type.isNumber(p[1]) || Type.isFunction(p[1]) || Type.isString(p[1])) &&
                Type.isPoint(p[0])) {
            // Point/Number
            el = new JXG.Circle(board, 'pointRadius', p[0], p[1], attr);
        } else if ((p[0].elementClass === Const.OBJECT_CLASS_CIRCLE) && Type.isPoint(p[1])) {
            // Circle/Point
            el = new JXG.Circle(board, 'pointCircle', p[1], p[0], attr);
        } else if ((p[1].elementClass === Const.OBJECT_CLASS_CIRCLE) && Type.isPoint(p[0])) {
            // Point/Circle
            el = new JXG.Circle(board, 'pointCircle', p[0], p[1], attr);
        } else if ((p[0].elementClass === Const.OBJECT_CLASS_LINE) && Type.isPoint(p[1])) {
            // Line/Point
            el = new JXG.Circle(board, 'pointLine', p[1], p[0], attr);
        } else if ((p[1].elementClass === Const.OBJECT_CLASS_LINE) && Type.isPoint(p[0])) {
            // Point/Line
            el = new JXG.Circle(board, 'pointLine', p[0], p[1], attr);
        } else if (parents.length === 3 && Type.isPoint(p[0]) && Type.isPoint(p[1]) && Type.isPoint(p[2])) {
            // Circle through three points
            // Check if circumcircle element is available
            if (JXG.elements.circumcircle) {
                el = JXG.elements.circumcircle(board, p, attr);
            } else {
                throw new Error('JSXGraph: Can\'t create circle with three points. Please include the circumcircle element (element/composition).');
            }

        } else {
            throw new Error("JSXGraph: Can't create circle with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [point,point], [point,number], [point,function], [point,circle], [point,point,point], [circle,transformation]");
        }

        el.isDraggable = isDraggable;
        el.setParents(p);
        el.elType = 'circle';
        for (i = 0; i < p.length; i++) {
            if (Type.isPoint(p[i])) {
                el.inherits.push(p[i]);
            }
        }
        return el;
    };

    JXG.registerElement('circle', JXG.createCircle);

    return {
        Circle: JXG.Circle,
        createCircle: JXG.createCircle
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG:true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 base/coords
 math/statistics
 utils/type
 base/element
  elements:
   segment
   transform
 */

define('base/polygon',[
    'jxg', 'base/constants', 'base/coords', 'math/statistics', 'math/geometry', 'utils/type', 'base/element'
], function (JXG, Const, Coords, Statistics, Geometry, Type, GeometryElement) {

    "use strict";

    /**
     * Creates a new instance of JXG.Polygon.
     * @class Polygon stores all style and functional properties that are required
     * to draw and to interactact with a polygon.
     * @param {JXG.Board} board Reference to the board the polygon is to be drawn on.
     * @param {Array} vertices Unique identifiers for the points defining the polygon.
     * Last point must be first point. Otherwise, the first point will be added at the list.
     * @param {Object} attributes An object which contains properties as given in {@link JXG.Options.elements}
     * and {@link JXG.Options.polygon}.
     * @constructor
     * @extends JXG.GeometryElement
     */

    JXG.Polygon = function (board, vertices, attributes) {
        this.constructor(board, attributes, Const.OBJECT_TYPE_POLYGON, Const.OBJECT_CLASS_AREA);

        var i, l, len, j, p,
            attr_line = Type.copyAttributes(attributes, board.options, 'polygon', 'borders');

        this.withLines = attributes.withlines;
        this.attr_line = attr_line;

        /**
         * References to the points defining the polygon. The last vertex is the same as the first vertex.
         * @type Array
         */
        this.vertices = [];
        for (i = 0; i < vertices.length; i++) {
            this.vertices[i] = this.board.select(vertices[i]);
        }

        // Close the polygon
        if (this.vertices.length > 0 && this.vertices[this.vertices.length - 1].id !== this.vertices[0].id) {
            this.vertices.push(this.vertices[0]);
        }

        /**
         * References to the border lines of the polygon.
         * @type Array
         */
        this.borders = [];

        if (this.withLines) {
            len = this.vertices.length - 1;
            for (j = 0; j < len; j++) {
                // This sets the "correct" labels for the first triangle of a construction.
                i = (j + 1) % len;
                attr_line.id = attr_line.ids && attr_line.ids[i];
                attr_line.name = attr_line.names && attr_line.names[i];
                attr_line.strokecolor = (Type.isArray(attr_line.colors) && attr_line.colors[i % attr_line.colors.length]) ||
                                            attr_line.strokecolor;
                attr_line.visible = Type.exists(attributes.borders.visible) ? attributes.borders.visible : attributes.visible;

                if (attr_line.strokecolor === false) {
                    attr_line.strokecolor = 'none';
                }

                l = board.create('segment', [this.vertices[i], this.vertices[i + 1]], attr_line);
                l.dump = false;
                this.borders[i] = l;
                l.parentPolygon = this;
            }
        }

        this.inherits.push(this.vertices, this.borders);

        // Register polygon at board
        // This needs to be done BEFORE the points get this polygon added in their descendants list
        this.id = this.board.setId(this, 'Py');

        // Add dependencies: either
        // - add polygon as child to an existing point
        // or
        // - add  points (supplied as coordinate arrays by the user and created by Type.providePoints) as children to the polygon
        for (i = 0; i < this.vertices.length - 1; i++) {
            p = this.board.select(this.vertices[i]);
            if (Type.exists(p._is_new)) {
                this.addChild(p);
                delete p._is_new;
            } else {
                p.addChild(this);
            }
        }

        this.board.renderer.drawPolygon(this);
        this.board.finalizeAdding(this);

        this.createGradient();
        this.elType = 'polygon';

        // create label
        this.createLabel();

        this.methodMap = JXG.deepCopy(this.methodMap, {
            borders: 'borders',
            vertices: 'vertices',
            A: 'Area',
            Area: 'Area',
            Perimeter: 'Perimeter',
            L: 'Perimeter',
            Length: 'Perimeter',
            boundingBox: 'boundingBox',
            bounds: 'bounds',
            addPoints: 'addPoints',
            insertPoints: 'insertPoints',
            removePoints: 'removePoints'
        });
    };

    JXG.Polygon.prototype = new GeometryElement();

    JXG.extend(JXG.Polygon.prototype, /** @lends JXG.Polygon.prototype */ {

        /**
         * Decides if a point (x,y) is inside of the polygon.
         * Implements W. Randolf Franklin's pnpoly method.
         *
         * See <a href="https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html">https://wrf.ecse.rpi.edu/Research/Short_Notes/pnpoly.html</a>.
         *
         * @param {Number} x_in x-coordinate (screen or user coordinates)
         * @param {Number} y_in y-coordinate (screen or user coordinates)
         * @param {Number} coord_type (Optional) the type of coordinates used here.
         *   Possible values are <b>JXG.COORDS_BY_USER</b> and <b>JXG.COORDS_BY_SCREEN</b>.
         *   Default value is JXG.COORDS_BY_SCREEN
         *
         * @returns {Boolean} if (x,y) is inside of the polygon.
         * @example
         * var pol = board.create('polygon', [[-1,2], [2,2], [-1,4]]);
         * var p = board.create('point', [4, 3]);
         * var txt = board.create('text', [-1, 0.5, function() {
         *   return 'Point A is inside of the polygon = ' +
         *     pol.pnpoly(p.X(), p.Y(), JXG.COORDS_BY_USER);
         * }]);
         *
         * </pre><div id="JXG7f96aec7-4e3d-4ffc-a3f5-d3f967b6691c" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG7f96aec7-4e3d-4ffc-a3f5-d3f967b6691c',
         *             {boundingbox: [-2, 5, 5,-2], axis: true, showcopyright: false, shownavigation: false});
         *     var pol = board.create('polygon', [[-1,2], [2,2], [-1,4]]);
         *     var p = board.create('point', [4, 3]);
         *     var txt = board.create('text', [-1, 0.5, function() {
         *     		return 'Point A is inside of the polygon = ' + pol.pnpoly(p.X(), p.Y(), JXG.COORDS_BY_USER);
         *     }]);
         *
         *     })();
         *
         * </script><pre>
         *
         */
        pnpoly: function(x_in, y_in, coord_type) {
            var i, j, len,
                x, y, crds,
                v = this.vertices,
                isIn = false;

            if (coord_type === Const.COORDS_BY_USER) {
                crds = new Coords(Const.COORDS_BY_USER, [x_in, y_in], this.board);
                x = crds.scrCoords[1];
                y = crds.scrCoords[2];
            } else {
                x = x_in;
                y = y_in;
            }

            len = this.vertices.length;
            for (i = 0, j = len - 2; i < len - 1; j = i++) {
                if (((v[i].coords.scrCoords[2] > y) !== (v[j].coords.scrCoords[2] > y)) &&
                    (x < (v[j].coords.scrCoords[1] - v[i].coords.scrCoords[1]) *
                    (y - v[i].coords.scrCoords[2]) / (v[j].coords.scrCoords[2] - v[i].coords.scrCoords[2]) + v[i].coords.scrCoords[1])
                   ) {
                    isIn = !isIn;
                }
            }

            return isIn;
        },

        /**
         * Checks whether (x,y) is near the polygon.
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @returns {Boolean} Returns true, if (x,y) is inside or at the boundary the polygon, otherwise false.
         */
        hasPoint: function (x, y) {
            var i, len;

            if (Type.evaluate(this.visProp.hasinnerpoints)) {
                // All points of the polygon trigger hasPoint: inner and boundary points
                if (this.pnpoly(x, y)) {
                    return true;
                }
            }

            // Only boundary points trigger hasPoint
            // We additionally test the boundary also in case hasInnerPoints.
            // Since even if the above test has failed, the strokewidth may be large and (x, y) may
            // be inside of hasPoint() of a vertices.
            len = this.borders.length;
            for (i = 0; i < len; i++) {
                if (this.borders[i].hasPoint(x, y)) {
                    return true;
                }
            }

            return false;
        },

        /**
         * Uses the boards renderer to update the polygon.
         */
        updateRenderer: function () {
            var i, len; // wasReal,


            if (!this.needsUpdate) {
                return this;
            }

            if (this.visPropCalc.visible) {
                // wasReal = this.isReal;

                len = this.vertices.length;
                this.isReal = true;
                for (i = 0; i < len; ++i) {
                    if (!this.vertices[i].isReal) {
                        this.isReal = false;
                        break;
                    }
                }

                if (//wasReal &&
                    !this.isReal) {
                    this.updateVisibility(false);
                }
            }

            if (this.visPropCalc.visible) {
                this.board.renderer.updatePolygon(this);
            }

            /* Update the label if visible. */
            if (this.hasLabel && this.visPropCalc.visible && this.label &&
                this.label.visPropCalc.visible && this.isReal) {

                this.label.update();
                this.board.renderer.updateText(this.label);
            }

            // Update rendNode display
            this.setDisplayRendNode();
            // if (this.visPropCalc.visible !== this.visPropOld.visible) {
            //     this.board.renderer.display(this, this.visPropCalc.visible);
            //     this.visPropOld.visible = this.visPropCalc.visible;
            //
            //     if (this.hasLabel) {
            //         this.board.renderer.display(this.label, this.label.visPropCalc.visible);
            //     }
            // }

            this.needsUpdate = false;
            return this;
        },

        /**
         * return TextAnchor
         */
        getTextAnchor: function () {
            var a, b, x, y, i;

            if (this.vertices.length === 0) {
                return new Coords(Const.COORDS_BY_USER, [1, 0, 0], this.board);
            }

            a = this.vertices[0].X();
            b = this.vertices[0].Y();
            x = a;
            y = b;
            for (i = 0; i < this.vertices.length; i++) {
                if (this.vertices[i].X() < a) {
                    a = this.vertices[i].X();
                }

                if (this.vertices[i].X() > x) {
                    x = this.vertices[i].X();
                }

                if (this.vertices[i].Y() > b) {
                    b = this.vertices[i].Y();
                }

                if (this.vertices[i].Y() < y) {
                    y = this.vertices[i].Y();
                }
            }

            return new Coords(Const.COORDS_BY_USER, [(a + x) * 0.5, (b + y) * 0.5], this.board);
        },

        getLabelAnchor: JXG.shortcut(JXG.Polygon.prototype, 'getTextAnchor'),

        // documented in geometry element
        cloneToBackground: function () {
            var copy = {}, er;

            copy.id = this.id + 'T' + this.numTraces;
            this.numTraces++;
            copy.vertices = this.vertices;
            copy.visProp = Type.deepCopy(this.visProp, this.visProp.traceattributes, true);
            copy.visProp.layer = this.board.options.layer.trace;
            copy.board = this.board;
            Type.clearVisPropOld(copy);

            copy.visPropCalc = {
                visible: Type.evaluate(copy.visProp.visible)
            };

            er = this.board.renderer.enhancedRendering;
            this.board.renderer.enhancedRendering = true;
            this.board.renderer.drawPolygon(copy);
            this.board.renderer.enhancedRendering = er;
            this.traces[copy.id] = copy.rendNode;

            return this;
        },

        /**
         * Hide the polygon including its border lines. It will still exist but not visible on the board.
         * @param {Boolean} [borderless=false] If set to true, the polygon is treated as a polygon without
         * borders, i.e. the borders will not be hidden.
         */
        hideElement: function (borderless) {
            var i;

            JXG.deprecated('Element.hideElement()', 'Element.setDisplayRendNode()');

            this.visPropCalc.visible = false;
            this.board.renderer.display(this, false);

            if (!borderless) {
                for (i = 0; i < this.borders.length; i++) {
                    this.borders[i].hideElement();
                }
            }

            if (this.hasLabel && Type.exists(this.label)) {
                this.label.hiddenByParent = true;
                if (this.label.visPropCalc.visible) {
                    this.label.hideElement();
                }
            }
        },

        /**
         * Make the element visible.
         * @param {Boolean} [borderless=false] If set to true, the polygon is treated as a polygon without
         * borders, i.e. the borders will not be shown.
         */
        showElement: function (borderless) {
            var i;

            JXG.deprecated('Element.showElement()', 'Element.setDisplayRendNode()');

            this.visPropCalc.visible = true;
            this.board.renderer.display(this, true);

            if (!borderless) {
                for (i = 0; i < this.borders.length; i++) {
                    this.borders[i].showElement().updateRenderer();
                }
            }

            if (Type.exists(this.label) && this.hasLabel && this.label.hiddenByParent) {
                this.label.hiddenByParent = false;
                if (!this.label.visPropCalc.visible) {
                    this.label.showElement().updateRenderer();
                }
            }
            return this;
        },

        /**
         * Area of (not self-intersecting) polygon
         * @returns {Number} Area of (not self-intersecting) polygon
         */
        Area: function () {
            return Math.abs(Geometry.signedPolygon(this.vertices, true));
        },

        /**
         * Perimeter of polygon.
         * @returns {Number} Perimeter of polygon in user units.
         *
         * @example
         * var p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 3.0]];
         *
         * var pol = board.create('polygon', p, {hasInnerPoints: true});
         * var t = board.create('text', [5, 5, function() { return pol.Perimeter(); }]);
         * </pre><div class="jxgbox" id="JXGb10b734d-89fc-4b9d-b4a7-e3f0c1c6bf77" style="width: 400px; height: 400px;"></div>
         * <script type="text/javascript">
         *  (function () {
         *   var board = JXG.JSXGraph.initBoard('JXGb10b734d-89fc-4b9d-b4a7-e3f0c1c6bf77', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}),
         *       p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 4.0]],
         *       cc1 = board.create('polygon', p, {hasInnerPoints: true}),
         *       t = board.create('text', [5, 5, function() { return cc1.Perimeter(); }]);
         *  })();
         * </script><pre>
         *
         */
        Perimeter: function() {
            var i,
                len = this.vertices.length,
                val = 0.0;

            for (i = 1; i < len; ++i) {
                val += this.vertices[i].Dist(this.vertices[i - 1]);
            }

            return val;
        },

        /**
         * Bounding box of a polygon. The bounding box is an array of four numbers: the first two numbers
         * determine the upper left corner, the last two number determine the lower right corner of the bounding box.
         *
         * The width and height of a polygon can then determined like this:
         * @example
         * var box = polygon.boundingBox();
         * var width = box[2] - box[0];
         * var height = box[1] - box[3];
         *
         * @returns {Array} Array containing four numbers: [minX, maxY, maxX, minY]
         */
        boundingBox: function () {
            var box = [0, 0, 0, 0], i, v,
                le = this.vertices.length - 1;

            if (le === 0) {
                return box;
            }
            box[0] = this.vertices[0].X();
            box[2] = box[0];
            box[1] = this.vertices[0].Y();
            box[3] = box[1];

            for (i = 1; i < le; ++i) {
                v = this.vertices[i].X();
                if (v < box[0]) {
                    box[0] = v;
                } else if (v > box[2]) {
                    box[2] = v;
                }

                v = this.vertices[i].Y();
                if (v > box[1]) {
                    box[1] = v;
                } else if (v < box[3]) {
                    box[3] = v;
                }
            }

            return box;
        },

        // Already documented in GeometryElement
        bounds: function () {
            return this.boundingBox();
        },

        /**
         * This method removes the SVG or VML nodes of the lines and the filled area from the renderer, to remove
         * the object completely you should use {@link JXG.Board#removeObject}.
         *
         * @private
         */
        remove: function () {
            var i;

            for (i = 0; i < this.borders.length; i++) {
                this.board.removeObject(this.borders[i]);
            }

            GeometryElement.prototype.remove.call(this);
        },

        /**
         * Finds the index to a given point reference.
         * @param {JXG.Point} p Reference to an element of type {@link JXG.Point}
         * @returns {Number} Index of the point or -1.
         */
        findPoint: function (p) {
            var i;

            if (!Type.isPoint(p)) {
                return -1;
            }

            for (i = 0; i < this.vertices.length; i++) {
                if (this.vertices[i].id === p.id) {
                    return i;
                }
            }

            return -1;
        },

        /**
         * Add more points to the polygon. The new points will be inserted at the end.
         * @param {JXG.Point} p Arbitrary number of points
         * @returns {JXG.Polygon} Reference to the polygon
         */
        addPoints: function (p) {
            var args = Array.prototype.slice.call(arguments);

            return this.insertPoints.apply(this, [this.vertices.length - 2].concat(args));
        },

        /**
         * Adds more points to the vertex list of the polygon, starting with index <tt><i</tt>
         * @param {Number} idx The position where the new vertices are inserted, starting with 0.
         * @param {JXG.Point} p Arbitrary number of points to insert.
         * @returns {JXG.Polygon} Reference to the polygon object
         */
        insertPoints: function (idx, p) {
            var i, npoints = [], tmp;

            if (arguments.length === 0) {
                return this;
            }


            if (idx < 0 || idx > this.vertices.length - 2) {
                return this;
            }

            for (i = 1; i < arguments.length; i++) {
                if (Type.isPoint(arguments[i])) {
                    npoints.push(arguments[i]);
                }
            }

            tmp = this.vertices.slice(0, idx + 1).concat(npoints);
            this.vertices = tmp.concat(this.vertices.slice(idx + 1));

            if (this.withLines) {
                tmp = this.borders.slice(0, idx);
                this.board.removeObject(this.borders[idx]);

                for (i = 0; i < npoints.length; i++) {
                    tmp.push(this.board.create('segment', [this.vertices[idx + i], this.vertices[idx + i + 1]], this.attr_line));
                }

                tmp.push(this.board.create('segment', [this.vertices[idx + npoints.length], this.vertices[idx + npoints.length + 1]], this.attr_line));
                this.borders = tmp.concat(this.borders.slice(idx + 1));
            }

            this.board.update();

            return this;
        },

        /**
         * Removes given set of vertices from the polygon
         * @param {JXG.Point} p Arbitrary number of vertices as {@link JXG.Point} elements or index numbers
         * @returns {JXG.Polygon} Reference to the polygon
         */
        removePoints: function (p) {
            var i, j, idx, nvertices = [], nborders = [],
                nidx = [], partition = [];

            // partition:
            // in order to keep the borders which could be recycled, we have to partition
            // the set of removed points. I.e. if the points 1, 2, 5, 6, 7, 10 are removed,
            // the partitions are
            //       1-2, 5-7, 10-10
            // this gives us the borders, that can be removed and the borders we have to create.


            // remove the last vertex which is identical to the first
            this.vertices = this.vertices.slice(0, this.vertices.length - 1);

            // collect all valid parameters as indices in nidx
            for (i = 0; i < arguments.length; i++) {
                idx = arguments[i];
                if (Type.isPoint(idx)) {
                    idx = this.findPoint(idx);
                }

                if (Type.isNumber(idx) && idx > -1 && idx < this.vertices.length && Type.indexOf(nidx, idx) === -1) {
                    nidx.push(idx);
                }
            }

            // remove the polygon from each removed point's children
            for (i = 0; i < nidx.length; i++) {
                this.vertices[nidx[i]].removeChild(this);
            }

            // sort the elements to be eliminated
            nidx = nidx.sort();
            nvertices = this.vertices.slice();
            nborders = this.borders.slice();

            // initialize the partition
            if (this.withLines) {
                partition.push([nidx[nidx.length - 1]]);
            }

            // run through all existing vertices and copy all remaining ones to nvertices
            // compute the partition
            for (i = nidx.length - 1; i > -1; i--) {
                nvertices[nidx[i]] = -1;

                if (this.withLines && (nidx[i] - 1 > nidx[i - 1])) {
                    partition[partition.length - 1][1] = nidx[i];
                    partition.push([nidx[i - 1]]);
                }
            }

            // finalize the partition computation
            if (this.withLines) {
                partition[partition.length - 1][1] = nidx[0];
            }

            // update vertices
            this.vertices = [];
            for (i = 0; i < nvertices.length; i++) {
                if (Type.isPoint(nvertices[i])) {
                    this.vertices.push(nvertices[i]);
                }
            }
            if (this.vertices[this.vertices.length - 1].id !== this.vertices[0].id) {
                this.vertices.push(this.vertices[0]);
            }

            // delete obsolete and create missing borders
            if (this.withLines) {
                for (i = 0; i < partition.length; i++) {
                    for (j = partition[i][1] - 1; j < partition[i][0] + 1; j++) {
                        // special cases
                        if (j < 0) {
                            // first vertex is removed, so the last border has to be removed, too
                            j = 0;
                            this.board.removeObject(this.borders[nborders.length - 1]);
                            nborders[nborders.length - 1] = -1;
                        } else if (j > nborders.length - 1) {
                            j = nborders.length - 1;
                        }

                        this.board.removeObject(this.borders[j]);
                        nborders[j] = -1;
                    }

                    // only create the new segment if it's not the closing border. the closing border is getting a special treatment at the end
                    // the if clause is newer than the min/max calls inside createSegment; i'm sure this makes the min/max calls obsolete, but
                    // just to be sure...
                    if (partition[i][1] !== 0 && partition[i][0] !== nvertices.length - 1) {
                        nborders[partition[i][0] - 1] = this.board.create('segment', [nvertices[Math.max(partition[i][1] - 1, 0)], nvertices[Math.min(partition[i][0] + 1, this.vertices.length - 1)]], this.attr_line);
                    }
                }

                this.borders = [];
                for (i = 0; i < nborders.length; i++) {
                    if (nborders[i] !== -1) {
                        this.borders.push(nborders[i]);
                    }
                }

                // if the first and/or the last vertex is removed, the closing border is created at the end.
                if (partition[0][1] === this.vertices.length - 1 || partition[partition.length - 1][1] === 0) {
                    this.borders.push(this.board.create('segment', [this.vertices[0], this.vertices[this.vertices.length - 2]], this.attr_line));
                }
            }

            this.board.update();

            return this;
        },

        // documented in element.js
        getParents: function () {
            this.setParents(this.vertices);
            return this.parents;
        },

        getAttributes: function () {
            var attr = GeometryElement.prototype.getAttributes.call(this), i;

            if (this.withLines) {
                attr.lines = attr.lines || {};
                attr.lines.ids = [];
                attr.lines.colors = [];

                for (i = 0; i < this.borders.length; i++) {
                    attr.lines.ids.push(this.borders[i].id);
                    attr.lines.colors.push(this.borders[i].visProp.strokecolor);
                }
            }

            return attr;
        },

        snapToGrid: function () {
            var i, force;

            if (Type.evaluate(this.visProp.snaptogrid)) {
                force = true;
            } else {
                force = false;
            }

            for (i = 0; i < this.vertices.length; i++) {
                this.vertices[i].handleSnapToGrid(force, true);
            }

        },

        /**
         * Moves the polygon by the difference of two coordinates.
         * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
         * @param {Array} coords coordinates in screen/user units
         * @param {Array} oldcoords previous coordinates in screen/user units
         * @returns {JXG.Polygon} this element
         */
        setPositionDirectly: function (method, coords, oldcoords) {
            var dc, t, i, len,
                c = new Coords(method, coords, this.board),
                oldc = new Coords(method, oldcoords, this.board);

            len = this.vertices.length - 1;
            for (i = 0; i < len; i++) {
                if (!this.vertices[i].draggable()) {
                    return this;
                }
            }

            dc = Statistics.subtract(c.usrCoords, oldc.usrCoords);
            t = this.board.create('transform', dc.slice(1), {type: 'translate'});
            t.applyOnce(this.vertices.slice(0, -1));

            return this;
        },

        /**
         * Algorithm by Sutherland and Hodgman to compute the intersection of two convex polygons.
         * The polygon itself is the clipping polygon, it expects as parameter a polygon to be clipped.
         * See <a href="https://en.wikipedia.org/wiki/Sutherland%E2%80%93Hodgman_algorithm">wikipedia entry</a>.
         * Called by {@link JXG.Polygon#intersect}.
         *
         * @private
         *
         * @param {JXG.Polygon} polygon Polygon which will be clipped.
         *
         * @returns {Array} of (normalized homogeneous user) coordinates (i.e. [z, x, y], where z==1 in most cases,
         *   representing the vertices of the intersection polygon.
         *
         */
        sutherlandHodgman: function(polygon) {
            // First the two polygons are sorted counter clockwise
            var clip = JXG.Math.Geometry.sortVertices(this.vertices),   // "this" is the clipping polygon
                subject = JXG.Math.Geometry.sortVertices(polygon.vertices), // "polygon" is the subject polygon

                lenClip = clip.length - 1,
                lenSubject = subject.length - 1,
                lenIn,

                outputList = [],
                inputList, i, j, S, E, cross,

                // Determines if the point c3 is right of the line through c1 and c2.
                // Since the polygons are sorted counter clockwise, "right of" and therefore >= is needed here
                isInside = function(c1, c2, c3) {
                    return ((c2[1] - c1[1]) * (c3[2] - c1[2]) - (c2[2] - c1[2]) * (c3[1] - c1[1])) >= 0;
                };

            for (i = 0; i < lenSubject; i++) {
                outputList.push(subject[i]);
            }

            for (i = 0; i < lenClip; i++) {
                inputList = outputList.slice(0);
                lenIn = inputList.length;
                outputList = [];

                S = inputList[lenIn - 1];

                for (j = 0; j < lenIn; j++) {
                    E = inputList[j];
                    if (isInside(clip[i], clip[i + 1], E)) {
                        if (!isInside(clip[i], clip[i + 1], S)) {
                            cross = JXG.Math.Geometry.meetSegmentSegment(S, E, clip[i], clip[i + 1]);
                            cross[0][1] /= cross[0][0];
                            cross[0][2] /= cross[0][0];
                            cross[0][0] = 1;
                            outputList.push(cross[0]);
                        }
                        outputList.push(E);
                    } else if (isInside(clip[i], clip[i + 1], S)) {
                        cross = JXG.Math.Geometry.meetSegmentSegment(S, E, clip[i], clip[i + 1]);
                        cross[0][1] /= cross[0][0];
                        cross[0][2] /= cross[0][0];
                        cross[0][0] = 1;
                        outputList.push(cross[0]);
                    }
                    S = E;
                }
            }

            return outputList;
        },

        /**
         * Generic method for the intersection of this polygon with another polygon.
         * The parent object is the clipping polygon, it expects as parameter a polygon to be clipped.
         * Both polygons have to be convex.
         * Calls the algorithm by Sutherland, Hodgman, {@link JXG.Polygon#sutherlandHodgman}.
         * <p>
         * An alternative is to use the methods from {@link JXG.Math.Clip}, where the algorithm by Greiner and Hormann
         * is used.
         *
         * @param {JXG.Polygon} polygon Polygon which will be clipped.
         *
         * @returns {Array} of (normalized homogeneous user) coordinates (i.e. [z, x, y], where z==1 in most cases,
         *   representing the vertices of the intersection polygon.
         *
         * @example
         *  // Static intersection of two polygons pol1 and pol2
         *  var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], {
         *                name:'pol1', withLabel: true,
         *                fillColor: 'yellow'
         *             });
         *  var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], {
         *                name:'pol2', withLabel: true
         *             });
         *
         *  // Static version:
         *  // the intersection polygon does not adapt to changes of pol1 or pol2.
         *  var pol3 = board.create('polygon', pol1.intersect(pol2), {fillColor: 'blue'});
         * </pre><div class="jxgbox" id="JXGd1fe5ea9-309f-494a-af07-ee3d033acb7c" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *   (function() {
         *       var board = JXG.JSXGraph.initBoard('JXGd1fe5ea9-309f-494a-af07-ee3d033acb7c', {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *       // Intersect two polygons pol1 and pol2
         *       var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], {
         *                name:'pol1', withLabel: true,
         *                fillColor: 'yellow'
         *             });
         *       var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], {
         *                name:'pol2', withLabel: true
         *             });
         *
         *       // Static version: the intersection polygon does not adapt to changes of pol1 or pol2.
         *       var pol3 = board.create('polygon', pol1.intersect(pol2), {fillColor: 'blue'});
         *   })();
         * </script><pre>
         *
         * @example
         *  // Dynamic intersection of two polygons pol1 and pol2
         *  var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], {
         *                name:'pol1', withLabel: true,
         *                fillColor: 'yellow'
         *             });
         *  var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], {
         *                name:'pol2', withLabel: true
         *             });
         *
         *  // Dynamic version:
         *  // the intersection polygon does adapt to changes of pol1 or pol2.
         *  // For this a curve element is used.
         *  var curve = board.create('curve', [[],[]], {fillColor: 'blue', fillOpacity: 0.4});
         *  curve.updateDataArray = function() {
         *      var mat = JXG.Math.transpose(pol1.intersect(pol2));
         *
         *      if (mat.length == 3) {
         *          this.dataX = mat[1];
         *          this.dataY = mat[2];
         *      } else {
         *          this.dataX = [];
         *          this.dataY = [];
         *      }
         *  };
         *  board.update();
         * </pre><div class="jxgbox" id="JXGf870d516-ca1a-4140-8fe3-5d64fb42e5f2" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *   (function() {
         *       var board = JXG.JSXGraph.initBoard('JXGf870d516-ca1a-4140-8fe3-5d64fb42e5f2', {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *       // Intersect two polygons pol1 and pol2
         *       var pol1 = board.create('polygon', [[-2, 3], [-4, -3], [2, 0], [4, 4]], {
         *                name:'pol1', withLabel: true,
         *                fillColor: 'yellow'
         *             });
         *       var pol2 = board.create('polygon', [[-2, -3], [-4, 1], [0, 4], [5, 1]], {
         *                name:'pol2', withLabel: true
         *             });
         *
         *  // Dynamic version:
         *  // the intersection polygon does  adapt to changes of pol1 or pol2.
         *  // For this a curve element is used.
         *    var curve = board.create('curve', [[],[]], {fillColor: 'blue', fillOpacity: 0.4});
         *    curve.updateDataArray = function() {
         *        var mat = JXG.Math.transpose(pol1.intersect(pol2));
         *
         *        if (mat.length == 3) {
         *            this.dataX = mat[1];
         *            this.dataY = mat[2];
         *        } else {
         *            this.dataX = [];
         *            this.dataY = [];
         *        }
         *    };
         *    board.update();
         *   })();
         * </script><pre>
         *
         */
        intersect: function(polygon) {
            return this.sutherlandHodgman(polygon);
        }


    });


    /**
     * @class A polygon is an area enclosed by a set of border lines which are determined by
     * <ul>
     *    <li> a list of points or
     *    <li> a list of coordinate arrays or
     *    <li> a function returning a list of coordinate arrays.
     * </ul>
     * Each two consecutive points of the list define a line.
     * @pseudo
     * @constructor
     * @name Polygon
     * @type Polygon
     * @augments JXG.Polygon
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {Array} vertices The polygon's vertices. If the first and the last vertex don't match the first one will be
     * added to the array by the creator. Here, two points match if they have the same 'id' attribute.
     *
     * Additionally, a polygon can be created by providing a polygon and a transformation (or an array of transformations).
     * The result is a polygon which is the transformation of the supplied polygon.
     *
     * @example
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     * var p3 = board.create('point', [4.0, 6.0]);
     * var p4 = board.create('point', [1.0, 4.0]);
     *
     * var pol = board.create('polygon', [p1, p2, p3, p4]);
     * </pre><div class="jxgbox" id="JXG682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *  (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}),
     *       p1 = board.create('point', [0.0, 2.0]),
     *       p2 = board.create('point', [2.0, 1.0]),
     *       p3 = board.create('point', [4.0, 6.0]),
     *       p4 = board.create('point', [1.0, 4.0]),
     *       cc1 = board.create('polygon', [p1, p2, p3, p4]);
     *  })();
     * </script><pre>
     *
     * @example
     * var p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 3.0]];
     *
     * var pol = board.create('polygon', p, {hasInnerPoints: true});
     * </pre><div class="jxgbox" id="JXG9f9a5946-112a-4768-99ca-f30792bcdefb" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *  (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG9f9a5946-112a-4768-99ca-f30792bcdefb', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}),
     *       p = [[0.0, 2.0], [2.0, 1.0], [4.0, 6.0], [1.0, 4.0]],
     *       cc1 = board.create('polygon', p, {hasInnerPoints: true});
     *  })();
     * </script><pre>
     *
     * @example
     *   var f1 = function() { return [0.0, 2.0]; },
     *       f2 = function() { return [2.0, 1.0]; },
     *       f3 = function() { return [4.0, 6.0]; },
     *       f4 = function() { return [1.0, 4.0]; },
     *       cc1 = board.create('polygon', [f1, f2, f3, f4]);
     *       board.update();
     *
     * </pre><div class="jxgbox" id="JXGceb09915-b783-44db-adff-7877ae3534c8" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *  (function () {
     *   var board = JXG.JSXGraph.initBoard('JXGceb09915-b783-44db-adff-7877ae3534c8', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}),
     *       f1 = function() { return [0.0, 2.0]; },
     *       f2 = function() { return [2.0, 1.0]; },
     *       f3 = function() { return [4.0, 6.0]; },
     *       f4 = function() { return [1.0, 4.0]; },
     *       cc1 = board.create('polygon', [f1, f2, f3, f4]);
     *       board.update();
     *  })();
     * </script><pre>
     *
     * @example
     * var t = board.create('transform', [2, 1.5], {type: 'scale'});
     * var a = board.create('point', [-3,-2], {name: 'a'});
     * var b = board.create('point', [-1,-4], {name: 'b'});
     * var c = board.create('point', [-2,-0.5], {name: 'c'});
     * var pol1 = board.create('polygon', [a,b,c], {vertices: {withLabel: false}});
     * var pol2 = board.create('polygon', [pol1, t], {vertices: {withLabel: true}});
     *
     * </pre><div id="JXG6530a69c-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG6530a69c-6339-11e8-9fb9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
     *     var a = board.create('point', [-3,-2], {name: 'a'});
     *     var b = board.create('point', [-1,-4], {name: 'b'});
     *     var c = board.create('point', [-2,-0.5], {name: 'c'});
     *     var pol1 = board.create('polygon', [a,b,c], {vertices: {withLabel: false}});
     *     var pol2 = board.create('polygon', [pol1, t], {vertices: {withLabel: true}});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createPolygon = function (board, parents, attributes) {
        var el, i, le, obj,
            points = [],
            attr, attr_points,
            is_transform = false;

        attr = Type.copyAttributes(attributes, board.options, 'polygon');
        obj = board.select(parents[0]);
        if (obj === null) {
            // This is necessary if the original polygon is defined in another board.
            obj = parents[0];
        }
        if (Type.isObject(obj) && obj.type === Const.OBJECT_TYPE_POLYGON &&
            Type.isTransformationOrArray(parents[1])) {

            is_transform = true;
            le = obj.vertices.length - 1;
            attr_points = Type.copyAttributes(attributes, board.options, 'polygon', 'vertices');
            for (i = 0; i < le; i++) {
                if (attr_points.withlabel) {
                    attr_points.name = (obj.vertices[i].name === '') ? '' : (obj.vertices[i].name + "'");
                }
                points.push(board.create('point', [obj.vertices[i], parents[1]], attr_points));
            }
        } else {
            points = Type.providePoints(board, parents, attributes, 'polygon', ['vertices']);
            if (points === false) {
                throw new Error("JSXGraph: Can't create polygon / polygonalchain with parent types other than 'point' and 'coordinate arrays' or a function returning an array of coordinates. Alternatively, a polygon and a transformation can be supplied");
            }
        }

        attr = Type.copyAttributes(attributes, board.options, 'polygon');
        el = new JXG.Polygon(board, points, attr);
        el.isDraggable = true;

        // Put the points to their position
        if (is_transform) {
            el.prepareUpdate().update().updateVisibility().updateRenderer();
            le = obj.vertices.length - 1;
            for (i = 0; i < le; i++) {
                points[i].prepareUpdate().update().updateVisibility().updateRenderer();
            }
        }

        return el;
    };

    /**
     * @class Constructs a regular polygon. It needs two points which define the base line and the number of vertices.
     * @pseudo
     * @description Constructs a regular polygon. It needs two points which define the base line and the number of vertices, or a set of points.
     * @constructor
     * @name RegularPolygon
     * @type Polygon
     * @augments Polygon
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_Number} p1,p2,n The constructed regular polygon has n vertices and the base line defined by p1 and p2.
     * @example
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     *
     * var pol = board.create('regularpolygon', [p1, p2, 5]);
     * </pre><div class="jxgbox" id="JXG682069e9-9e2c-4f63-9b73-e26f8a2b2bb1" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *  (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG682069e9-9e2c-4f63-9b73-e26f8a2b2bb1', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}),
     *       p1 = board.create('point', [0.0, 2.0]),
     *       p2 = board.create('point', [2.0, 1.0]),
     *       cc1 = board.create('regularpolygon', [p1, p2, 5]);
     *  })();
     * </script><pre>
     * @example
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [4.0,4.0]);
     * var p3 = board.create('point', [2.0,0.0]);
     *
     * var pol = board.create('regularpolygon', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXG096a78b3-bd50-4bac-b958-3be5e7df17ed" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG096a78b3-bd50-4bac-b958-3be5e7df17ed', {boundingbox: [-1, 9, 9, -1], axis: false, showcopyright: false, shownavigation: false}),
     *       p1 = board.create('point', [0.0, 2.0]),
     *       p2 = board.create('point', [4.0, 4.0]),
     *       p3 = board.create('point', [2.0,0.0]),
     *       cc1 = board.create('regularpolygon', [p1, p2, p3]);
     * })();
     * </script><pre>
     *
     * @example
     *         // Line of reflection
     *         var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
     *         var reflect = board.create('transform', [li], {type: 'reflect'});
     *         var pol1 = board.create('polygon', [[-3,-2], [-1,-4], [-2,-0.5]]);
     *         var pol2 = board.create('polygon', [pol1, reflect]);
     *
     * </pre><div id="JXG58fc3078-d8d1-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG58fc3078-d8d1-11e7-93b3-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *             var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
     *             var reflect = board.create('transform', [li], {type: 'reflect'});
     *             var pol1 = board.create('polygon', [[-3,-2], [-1,-4], [-2,-0.5]]);
     *             var pol2 = board.create('polygon', [pol1, reflect]);
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createRegularPolygon = function (board, parents, attributes) {
        var el, i, n,
            p = [], rot, len, pointsExist, attr;

        len = parents.length;
        n = parents[len - 1];

        if (Type.isNumber(n) && (parents.length !== 3 || n < 3)) {
            throw new Error("JSXGraph: A regular polygon needs two point types and a number > 2 as input.");
        }

        if (Type.isNumber(board.select(n))) { // Regular polygon given by 2 points and a number
            len--;
            pointsExist = false;
        } else {                              // Regular polygon given by n points
            n = len;
            pointsExist = true;
        }

        p = Type.providePoints(board, parents.slice(0, len), attributes, 'regularpolygon', ['vertices']);
        if (p === false) {
            throw new Error("JSXGraph: Can't create regular polygon with parent types other than 'point' and 'coordinate arrays' or a function returning an array of coordinates");
        }

        attr = Type.copyAttributes(attributes, board.options, 'regularpolygon', 'vertices');
        for (i = 2; i < n; i++) {
            rot = board.create('transform', [Math.PI * (2 - (n - 2) / n), p[i - 1]], {type: 'rotate'});
            if (pointsExist) {
                p[i].addTransform(p[i - 2], rot);
                p[i].fullUpdate();
            } else {
                if (Type.isArray(attr.ids) && attr.ids.length >= n - 2) {
                    attr.id = attr.ids[i - 2];
                }
                p[i] = board.create('point', [p[i - 2], rot], attr);
                p[i].type = Const.OBJECT_TYPE_CAS;

                // The next two lines of code are needed to make regular polygones draggable
                // The new helper points are set to be draggable.
                p[i].isDraggable = true;
                p[i].visProp.fixed = false;
            }
        }

        attr = Type.copyAttributes(attributes, board.options, 'regularpolygon');
        el = board.create('polygon', p, attr);
        el.elType = 'regularpolygon';

        return el;
    };

    /**
     * @class  A polygonal chain is a connected series of line segments determined by
     * <ul>
     *    <li> a list of points or
     *    <li> a list of coordinate arrays or
     *    <li> a function returning a list of coordinate arrays.
     * </ul>
     * Each two consecutive points of the list define a line.
     * In JSXGraph, a polygonal chain is simply realized as polygon without the last - closing - point.
     * This may lead to unexpected results. Polygonal chains can be distinguished from polygons by the attribute 'elType' which
     * is 'polygonalchain' for the first and 'polygon' for the latter.
     * @pseudo
     * @constructor
     * @name PolygonalChain
     * @type Polygon
     * @augments JXG.Polygon
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {Array} vertices The polygon's vertices.
     *
     * Additionally, a polygonal chain can be created by providing a polygonal chain and a transformation (or an array of transformations).
     * The result is a polygonal chain which is the transformation of the supplied polygona chain.
     *
     * @example
     *     var attr = {
     *             snapToGrid: true
     *         },
     *         p = [];
     *
     * 	p.push(board.create('point', [-4, 0], attr));
     * 	p.push(board.create('point', [-1, -3], attr));
     * 	p.push(board.create('point', [0, 2], attr));
     * 	p.push(board.create('point', [2, 1], attr));
     * 	p.push(board.create('point', [4, -2], attr));
     *
     *  var chain = board.create('polygonalchain', p, {borders: {strokeWidth: 3}});
     *
     * </pre><div id="JXG878f93d8-3e49-46cf-aca2-d3bb7d60c5ae" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG878f93d8-3e49-46cf-aca2-d3bb7d60c5ae',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *         var attr = {
     *                 snapToGrid: true
     *             },
     *             p = [];
     *
     *     	p.push(board.create('point', [-4, 0], attr));
     *     	p.push(board.create('point', [-1, -3], attr));
     *     	p.push(board.create('point', [0, 2], attr));
     *     	p.push(board.create('point', [2, 1], attr));
     *     	p.push(board.create('point', [4, -2], attr));
     *
     *         var chain = board.create('polygonalchain', p, {borders: {strokeWidth: 3}});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createPolygonalChain = function (board, parents, attributes) {
        var attr, el;

        attr = Type.copyAttributes(attributes, board.options, 'polygonalchain');
        el = board.create('polygon', parents, attr);
        el.elType = 'polygonalchain';

        // A polygonal chain is not necessarily closed.
        el.vertices.pop();
        board.removeObject(el.borders[el.borders.length - 1]);
        el.borders.pop();

        return el;
    };

    JXG.registerElement('polygon', JXG.createPolygon);
    JXG.registerElement('regularpolygon', JXG.createRegularPolygon);
    JXG.registerElement('polygonalchain', JXG.createPolygonalChain);

    return {
        Polygon: JXG.Polygon,
        createPolygon: JXG.createPolygon,
        createRegularPolygon: JXG.createRegularPolygon
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 base/coords
 base/element
 math/math
 math/geometry
 math/statistics
 math/numerics
 parser/geonext
 utils/type
  elements:
   transform
 */

/**
 * @fileoverview In this file the geometry element Curve is defined.
 */

define('base/curve',[
    'jxg', 'base/constants', 'base/coords', 'base/element', 'math/math', 'math/numerics',
    'math/plot', 'math/geometry', 'parser/geonext', 'utils/type', 'math/qdt'
], function (JXG, Const, Coords, GeometryElement, Mat, Numerics, Plot, Geometry, GeonextParser, Type, QDT) {

    "use strict";

    /**
     * Curves are the common object for function graphs, parametric curves, polar curves, and data plots.
     * @class Creates a new curve object. Do not use this constructor to create a curve. Use {@link JXG.Board#create} with
     * type {@link Curve}, or {@link Functiongraph} instead.
     * @augments JXG.GeometryElement
     * @param {String|JXG.Board} board The board the new curve is drawn on.
     * @param {Array} parents defining terms An array with the functon terms or the data points of the curve.
     * @param {Object} attributes Defines the visual appearance of the curve.
     * @see JXG.Board#generateName
     * @see JXG.Board#addCurve
     */
    JXG.Curve = function (board, parents, attributes) {
        this.constructor(board, attributes, Const.OBJECT_TYPE_CURVE, Const.OBJECT_CLASS_CURVE);

        this.points = [];
        /**
         * Number of points on curves. This value changes
         * between numberPointsLow and numberPointsHigh.
         * It is set in {@link JXG.Curve#updateCurve}.
         */
        this.numberPoints = Type.evaluate(this.visProp.numberpointshigh);

        this.bezierDegree = 1;

        /**
         * Array holding the x-coordinates of a data plot.
         * This array can be updated during run time by overwriting
         * the method {@link JXG.Curve#updateDataArray}.
         * @type array
         */
        this.dataX = null;

        /**
         * Array holding the y-coordinates of a data plot.
         * This array can be updated during run time by overwriting
         * the method {@link JXG.Curve#updateDataArray}.
         * @type array
         */
        this.dataY = null;

        /**
         * Array of ticks storing all the ticks on this curve. Do not set this field directly and use
         * {@link JXG.Curve#addTicks} and {@link JXG.Curve#removeTicks} to add and remove ticks to and
         * from the curve.
         * @type Array
         * @see JXG.Ticks
         */
        this.ticks = [];

        /**
         * Stores a quad tree if it is required. The quad tree is generated in the curve
         * updates and can be used to speed up the hasPoint method.
         * @type JXG.Math.Quadtree
         */
        this.qdt = null;

        if (Type.exists(parents[0])) {
            this.varname = parents[0];
        } else {
            this.varname = 'x';
        }

        // function graphs: "x"
        this.xterm = parents[1];
        // function graphs: e.g. "x^2"
        this.yterm = parents[2];

        // Converts GEONExT syntax into JavaScript syntax
        this.generateTerm(this.varname, this.xterm, this.yterm, parents[3], parents[4]);
        // First evaluation of the curve
        this.updateCurve();

        this.id = this.board.setId(this, 'G');
        this.board.renderer.drawCurve(this);

        this.board.finalizeAdding(this);

        this.createGradient();
        this.elType = 'curve';
        this.createLabel();

        if (Type.isString(this.xterm)) {
            this.notifyParents(this.xterm);
        }
        if (Type.isString(this.yterm)) {
            this.notifyParents(this.yterm);
        }

        this.methodMap = Type.deepCopy(this.methodMap, {
            generateTerm: 'generateTerm',
            setTerm: 'generateTerm',
            move: 'moveTo',
            moveTo: 'moveTo'
        });
    };

    JXG.Curve.prototype = new GeometryElement();

    JXG.extend(JXG.Curve.prototype, /** @lends JXG.Curve.prototype */ {

        /**
         * Gives the default value of the left bound for the curve.
         * May be overwritten in {@link JXG.Curve#generateTerm}.
         * @returns {Number} Left bound for the curve.
         */
        minX: function () {
            var leftCoords;

            if (Type.evaluate(this.visProp.curvetype) === 'polar') {
                return 0;
            }

            leftCoords = new Coords(Const.COORDS_BY_SCREEN, [-this.board.canvasWidth * 0.1, 0], this.board, false);
            return leftCoords.usrCoords[1];
        },

        /**
         * Gives the default value of the right bound for the curve.
         * May be overwritten in {@link JXG.Curve#generateTerm}.
         * @returns {Number} Right bound for the curve.
         */
        maxX: function () {
            var rightCoords;

            if (Type.evaluate(this.visProp.curvetype) === 'polar') {
                return 2 * Math.PI;
            }
            rightCoords = new Coords(Const.COORDS_BY_SCREEN, [this.board.canvasWidth * 1.1, 0], this.board, false);

            return rightCoords.usrCoords[1];
        },

        /**
         * The parametric function which defines the x-coordinate of the curve.
         * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}.
         * @param {Boolean} suspendUpdate A boolean flag which is false for the
         * first call of the function during a fresh plot of the curve and true
         * for all subsequent calls of the function. This may be used to speed up the
         * plotting of the curve, if the e.g. the curve depends on some input elements.
         * @returns {Number} x-coordinate of the curve at t.
         */
        X: function (t) {
            return NaN;
        },

        /**
        * The parametric function which defines the y-coordinate of the curve.
        * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}.
        * @param {Boolean} suspendUpdate A boolean flag which is false for the
        * first call of the function during a fresh plot of the curve and true
        * for all subsequent calls of the function. This may be used to speed up the
        * plotting of the curve, if the e.g. the curve depends on some input elements.
        * @returns {Number} y-coordinate of the curve at t.
         */
        Y: function (t) {
            return NaN;
        },

        /**
         * Treat the curve as curve with homogeneous coordinates.
         * @param {Number} t A number between {@link JXG.Curve#minX} and {@link JXG.Curve#maxX}.
         * @returns {Number} Always 1.0
         */
        Z: function (t) {
            return 1;
        },

        /**
         * Checks whether (x,y) is near the curve.
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @param {Number} start Optional start index for search on data plots.
         * @returns {Boolean} True if (x,y) is near the curve, False otherwise.
         */
        hasPoint: function (x, y, start) {
            var t, checkPoint, len, invMat, c,
                i, tX, tY,
                res = [],
                points, qdt,
                steps = Type.evaluate(this.visProp.numberpointslow),
                d = (this.maxX() - this.minX()) / steps,
                prec, type,
                dist = Infinity,
                ux2, uy2,
                ev_ct,
                mi, ma,
                suspendUpdate = true;


            if (Type.isObject(Type.evaluate(this.visProp.precision))) {
                type = this.board._inputDevice;
                prec = Type.evaluate(this.visProp.precision[type]);
            } else {
                // 'inherit'
                prec = this.board.options.precision.hasPoint;
            }
            checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false);
            x = checkPoint.usrCoords[1];
            y = checkPoint.usrCoords[2];

            // We use usrCoords. Only in the final distance calculation
            // screen coords are used
            prec += Type.evaluate(this.visProp.strokewidth) * 0.5;
            prec *= prec; // We do not want to take sqrt
            ux2 = this.board.unitX * this.board.unitX;
            uy2 = this.board.unitY * this.board.unitY;

            mi = this.minX();
            ma = this.maxX();
            if (Type.exists(this._visibleArea)) {
                mi = this._visibleArea[0];
                ma = this._visibleArea[1];
                d = (ma - mi) / steps;
            }

            ev_ct = Type.evaluate(this.visProp.curvetype);
            if (ev_ct === 'parameter' || ev_ct === 'polar') {
                if (this.transformations.length > 0) {
                    /**
                     * Transform the mouse/touch coordinates
                     * back to the original position of the curve.
                     */
                    this.updateTransformMatrix();
                    invMat = Mat.inverse(this.transformMat);
                    c = Mat.matVecMult(invMat, [1, x, y]);
                    x = c[1];
                    y = c[2];
                }

                // Brute force search for a point on the curve close to the mouse pointer
                for (i = 0, t = mi; i < steps; i++) {
                    tX = this.X(t, suspendUpdate);
                    tY = this.Y(t, suspendUpdate);

                    dist = (x - tX) * (x - tX) * ux2 + (y - tY) * (y - tY) * uy2;

                    if (dist <= prec) {
                        return true;
                    }

                    t += d;
                }
            } else if (ev_ct === 'plot' ||
                ev_ct === 'functiongraph') {

                if (!Type.exists(start) || start < 0) {
                    start = 0;
                }

                if (Type.exists(this.qdt) &&
                    Type.evaluate(this.visProp.useqdt) &&
                    this.bezierDegree !== 3
                ) {
                    qdt = this.qdt.query(new Coords(Const.COORDS_BY_USER, [x, y], this.board));
                    points = qdt.points;
                    len = points.length;
                } else {
                    points = this.points;
                    len = this.numberPoints - 1;
                }

                for (i = start; i < len; i++) {
                    if (this.bezierDegree === 3) {
                        res.push(Geometry.projectCoordsToBeziersegment([1, x, y], this, i));
                    } else {
                        if (qdt) {
                            if (points[i].prev) {
                                res = Geometry.projectCoordsToSegment(
                                    [1, x, y],
                                    points[i].prev.usrCoords,
                                    points[i].usrCoords
                                );
                            }

                            // If the next point in the array is the same as the current points
                            // next neighbor we don't have to project it onto that segment because
                            // that will already be done in the next iteration of this loop.
                            if (points[i].next && points[i + 1] !== points[i].next) {
                                res = Geometry.projectCoordsToSegment(
                                    [1, x, y],
                                    points[i].usrCoords,
                                    points[i].next.usrCoords
                                );
                            }
                        } else {
                            res = Geometry.projectCoordsToSegment(
                                [1, x, y],
                                points[i].usrCoords,
                                points[i + 1].usrCoords
                            );
                        }
                    }

                    if (res[1] >= 0 && res[1] <= 1 &&
                        (x - res[0][1]) * (x - res[0][1]) * ux2 +
                        (y - res[0][2]) * (y - res[0][2]) * uy2 <= prec) {
                        return true;
                    }
                }
                return false;
            }
            return (dist < prec);
        },

        /**
         * Allocate points in the Coords array this.points
         */
        allocatePoints: function () {
            var i, len;

            len = this.numberPoints;

            if (this.points.length < this.numberPoints) {
                for (i = this.points.length; i < len; i++) {
                    this.points[i] = new Coords(Const.COORDS_BY_USER, [0, 0], this.board, false);
                }
            }
        },

        /**
         * Computes for equidistant points on the x-axis the values of the function
         * @returns {JXG.Curve} Reference to the curve object.
         * @see JXG.Curve#updateCurve
         */
        update: function () {
            if (this.needsUpdate) {
                if (Type.evaluate(this.visProp.trace)) {
                    this.cloneToBackground(true);
                }
                this.updateCurve();
            }

            return this;
        },

        /**
         * Updates the visual contents of the curve.
         * @returns {JXG.Curve} Reference to the curve object.
         */
        updateRenderer: function () {
            //var wasReal;

            if (!this.needsUpdate) {
                return this;
            }

            if (this.visPropCalc.visible) {
                // wasReal = this.isReal;

                this.isReal = Plot.checkReal(this.points);

                if (//wasReal &&
                    !this.isReal) {
                    this.updateVisibility(false);
                }
            }

            if (this.visPropCalc.visible) {
                this.board.renderer.updateCurve(this);
            }

            /* Update the label if visible. */
            if (this.hasLabel && this.visPropCalc.visible && this.label &&
                this.label.visPropCalc.visible && this.isReal) {

                this.label.update();
                this.board.renderer.updateText(this.label);
            }

            // Update rendNode display
            this.setDisplayRendNode();
            // if (this.visPropCalc.visible !== this.visPropOld.visible) {
            //     this.board.renderer.display(this, this.visPropCalc.visible);
            //     this.visPropOld.visible = this.visPropCalc.visible;
            //
            //     if (this.hasLabel) {
            //         this.board.renderer.display(this.label, this.label.visPropCalc.visible);
            //     }
            // }

            this.needsUpdate = false;
            return this;
        },

        /**
         * For dynamic dataplots updateCurve can be used to compute new entries
         * for the arrays {@link JXG.Curve#dataX} and {@link JXG.Curve#dataY}. It
         * is used in {@link JXG.Curve#updateCurve}. Default is an empty method, can
         * be overwritten by the user.
         *
         *
         * @example
         * // This example overwrites the updateDataArray method.
         * // There, new values for the arrays JXG.Curve.dataX and JXG.Curve.dataY
         * // are computed from the value of the slider N
         *
         * var N = board.create('slider', [[0,1.5],[3,1.5],[1,3,40]], {name:'n',snapWidth:1});
         * var circ = board.create('circle',[[4,-1.5],1],{strokeWidth:1, strokecolor:'black', strokeWidth:2,
         * 		fillColor:'#0055ff13'});
         *
         * var c = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:2});
         * c.updateDataArray = function() {
         *         var r = 1, n = Math.floor(N.Value()),
         *             x = [0], y = [0],
         *             phi = Math.PI/n,
         *             h = r*Math.cos(phi),
         *             s = r*Math.sin(phi),
         *             i, j,
         *             px = 0, py = 0, sgn = 1,
         *             d = 16,
         *             dt = phi/d,
         *             pt;
         *
         *         for (i = 0; i < n; i++) {
         *             for (j = -d; j <= d; j++) {
         *                 pt = dt*j;
         *                 x.push(px + r*Math.sin(pt));
         *                 y.push(sgn*r*Math.cos(pt) - (sgn-1)*h*0.5);
         *             }
         *             px += s;
         *             sgn *= (-1);
         *         }
         *         x.push((n - 1)*s);
         *         y.push(h + (sgn - 1)*h*0.5);
         *         this.dataX = x;
         *         this.dataY = y;
         *     }
         *
         * var c2 = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:1});
         * c2.updateDataArray = function() {
         *         var r = 1, n = Math.floor(N.Value()),
         *             px = circ.midpoint.X(), py = circ.midpoint.Y(),
         *             x = [px], y = [py],
         *             phi = Math.PI/n,
         *             s = r*Math.sin(phi),
         *             i, j,
         *             d = 16,
         *             dt = phi/d,
         *             pt = Math.PI*0.5+phi;
         *
         *         for (i = 0; i < n; i++) {
         *             for (j= -d; j <= d; j++) {
         *                 x.push(px + r*Math.cos(pt));
         *                 y.push(py + r*Math.sin(pt));
         *                 pt -= dt;
         *             }
         *             x.push(px);
         *             y.push(py);
         *             pt += dt;
         *         }
         *         this.dataX = x;
         *         this.dataY = y;
         *     }
         *     board.update();
         *
         * </pre><div id="JXG20bc7802-e69e-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 600px; height: 400px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG20bc7802-e69e-11e5-b1bf-901b0e1b8723',
         *             {boundingbox: [-1.5,2,8,-3], keepaspectratio: true, axis: true, showcopyright: false, shownavigation: false});
         *             var N = board.create('slider', [[0,1.5],[3,1.5],[1,3,40]], {name:'n',snapWidth:1});
         *             var circ = board.create('circle',[[4,-1.5],1],{strokeWidth:1, strokecolor:'black',
         *             strokeWidth:2, fillColor:'#0055ff13'});
         *
         *             var c = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:2});
         *             c.updateDataArray = function() {
         *                     var r = 1, n = Math.floor(N.Value()),
         *                         x = [0], y = [0],
         *                         phi = Math.PI/n,
         *                         h = r*Math.cos(phi),
         *                         s = r*Math.sin(phi),
         *                         i, j,
         *                         px = 0, py = 0, sgn = 1,
         *                         d = 16,
         *                         dt = phi/d,
         *                         pt;
         *
         *                     for (i=0;i<n;i++) {
         *                         for (j=-d;j<=d;j++) {
         *                             pt = dt*j;
         *                             x.push(px+r*Math.sin(pt));
         *                             y.push(sgn*r*Math.cos(pt)-(sgn-1)*h*0.5);
         *                         }
         *                         px += s;
         *                         sgn *= (-1);
         *                     }
         *                     x.push((n-1)*s);
         *                     y.push(h+(sgn-1)*h*0.5);
         *                     this.dataX = x;
         *                     this.dataY = y;
         *                 }
         *
         *             var c2 = board.create('curve', [[0],[0]],{strokecolor:'red', strokeWidth:1});
         *             c2.updateDataArray = function() {
         *                     var r = 1, n = Math.floor(N.Value()),
         *                         px = circ.midpoint.X(), py = circ.midpoint.Y(),
         *                         x = [px], y = [py],
         *                         phi = Math.PI/n,
         *                         s = r*Math.sin(phi),
         *                         i, j,
         *                         d = 16,
         *                         dt = phi/d,
         *                         pt = Math.PI*0.5+phi;
         *
         *                     for (i=0;i<n;i++) {
         *                         for (j=-d;j<=d;j++) {
         *                             x.push(px+r*Math.cos(pt));
         *                             y.push(py+r*Math.sin(pt));
         *                             pt -= dt;
         *                         }
         *                         x.push(px);
         *                         y.push(py);
         *                         pt += dt;
         *                     }
         *                     this.dataX = x;
         *                     this.dataY = y;
         *                 }
         *                 board.update();
         *
         *     })();
         *
         * </script><pre>
         *
         * @example
         * // This is an example which overwrites updateDataArray and produces
         * // a Bezier curve of degree three.
         * var A = board.create('point', [-3,3]);
         * var B = board.create('point', [3,-2]);
         * var line = board.create('segment', [A,B]);
         *
         * var height = 0.5; // height of the curly brace
         *
         * // Curly brace
         * var crl = board.create('curve', [[0],[0]], {strokeWidth:1, strokeColor:'black'});
         * crl.bezierDegree = 3;
         * crl.updateDataArray = function() {
         *     var d = [B.X()-A.X(), B.Y()-A.Y()],
         *         dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]),
         *         mid = [(A.X()+B.X())*0.5, (A.Y()+B.Y())*0.5];
         *
         *     d[0] *= height/dl;
         *     d[1] *= height/dl;
         *
         *     this.dataX = [ A.X(), A.X()-d[1], mid[0], mid[0]-d[1], mid[0], B.X()-d[1], B.X() ];
         *     this.dataY = [ A.Y(), A.Y()+d[0], mid[1], mid[1]+d[0], mid[1], B.Y()+d[0], B.Y() ];
         * };
         *
         * // Text
         * var txt = board.create('text', [
         *                     function() {
         *                         var d = [B.X()-A.X(), B.Y()-A.Y()],
         *                             dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]),
         *                             mid = (A.X()+B.X())*0.5;
         *
         *                         d[1] *= height/dl;
         *                         return mid-d[1]+0.1;
         *                     },
         *                     function() {
         *                         var d = [B.X()-A.X(), B.Y()-A.Y()],
         *                             dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]),
         *                             mid = (A.Y()+B.Y())*0.5;
         *
         *                         d[0] *= height/dl;
         *                         return mid+d[0]+0.1;
         *                     },
         *                     function() { return "length=" + JXG.toFixed(B.Dist(A), 2); }
         *                 ]);
         *
         *
         * board.update(); // This update is necessary to call updateDataArray the first time.
         *
         * </pre><div id="JXGa61a4d66-e69f-11e5-b1bf-901b0e1b8723"  class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *      var board = JXG.JSXGraph.initBoard('JXGa61a4d66-e69f-11e5-b1bf-901b0e1b8723',
         *             {boundingbox: [-4, 4, 4,-4], axis: true, showcopyright: false, shownavigation: false});
         *     var A = board.create('point', [-3,3]);
         *     var B = board.create('point', [3,-2]);
         *     var line = board.create('segment', [A,B]);
         *
         *     var height = 0.5; // height of the curly brace
         *
         *     // Curly brace
         *     var crl = board.create('curve', [[0],[0]], {strokeWidth:1, strokeColor:'black'});
         *     crl.bezierDegree = 3;
         *     crl.updateDataArray = function() {
         *         var d = [B.X()-A.X(), B.Y()-A.Y()],
         *             dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]),
         *             mid = [(A.X()+B.X())*0.5, (A.Y()+B.Y())*0.5];
         *
         *         d[0] *= height/dl;
         *         d[1] *= height/dl;
         *
         *         this.dataX = [ A.X(), A.X()-d[1], mid[0], mid[0]-d[1], mid[0], B.X()-d[1], B.X() ];
         *         this.dataY = [ A.Y(), A.Y()+d[0], mid[1], mid[1]+d[0], mid[1], B.Y()+d[0], B.Y() ];
         *     };
         *
         *     // Text
         *     var txt = board.create('text', [
         *                         function() {
         *                             var d = [B.X()-A.X(), B.Y()-A.Y()],
         *                                 dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]),
         *                                 mid = (A.X()+B.X())*0.5;
         *
         *                             d[1] *= height/dl;
         *                             return mid-d[1]+0.1;
         *                         },
         *                         function() {
         *                             var d = [B.X()-A.X(), B.Y()-A.Y()],
         *                                 dl = Math.sqrt(d[0]*d[0]+d[1]*d[1]),
         *                                 mid = (A.Y()+B.Y())*0.5;
         *
         *                             d[0] *= height/dl;
         *                             return mid+d[0]+0.1;
         *                         },
         *                         function() { return "length="+JXG.toFixed(B.Dist(A), 2); }
         *                     ]);
         *
         *
         *     board.update(); // This update is necessary to call updateDataArray the first time.
         *
         *     })();
         *
         * </script><pre>
         *
         *
         */
        updateDataArray: function () {
            // this used to return this, but we shouldn't rely on the user to implement it.
        },

        /**
         * Computes the curve path
         * @see JXG.Curve#update
         * @returns {JXG.Curve} Reference to the curve object.
         */
        updateCurve: function () {
            var len, mi, ma, x, y, i,
                version = this.visProp.plotversion,
                //t1, t2, l1,
                suspendUpdate = false;

            this.updateTransformMatrix();
            this.updateDataArray();
            mi = this.minX();
            ma = this.maxX();

            // Discrete data points
            // x-coordinates are in an array
            if (Type.exists(this.dataX)) {
                this.numberPoints = this.dataX.length;
                len = this.numberPoints;

                // It is possible, that the array length has increased.
                this.allocatePoints();

                for (i = 0; i < len; i++) {
                    x = i;

                    // y-coordinates are in an array
                    if (Type.exists(this.dataY)) {
                        y = i;
                        // The last parameter prevents rounding in usr2screen().
                        this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.dataX[i], this.dataY[i]], false);
                    } else {
                        // discrete x data, continuous y data
                        y = this.X(x);
                        // The last parameter prevents rounding in usr2screen().
                        this.points[i].setCoordinates(Const.COORDS_BY_USER, [this.dataX[i], this.Y(y, suspendUpdate)], false);
                    }
                    this.points[i]._t = i;

                    // this.updateTransform(this.points[i]);
                    suspendUpdate = true;
                }
                // continuous x data
            } else {
                if (Type.evaluate(this.visProp.doadvancedplot)) {
                    // console.time("plot");

                    if (version === 1 || Type.evaluate(this.visProp.doadvancedplotold)) {
                        Plot.updateParametricCurveOld(this, mi, ma);
                    } else if (version === 2) {
                        Plot.updateParametricCurve_v2(this, mi, ma);
                    } else if (version === 3) {
                        Plot.updateParametricCurve_v3(this, mi, ma);
                    } else if (version === 4) {
                        Plot.updateParametricCurve_v4(this, mi, ma);
                    } else {
                        Plot.updateParametricCurve_v2(this, mi, ma);
                    }
                    // console.timeEnd("plot");
                } else {
                    if (this.board.updateQuality === this.board.BOARD_QUALITY_HIGH) {
                        this.numberPoints = Type.evaluate(this.visProp.numberpointshigh);
                    } else {
                        this.numberPoints = Type.evaluate(this.visProp.numberpointslow);
                    }

                    // It is possible, that the array length has increased.
                    this.allocatePoints();
                    Plot.updateParametricCurveNaive(this, mi, ma, this.numberPoints);
                }
                len = this.numberPoints;

                if (Type.evaluate(this.visProp.useqdt) &&
                    this.board.updateQuality === this.board.BOARD_QUALITY_HIGH) {
                    this.qdt = new QDT(this.board.getBoundingBox());
                    for (i = 0; i < this.points.length; i++) {
                        this.qdt.insert(this.points[i]);

                        if (i > 0) {
                            this.points[i].prev = this.points[i - 1];
                        }

                        if (i < len - 1) {
                            this.points[i].next = this.points[i + 1];
                        }
                    }
                }

                // for (i = 0; i < len; i++) {
                //     this.updateTransform(this.points[i]);
                // }
            }

            if (Type.evaluate(this.visProp.curvetype) !== 'plot' &&
                Type.evaluate(this.visProp.rdpsmoothing)) {
                // console.time("rdp");
                this.points = Numerics.RamerDouglasPeucker(this.points, 0.2);
                this.numberPoints = this.points.length;
                // console.timeEnd("rdp");
                // console.log(this.numberPoints);
            }

            len = this.numberPoints;
            for (i = 0; i < len; i++) {
                this.updateTransform(this.points[i]);
            }

            return this;
        },

        updateTransformMatrix: function () {
            var t, i,
                len = this.transformations.length;

            this.transformMat = [[1, 0, 0], [0, 1, 0], [0, 0, 1]];

            for (i = 0; i < len; i++) {
                t = this.transformations[i];
                t.update();
                this.transformMat = Mat.matMatMult(t.matrix, this.transformMat);
            }

            return this;
        },

        /**
         * Applies the transformations of the curve to the given point <tt>p</tt>.
         * Before using it, {@link JXG.Curve#updateTransformMatrix} has to be called.
         * @param {JXG.Point} p
         * @returns {JXG.Point} The given point.
         */
        updateTransform: function (p) {
            var c,
                len = this.transformations.length;

            if (len > 0) {
                c = Mat.matVecMult(this.transformMat, p.usrCoords);
                p.setCoordinates(Const.COORDS_BY_USER, c, false, true);
            }

            return p;
        },

        /**
         * Add transformations to this curve.
         * @param {JXG.Transformation|Array} transform Either one {@link JXG.Transformation} or an array of {@link JXG.Transformation}s.
         * @returns {JXG.Curve} Reference to the curve object.
         */
        addTransform: function (transform) {
            var i,
                list = Type.isArray(transform) ? transform : [transform],
                len = list.length;

            for (i = 0; i < len; i++) {
                this.transformations.push(list[i]);
            }

            return this;
        },

        /**
         * Generate the method curve.X() in case curve.dataX is an array
         * and generate the method curve.Y() in case curve.dataY is an array.
         * @private
         * @param {String} which Either 'X' or 'Y'
         * @returns {function}
         **/
        interpolationFunctionFromArray: function (which) {
            var data = 'data' + which,
                that = this;

            return function (t, suspendedUpdate) {
                var i, j, t0, t1,
                    arr = that[data],
                    len = arr.length,
                    last,
                    f = [];

                if (isNaN(t)) {
                    return NaN;
                }

                if (t < 0) {
                    if (Type.isFunction(arr[0])) {
                        return arr[0]();
                    }

                    return arr[0];
                }

                if (that.bezierDegree === 3) {
                    last = (len - 1) / 3;

                    if (t >= last) {
                        if (Type.isFunction(arr[arr.length - 1])) {
                            return arr[arr.length - 1]();
                        }

                        return arr[arr.length - 1];
                    }

                    i = Math.floor(t) * 3;
                    t0 = t % 1;
                    t1 = 1 - t0;

                    for (j = 0; j < 4; j++) {
                        if (Type.isFunction(arr[i + j])) {
                            f[j] = arr[i + j]();
                        } else {
                            f[j] = arr[i + j];
                        }
                    }

                    return t1 * t1 * (t1 * f[0] + 3 * t0 * f[1]) + (3 * t1 * f[2] + t0 * f[3]) * t0 * t0;
                }

                if (t > len - 2) {
                    i = len - 2;
                } else {
                    i = parseInt(Math.floor(t), 10);
                }

                if (i === t) {
                    if (Type.isFunction(arr[i])) {
                        return arr[i]();
                    }
                    return arr[i];
                }

                for (j = 0; j < 2; j++) {
                    if (Type.isFunction(arr[i + j])) {
                        f[j] = arr[i + j]();
                    } else {
                        f[j] = arr[i + j];
                    }
                }
                return f[0] + (f[1] - f[0]) * (t - i);
            };
        },

        /**
         * Converts the JavaScript/JessieCode/GEONExT syntax of the defining function term into JavaScript.
         * New methods X() and Y() for the Curve object are generated, further
         * new methods for minX() and maxX().
         * @see JXG.GeonextParser.geonext2JS.
         */
        generateTerm: function (varname, xterm, yterm, mi, ma) {
            var fx, fy;

            // Generate the methods X() and Y()
            if (Type.isArray(xterm)) {
                // Discrete data
                this.dataX = xterm;

                this.numberPoints = this.dataX.length;
                this.X = this.interpolationFunctionFromArray.apply(this, ['X']);
                this.visProp.curvetype = 'plot';
                this.isDraggable = true;
            } else {
                // Continuous data
                this.X = Type.createFunction(xterm, this.board, varname);
                if (Type.isString(xterm)) {
                    this.visProp.curvetype = 'functiongraph';
                } else if (Type.isFunction(xterm) || Type.isNumber(xterm)) {
                    this.visProp.curvetype = 'parameter';
                }

                this.isDraggable = true;
            }

            if (Type.isArray(yterm)) {
                this.dataY = yterm;
                this.Y = this.interpolationFunctionFromArray.apply(this, ['Y']);
            } else {
                this.Y = Type.createFunction(yterm, this.board, varname);
            }

            /**
             * Polar form
             * Input data is function xterm() and offset coordinates yterm
             */
            if (Type.isFunction(xterm) && Type.isArray(yterm)) {
                // Xoffset, Yoffset
                fx = Type.createFunction(yterm[0], this.board, '');
                fy = Type.createFunction(yterm[1], this.board, '');

                this.X = function (phi) {
                    return xterm(phi) * Math.cos(phi) + fx();
                };

                this.Y = function (phi) {
                    return xterm(phi) * Math.sin(phi) + fy();
                };

                this.visProp.curvetype = 'polar';
            }

            // Set the bounds lower bound
            if (Type.exists(mi)) {
                this.minX = Type.createFunction(mi, this.board, '');
            }
            if (Type.exists(ma)) {
                this.maxX = Type.createFunction(ma, this.board, '');
            }
        },

        /**
         * Finds dependencies in a given term and notifies the parents by adding the
         * dependent object to the found objects child elements.
         * @param {String} contentStr String containing dependencies for the given object.
         */
        notifyParents: function (contentStr) {
            var fstr, dep,
                isJessieCode = false,
                obj;

            // Read dependencies found by the JessieCode parser
            obj = { 'xterm': 1, 'yterm': 1 };
            for (fstr in obj) {
                if (obj.hasOwnProperty(fstr) && this.hasOwnProperty(fstr) && this[fstr].origin) {
                    isJessieCode = true;
                    for (dep in this[fstr].origin.deps) {
                        if (this[fstr].origin.deps.hasOwnProperty(dep)) {
                            this[fstr].origin.deps[dep].addChild(this);
                        }
                    }
                }
            }

            if (!isJessieCode) {
                GeonextParser.findDependencies(this, contentStr, this.board);
            }
        },

        // documented in geometry element
        getLabelAnchor: function () {
            var c, x, y,
                ax = 0.05 * this.board.canvasWidth,
                ay = 0.05 * this.board.canvasHeight,
                bx = 0.95 * this.board.canvasWidth,
                by = 0.95 * this.board.canvasHeight;

            switch (Type.evaluate(this.visProp.label.position)) {
                case 'ulft':
                    x = ax;
                    y = ay;
                    break;
                case 'llft':
                    x = ax;
                    y = by;
                    break;
                case 'rt':
                    x = bx;
                    y = 0.5 * by;
                    break;
                case 'lrt':
                    x = bx;
                    y = by;
                    break;
                case 'urt':
                    x = bx;
                    y = ay;
                    break;
                case 'top':
                    x = 0.5 * bx;
                    y = ay;
                    break;
                case 'bot':
                    x = 0.5 * bx;
                    y = by;
                    break;
                default:
                    // includes case 'lft'
                    x = ax;
                    y = 0.5 * by;
            }

            c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board, false);
            return Geometry.projectCoordsToCurve(c.usrCoords[1], c.usrCoords[2], 0, this, this.board)[0];
        },

        // documented in geometry element
        cloneToBackground: function () {
            var er,
                copy = {
                    id: this.id + 'T' + this.numTraces,
                    elementClass: Const.OBJECT_CLASS_CURVE,

                    points: this.points.slice(0),
                    bezierDegree: this.bezierDegree,
                    numberPoints: this.numberPoints,
                    board: this.board,
                    visProp: Type.deepCopy(this.visProp, this.visProp.traceattributes, true)
                };

            copy.visProp.layer = this.board.options.layer.trace;
            copy.visProp.curvetype = this.visProp.curvetype;
            this.numTraces++;

            Type.clearVisPropOld(copy);
            copy.visPropCalc = {
                visible: Type.evaluate(copy.visProp.visible)
            };
            er = this.board.renderer.enhancedRendering;
            this.board.renderer.enhancedRendering = true;
            this.board.renderer.drawCurve(copy);
            this.board.renderer.enhancedRendering = er;
            this.traces[copy.id] = copy.rendNode;

            return this;
        },

        // Already documented in GeometryElement
        bounds: function () {
            var minX = Infinity, maxX = -Infinity, minY = Infinity, maxY = -Infinity,
                l = this.points.length, i,
                bezier, up;

            if (this.bezierDegree === 3) {
                // Add methods X(), Y()
                for (i = 0; i < l; i++) {
                    this.points[i].X = Type.bind(function () { return this.usrCoords[1]; }, this.points[i]);
                    this.points[i].Y = Type.bind(function () { return this.usrCoords[2]; }, this.points[i]);
                }
                bezier = Numerics.bezier(this.points);
                up = bezier[3]();
                minX = Numerics.fminbr(function (t) { return bezier[0](t); }, [0, up]);
                maxX = Numerics.fminbr(function (t) { return -bezier[0](t); }, [0, up]);
                minY = Numerics.fminbr(function (t) { return bezier[1](t); }, [0, up]);
                maxY = Numerics.fminbr(function (t) { return -bezier[1](t); }, [0, up]);

                minX = bezier[0](minX);
                maxX = bezier[0](maxX);
                minY = bezier[1](minY);
                maxY = bezier[1](maxY);
                return [minX, maxY, maxX, minY];
            }

            // Linear segments
            for (i = 0; i < l; i++) {
                if (minX > this.points[i].usrCoords[1]) {
                    minX = this.points[i].usrCoords[1];
                }

                if (maxX < this.points[i].usrCoords[1]) {
                    maxX = this.points[i].usrCoords[1];
                }

                if (minY > this.points[i].usrCoords[2]) {
                    minY = this.points[i].usrCoords[2];
                }

                if (maxY < this.points[i].usrCoords[2]) {
                    maxY = this.points[i].usrCoords[2];
                }
            }

            return [minX, maxY, maxX, minY];
        },

        // documented in element.js
        getParents: function () {
            var p = [this.xterm, this.yterm, this.minX(), this.maxX()];

            if (this.parents.length !== 0) {
                p = this.parents;
            }

            return p;
        },

        /**
         * Shift the curve by the vector 'where'.
         *
         * @param {Array} where Array containing the x and y coordinate of the target location.
         * @returns {JXG.Curve} Reference to itself.
         */
        moveTo: function (where) {
            // TODO add animation
            var delta = [], p;
            if (this.points.length > 0 && !Type.evaluate(this.visProp.fixed)) {
                p = this.points[0];
                if (where.length === 3) {
                    delta = [where[0] - p.usrCoords[0],
                    where[1] - p.usrCoords[1],
                    where[2] - p.usrCoords[2]];
                } else {
                    delta = [where[0] - p.usrCoords[1],
                    where[1] - p.usrCoords[2]];
                }
                this.setPosition(Const.COORDS_BY_USER, delta);
            }
            return this;
        },

        /**
         * If the curve is the result of a transformation applied
         * to a continuous curve, the glider projection has to be done
         * on the original curve. Otherwise there will be problems
         * when changing between high and low precision plotting,
         * since there number of points changes.
         *
         * @private
         * @returns {Array} [Boolean, curve]: Array contining 'true' if curve is result of a transformation,
         *   and the source curve of the transformation.
         */
        getTransformationSource: function () {
            var isTransformed, curve_org;
            if (Type.exists(this._transformationSource)) {
                curve_org = this._transformationSource;
                if (curve_org.elementClass === Const.OBJECT_CLASS_CURVE //&&
                    //Type.evaluate(curve_org.visProp.curvetype) !== 'plot'
                ) {
                    isTransformed = true;
                }
            }
            return [isTransformed, curve_org];
        }

    });

    /**
     * @class This element is used to provide a constructor for curve, which is just a wrapper for element {@link Curve}.
     * A curve is a mapping from R to R^2. t mapsto (x(t),y(t)). The graph is drawn for t in the interval [a,b].
     * <p>
     * The following types of curves can be plotted:
     * <ul>
     *  <li> parametric curves: t mapsto (x(t),y(t)), where x() and y() are univariate functions.
     *  <li> polar curves: curves commonly written with polar equations like spirals and cardioids.
     *  <li> data plots: plot line segments through a given list of coordinates.
     * </ul>
     * @pseudo
     * @description
     * @name Curve
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     *
     * @param {function,number_function,number_function,number_function,number} x,y,a_,b_ Parent elements for Parametric Curves.
     *                     <p>
     *                     x describes the x-coordinate of the curve. It may be a function term in one variable, e.g. x(t).
     *                     In case of x being of type number, x(t) is set to  a constant function.
     *                     this function at the values of the array.
     *                     </p>
     *                     <p>
     *                     y describes the y-coordinate of the curve. In case of a number, y(t) is set to the constant function
     *                     returning this number.
     *                     </p>
     *                     <p>
     *                     Further parameters are an optional number or function for the left interval border a,
     *                     and an optional number or function for the right interval border b.
     *                     </p>
     *                     <p>
     *                     Default values are a=-10 and b=10.
     *                     </p>
     * @param {array_array,function,number} x,y Parent elements for Data Plots.
     *                     <p>
     *                     x and y are arrays contining the x and y coordinates of the data points which are connected by
     *                     line segments. The individual entries of x and y may also be functions.
     *                     In case of x being an array the curve type is data plot, regardless of the second parameter and
     *                     if additionally the second parameter y is a function term the data plot evaluates.
     *                     </p>
     * @param {function_array,function,number_function,number_function,number} r,offset_,a_,b_ Parent elements for Polar Curves.
     *                     <p>
     *                     The first parameter is a function term r(phi) describing the polar curve.
     *                     </p>
     *                     <p>
     *                     The second parameter is the offset of the curve. It has to be
     *                     an array containing numbers or functions describing the offset. Default value is the origin [0,0].
     *                     </p>
     *                     <p>
     *                     Further parameters are an optional number or function for the left interval border a,
     *                     and an optional number or function for the right interval border b.
     *                     </p>
     *                     <p>
     *                     Default values are a=-10 and b=10.
     *                     </p>
     * <p>
     * Additionally, a curve can be created by providing a curve and a transformation (or an array of transformations).
     * The result is a curve which is the transformation of the supplied curve.
     *
     * @see JXG.Curve
     * @example
     * // Parametric curve
     * // Create a curve of the form (t-sin(t), 1-cos(t), i.e.
     * // the cycloid curve.
     *   var graph = board.create('curve',
     *                        [function(t){ return t-Math.sin(t);},
     *                         function(t){ return 1-Math.cos(t);},
     *                         0, 2*Math.PI]
     *                     );
     * </pre><div class="jxgbox" id="JXGaf9f818b-f3b6-4c4d-8c4c-e4a4078b726d" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var c1_board = JXG.JSXGraph.initBoard('JXGaf9f818b-f3b6-4c4d-8c4c-e4a4078b726d', {boundingbox: [-1, 5, 7, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var graph1 = c1_board.create('curve', [function(t){ return t-Math.sin(t);},function(t){ return 1-Math.cos(t);},0, 2*Math.PI]);
     * </script><pre>
     * @example
     * // Data plots
     * // Connect a set of points given by coordinates with dashed line segments.
     * // The x- and y-coordinates of the points are given in two separate
     * // arrays.
     *   var x = [0,1,2,3,4,5,6,7,8,9];
     *   var y = [9.2,1.3,7.2,-1.2,4.0,5.3,0.2,6.5,1.1,0.0];
     *   var graph = board.create('curve', [x,y], {dash:2});
     * </pre><div class="jxgbox" id="JXG7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var c3_board = JXG.JSXGraph.initBoard('JXG7dcbb00e-b6ff-481d-b4a8-887f5d8c6a83', {boundingbox: [-1,10,10,-1], axis: true, showcopyright: false, shownavigation: false});
     *   var x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
     *   var y = [9.2, 1.3, 7.2, -1.2, 4.0, 5.3, 0.2, 6.5, 1.1, 0.0];
     *   var graph3 = c3_board.create('curve', [x,y], {dash:2});
     * </script><pre>
     * @example
     * // Polar plot
     * // Create a curve with the equation r(phi)= a*(1+phi), i.e.
     * // a cardioid.
     *   var a = board.create('slider',[[0,2],[2,2],[0,1,2]]);
     *   var graph = board.create('curve',
     *                        [function(phi){ return a.Value()*(1-Math.cos(phi));},
     *                         [1,0],
     *                         0, 2*Math.PI],
     *                         {curveType: 'polar'}
     *                     );
     * </pre><div class="jxgbox" id="JXGd0bc7a2a-8124-45ca-a6e7-142321a8f8c2" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var c2_board = JXG.JSXGraph.initBoard('JXGd0bc7a2a-8124-45ca-a6e7-142321a8f8c2', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false});
     *   var a = c2_board.create('slider',[[0,2],[2,2],[0,1,2]]);
     *   var graph2 = c2_board.create('curve', [function(phi){ return a.Value()*(1-Math.cos(phi));}, [1,0], 0, 2*Math.PI], {curveType: 'polar'});
     * </script><pre>
     *
     * @example
     *  // Draggable Bezier curve
     *  var col, p, c;
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[1, 2.5 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[-1, -2.5 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -2], {size: 5, strokeColor:col, fillColor:col}));
     *
     *  c = board.create('curve', JXG.Math.Numerics.bezier(p),
     *              {strokeColor:'red', name:"curve", strokeWidth:5, fixed: false}); // Draggable curve
     *  c.addParents(p);
     * </pre><div class="jxgbox" id="JXG7bcc6280-f6eb-433e-8281-c837c3387849" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function(){
     *  var board, col, p, c;
     *  board = JXG.JSXGraph.initBoard('JXG7bcc6280-f6eb-433e-8281-c837c3387849', {boundingbox: [-3,3,3,-3], axis: true, showcopyright: false, shownavigation: false});
     *  col = 'blue';
     *  p = [];
     *  p.push(board.create('point',[-2, -1 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[1, 2.5 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[-1, -2.5 ], {size: 5, strokeColor:col, fillColor:col}));
     *  p.push(board.create('point',[2, -2], {size: 5, strokeColor:col, fillColor:col}));
     *
     *  c = board.create('curve', JXG.Math.Numerics.bezier(p),
     *              {strokeColor:'red', name:"curve", strokeWidth:5, fixed: false}); // Draggable curve
     *  c.addParents(p);
     * })();
     * </script><pre>
     *
     * @example
     *         // The curve cu2 is the reflection of cu1 against line li
     *         var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
     *         var reflect = board.create('transform', [li], {type: 'reflect'});
     *         var cu1 = board.create('curve', [[-1, -1, -0.5, -1, -1, -0.5], [-3, -2, -2, -2, -2.5, -2.5]]);
     *         var cu2 = board.create('curve', [cu1, reflect], {strokeColor: 'red'});
     *
     * </pre><div id="JXG866dc7a2-d448-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG866dc7a2-d448-11e7-93b3-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *             var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
     *             var reflect = board.create('transform', [li], {type: 'reflect'});
     *             var cu1 = board.create('curve', [[-1, -1, -0.5, -1, -1, -0.5], [-3, -2, -2, -2, -2.5, -2.5]]);
     *             var cu2 = board.create('curve', [cu1, reflect], {strokeColor: 'red'});
     *
     *     })();
     *
     * </script><pre>
     */
    JXG.createCurve = function (board, parents, attributes) {
        var obj, cu,
            attr = Type.copyAttributes(attributes, board.options, 'curve');

        obj = board.select(parents[0], true);
        if (Type.isObject(obj) &&
            (obj.type === Const.OBJECT_TYPE_CURVE ||
                obj.type === Const.OBJECT_TYPE_ANGLE ||
                obj.type === Const.OBJECT_TYPE_ARC ||
                obj.type === Const.OBJECT_TYPE_CONIC ||
                obj.type === Const.OBJECT_TYPE_SECTOR) &&
            Type.isTransformationOrArray(parents[1])) {

            if (obj.type === Const.OBJECT_TYPE_SECTOR) {
                attr = Type.copyAttributes(attributes, board.options, 'sector');
            } else if (obj.type === Const.OBJECT_TYPE_ARC) {
                attr = Type.copyAttributes(attributes, board.options, 'arc');
            } else if (obj.type === Const.OBJECT_TYPE_ANGLE) {
                if (!Type.exists(attributes.withLabel)) {
                    attributes.withLabel = false;
                }
                attr = Type.copyAttributes(attributes, board.options, 'angle');
            } else {
                attr = Type.copyAttributes(attributes, board.options, 'curve');
            }
            attr = Type.copyAttributes(attr, board.options, 'curve');

            cu = new JXG.Curve(board, ['x', [], []], attr);
            cu.updateDataArray = function () {
                var i, le = obj.numberPoints;
                this.bezierDegree = obj.bezierDegree;
                this.dataX = [];
                this.dataY = [];
                for (i = 0; i < le; i++) {
                    this.dataX.push(obj.points[i].usrCoords[1]);
                    this.dataY.push(obj.points[i].usrCoords[2]);
                }
                return this;
            };
            cu.addTransform(parents[1]);
            obj.addChild(cu);
            cu.setParents([obj]);
            cu._transformationSource = obj;

            return cu;
        }
        attr = Type.copyAttributes(attributes, board.options, 'curve');
        return new JXG.Curve(board, ['x'].concat(parents), attr);
    };

    JXG.registerElement('curve', JXG.createCurve);

    /**
     * @class This element is used to provide a constructor for functiongraph,
     * which is just a wrapper for element {@link Curve} with {@link JXG.Curve#X}()
     * set to x. The graph is drawn for x in the interval [a,b].
     * @pseudo
     * @description
     * @name Functiongraph
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     * @param {function_number,function_number,function} f,a_,b_ Parent elements are a function term f(x) describing the function graph.
     *         <p>
     *         Further, an optional number or function for the left interval border a,
     *         and an optional number or function for the right interval border b.
     *         <p>
     *         Default values are a=-10 and b=10.
     * @see JXG.Curve
     * @example
     * // Create a function graph for f(x) = 0.5*x*x-2*x
     *   var graph = board.create('functiongraph',
     *                        [function(x){ return 0.5*x*x-2*x;}, -2, 4]
     *                     );
     * </pre><div class="jxgbox" id="JXGefd432b5-23a3-4846-ac5b-b471e668b437" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var alex1_board = JXG.JSXGraph.initBoard('JXGefd432b5-23a3-4846-ac5b-b471e668b437', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     *   var graph = alex1_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, 4]);
     * </script><pre>
     * @example
     * // Create a function graph for f(x) = 0.5*x*x-2*x with variable interval
     *   var s = board.create('slider',[[0,4],[3,4],[-2,4,5]]);
     *   var graph = board.create('functiongraph',
     *                        [function(x){ return 0.5*x*x-2*x;},
     *                         -2,
     *                         function(){return s.Value();}]
     *                     );
     * </pre><div class="jxgbox" id="JXG4a203a84-bde5-4371-ad56-44619690bb50" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var alex2_board = JXG.JSXGraph.initBoard('JXG4a203a84-bde5-4371-ad56-44619690bb50', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     *   var s = alex2_board.create('slider',[[0,4],[3,4],[-2,4,5]]);
     *   var graph = alex2_board.create('functiongraph', [function(x){ return 0.5*x*x-2*x;}, -2, function(){return s.Value();}]);
     * </script><pre>
     */
    JXG.createFunctiongraph = function (board, parents, attributes) {
        var attr,
            par = ['x', 'x'].concat(parents);

        attr = Type.copyAttributes(attributes, board.options, 'curve');
        attr.curvetype = 'functiongraph';
        return new JXG.Curve(board, par, attr);
    };

    JXG.registerElement('functiongraph', JXG.createFunctiongraph);
    JXG.registerElement('plot', JXG.createFunctiongraph);

    /**
     * @class This element is used to provide a constructor for (natural) cubic spline curves.
     * Create a dynamic spline interpolated curve given by sample points p_1 to p_n.
     * @pseudo
     * @description
     * @name Spline
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     * @param {JXG.Board} board Reference to the board the spline is drawn on.
     * @param {Array} parents Array of points the spline interpolates. This can be
     *   <ul>
     *   <li> an array of JXGGraph points</li>
     *   <li> an array of coordinate pairs</li>
     *   <li> an array of functions returning coordinate pairs</li>
     *   <li> an array consisting of an array with x-coordinates and an array of y-coordinates</li>
     *   </ul>
     *   All individual entries of coordinates arrays may be numbers or functions returning numbers.
     * @param {Object} attributes Define color, width, ... of the spline
     * @returns {JXG.Curve} Returns reference to an object of type JXG.Curve.
     * @see JXG.Curve
     * @example
     *
     * var p = [];
     * p[0] = board.create('point', [-2,2], {size: 4, face: 'o'});
     * p[1] = board.create('point', [0,-1], {size: 4, face: 'o'});
     * p[2] = board.create('point', [2,0], {size: 4, face: 'o'});
     * p[3] = board.create('point', [4,1], {size: 4, face: 'o'});
     *
     * var c = board.create('spline', p, {strokeWidth:3});
     * </pre><div id="JXG6c197afc-e482-11e5-b1bf-901b0e1b8723" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG6c197afc-e482-11e5-b1bf-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *
     *     var p = [];
     *     p[0] = board.create('point', [-2,2], {size: 4, face: 'o'});
     *     p[1] = board.create('point', [0,-1], {size: 4, face: 'o'});
     *     p[2] = board.create('point', [2,0], {size: 4, face: 'o'});
     *     p[3] = board.create('point', [4,1], {size: 4, face: 'o'});
     *
     *     var c = board.create('spline', p, {strokeWidth:3});
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createSpline = function (board, parents, attributes) {
        var el, funcs, ret;

        funcs = function () {
            var D, x = [], y = [];

            return [function (t, suspended) {   // Function term
                var i, j, c;

                if (!suspended) {
                    x = [];
                    y = [];

                    // given as [x[], y[]]
                    if (parents.length === 2 && Type.isArray(parents[0]) && Type.isArray(parents[1]) && parents[0].length === parents[1].length) {
                        for (i = 0; i < parents[0].length; i++) {
                            if (Type.isFunction(parents[0][i])) {
                                x.push(parents[0][i]());
                            } else {
                                x.push(parents[0][i]);
                            }

                            if (Type.isFunction(parents[1][i])) {
                                y.push(parents[1][i]());
                            } else {
                                y.push(parents[1][i]);
                            }
                        }
                    } else {
                        for (i = 0; i < parents.length; i++) {
                            if (Type.isPoint(parents[i])) {
                                x.push(parents[i].X());
                                y.push(parents[i].Y());
                                // given as [[x1,y1], [x2, y2], ...]
                            } else if (Type.isArray(parents[i]) && parents[i].length === 2) {
                                for (j = 0; j < parents.length; j++) {
                                    if (Type.isFunction(parents[j][0])) {
                                        x.push(parents[j][0]());
                                    } else {
                                        x.push(parents[j][0]);
                                    }

                                    if (Type.isFunction(parents[j][1])) {
                                        y.push(parents[j][1]());
                                    } else {
                                        y.push(parents[j][1]);
                                    }
                                }
                            } else if (Type.isFunction(parents[i]) && parents[i]().length === 2) {
                                c = parents[i]();
                                x.push(c[0]);
                                y.push(c[1]);
                            }
                        }
                    }

                    // The array D has only to be calculated when the position of one or more sample points
                    // changes. Otherwise D is always the same for all points on the spline.
                    D = Numerics.splineDef(x, y);
                }

                return Numerics.splineEval(t, x, y, D);
            },
            // minX()
            function () {
                return x[0];
            },
            //maxX()
            function () {
                return x[x.length - 1];
            }];

        };

        attributes = Type.copyAttributes(attributes, board.options, 'curve');
        attributes.curvetype = 'functiongraph';
        ret = funcs();
        el = new JXG.Curve(board, ['x', 'x', ret[0], ret[1], ret[2]], attributes);
        el.setParents(parents);
        el.elType = 'spline';

        return el;
    };

    /**
     * Register the element type spline at JSXGraph
     * @private
     */
    JXG.registerElement('spline', JXG.createSpline);

    /**
     * @class This element is used to provide a constructor for cardinal spline curves.
     * Create a dynamic cardinal spline interpolated curve given by sample points p_1 to p_n.
     * @pseudo
     * @description
     * @name Cardinalspline
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     * @param {JXG.Board} board Reference to the board the cardinal spline is drawn on.
     * @param {Array} parents Array with three entries.
     * <p>
     *   First entry: Array of points the spline interpolates. This can be
     *   <ul>
     *   <li> an array of JXGGraph points</li>
     *   <li> an array of coordinate pairs</li>
     *   <li> an array of functions returning coordinate pairs</li>
     *   <li> an array consisting of an array with x-coordinates and an array of y-coordinates</li>
     *   </ul>
     *   All individual entries of coordinates arrays may be numbers or functions returning numbers.
     *   <p>
     *   Second entry: tau number or function
     *   <p>
     *   Third entry: type string containing 'uniform' (default) or 'centripetal'.
     * @param {Object} attributes Define color, width, ... of the cardinal spline
     * @returns {JXG.Curve} Returns reference to an object of type JXG.Curve.
     * @see JXG.Curve
     * @example
     * //create a cardinal spline out of an array of JXG points with adjustable tension
     * //create array of points
     * var p1 = board.create('point',[0,0])
     * var p2 = board.create('point',[1,4])
     * var p3 = board.create('point',[4,5])
     * var p4 = board.create('point',[2,3])
     * var p5 = board.create('point',[3,0])
     * var p = [p1,p2,p3,p4,p5]
     *
     * // tension
     * tau = board.create('slider', [[4,3],[9,3],[0.001,0.5,1]], {name:'tau'});
     * c = board.create('curve', JXG.Math.Numerics.CardinalSpline(p, function(){ return tau.Value();}), {strokeWidth:3});
     * </pre><div id="JXG6c197afc-e482-11e5-b2af-901b0e1b8723" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG6c197afc-e482-11e5-b2af-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *
     *     var p = [];
     *     p[0] = board.create('point', [-2,2], {size: 4, face: 'o'});
     *     p[1] = board.create('point', [0,-1], {size: 4, face: 'o'});
     *     p[2] = board.create('point', [2,0], {size: 4, face: 'o'});
     *     p[3] = board.create('point', [4,1], {size: 4, face: 'o'});
     *
     *     var c = board.create('spline', p, {strokeWidth:3});
     *     })();
     *
     * </script><pre>
     */
    JXG.createCardinalSpline = function (board, parents, attributes) {
        var el, getPointLike,
            points, tau, type,
            p, q, i, le,
            splineArr,
            errStr = "\nPossible parent types: [points:array, tau:number|function, type:string]";

        if (!Type.exists(parents[0]) || !Type.isArray(parents[0])) {
            throw new Error("JSXGraph: JXG.createCardinalSpline: argument 1 'points' has to be array of points or coordinate pairs" + errStr);
        }
        if (!Type.exists(parents[1]) || (!Type.isNumber(parents[1]) && !Type.isFunction(parents[1]))) {
            throw new Error("JSXGraph: JXG.createCardinalSpline: argument 2 'tau' has to be number between [0,1] or function'" + errStr);
        }
        if (!Type.exists(parents[2]) || !Type.isString(parents[2])) {
            throw new Error("JSXGraph: JXG.createCardinalSpline: argument 3 'type' has to be string 'uniform' or 'centripetal'" + errStr);
        }

        attributes = Type.copyAttributes(attributes, board.options, 'curve');
        attributes = Type.copyAttributes(attributes, board.options, 'cardinalspline');
        attributes.curvetype = 'parameter';

        p = parents[0];
        q = [];

        // given as [x[], y[]]
        if (!attributes.isarrayofcoordinates &&
            p.length === 2 && Type.isArray(p[0]) && Type.isArray(p[1]) &&
            p[0].length === p[1].length) {
            for (i = 0; i < p[0].length; i++) {
                q[i] = [];
                if (Type.isFunction(p[0][i])) {
                    q[i].push(p[0][i]());
                } else {
                    q[i].push(p[0][i]);
                }

                if (Type.isFunction(p[1][i])) {
                    q[i].push(p[1][i]());
                } else {
                    q[i].push(p[1][i]);
                }
            }
        } else {
            // given as [[x0, y0], [x1, y1], point, ...]
            for (i = 0; i < p.length; i++) {
                if (Type.isString(p[i])) {
                    q.push(board.select(p[i]));
                } else if (Type.isPoint(p[i])) {
                    q.push(p[i]);
                    // given as [[x0,y0], [x1, y2], ...]
                } else if (Type.isArray(p[i]) && p[i].length === 2) {
                    q[i] = [];
                    if (Type.isFunction(p[i][0])) {
                        q[i].push(p[i][0]());
                    } else {
                        q[i].push(p[i][0]);
                    }

                    if (Type.isFunction(p[i][1])) {
                        q[i].push(p[i][1]());
                    } else {
                        q[i].push(p[i][1]);
                    }
                } else if (Type.isFunction(p[i]) && p[i]().length === 2) {
                    q.push(parents[i]());
                }
            }
        }

        if (attributes.createpoints === true) {
            points = Type.providePoints(board, q, attributes, 'cardinalspline', ['points']);
        } else {
            points = [];

            /**
             * @ignore
             */
            getPointLike = function (ii) {
                return {
                    X: function () { return q[ii][0]; },
                    Y: function () { return q[ii][1]; },
                    Dist: function (p) {
                        var dx = this.X() - p.X(),
                            dy = this.Y() - p.Y();
                        return Math.sqrt(dx * dx + dy * dy);
                    }
                };
            };

            for (i = 0; i < q.length; i++) {
                if (Type.isPoint(q[i])) {
                    points.push(q[i]);
                } else {
                    points.push(getPointLike(i));
                }
            }
        }

        tau = parents[1];
        type = parents[2];

        splineArr = ['x'].concat(Numerics.CardinalSpline(points, tau, type));

        el = new JXG.Curve(board, splineArr, attributes);
        le = points.length;
        el.setParents(points);
        for (i = 0; i < le; i++) {
            p = points[i];
            if (Type.isPoint(p)) {
                if (Type.exists(p._is_new)) {
                    el.addChild(p);
                    delete p._is_new;
                } else {
                    p.addChild(el);
                }
            }
        }
        el.elType = 'cardinalspline';

        return el;
    };

    /**
     * Register the element type cardinalspline at JSXGraph
     * @private
     */
    JXG.registerElement('cardinalspline', JXG.createCardinalSpline);

    /**
     * @class This element is used to provide a constructor for metapost spline curves.
     * Create a dynamic metapost spline interpolated curve given by sample points p_1 to p_n.
     * @pseudo
     * @description
     * @name Metapostspline
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     * @param {JXG.Board} board Reference to the board the metapost spline is drawn on.
     * @param {Array} parents Array with two entries.
     * <p>
     *   First entry: Array of points the spline interpolates. This can be
     *   <ul>
     *   <li> an array of JXGGraph points</li>
     *   <li> an object of coordinate pairs</li>
     *   <li> an array of functions returning coordinate pairs</li>
     *   <li> an array consisting of an array with x-coordinates and an array of y-coordinates</li>
     *   </ul>
     *   All individual entries of coordinates arrays may be numbers or functions returning numbers.
     *   <p>
     *   Second entry: JavaScript object containing the control values like tension, direction, curl.
     * @param {Object} attributes Define color, width, ... of the metapost spline
     * @returns {JXG.Curve} Returns reference to an object of type JXG.Curve.
     * @see JXG.Curve
     * @example
     *     var po = [],
     *         attr = {
     *             size: 5,
     *             color: 'red'
     *         },
     *         controls;
     *
     *     var tension = board.create('slider', [[-3, 6], [3, 6], [0, 1, 20]], {name: 'tension'});
     *     var curl = board.create('slider', [[-3, 5], [3, 5], [0, 1, 30]], {name: 'curl A, D'});
     *     var dir = board.create('slider', [[-3, 4], [3, 4], [-180, 0, 180]], {name: 'direction B'});
     *
     *     po.push(board.create('point', [-3, -3]));
     *     po.push(board.create('point', [0, -3]));
     *     po.push(board.create('point', [4, -5]));
     *     po.push(board.create('point', [6, -2]));
     *
     *     var controls = {
     *         tension: function() {return tension.Value(); },
     *         direction: { 1: function() {return dir.Value(); } },
     *         curl: { 0: function() {return curl.Value(); },
     *                 3: function() {return curl.Value(); }
     *             },
     *         isClosed: false
     *     };
     *
     *     // Plot a metapost curve
     *     var cu = board.create('metapostspline', [po, controls], {strokeColor: 'blue', strokeWidth: 2});
     *
     *
     * </pre><div id="JXGb8c6ffed-7419-41a3-9e55-3754b2327ae9" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGb8c6ffed-7419-41a3-9e55-3754b2327ae9',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *         var po = [],
     *             attr = {
     *                 size: 5,
     *                 color: 'red'
     *             },
     *             controls;
     *
     *         var tension = board.create('slider', [[-3, 6], [3, 6], [0, 1, 20]], {name: 'tension'});
     *         var curl = board.create('slider', [[-3, 5], [3, 5], [0, 1, 30]], {name: 'curl A, D'});
     *         var dir = board.create('slider', [[-3, 4], [3, 4], [-180, 0, 180]], {name: 'direction B'});
     *
     *         po.push(board.create('point', [-3, -3]));
     *         po.push(board.create('point', [0, -3]));
     *         po.push(board.create('point', [4, -5]));
     *         po.push(board.create('point', [6, -2]));
     *
     *         var controls = {
     *             tension: function() {return tension.Value(); },
     *             direction: { 1: function() {return dir.Value(); } },
     *             curl: { 0: function() {return curl.Value(); },
     *                     3: function() {return curl.Value(); }
     *                 },
     *             isClosed: false
     *         };
     *
     *         // Plot a metapost curve
     *         var cu = board.create('metapostspline', [po, controls], {strokeColor: 'blue', strokeWidth: 2});
     *
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createMetapostSpline = function (board, parents, attributes) {
        var el, getPointLike,
            points, controls,
            p, q, i, le,
            errStr = "\nPossible parent types: [points:array, controls:object";

        if (!Type.exists(parents[0]) || !Type.isArray(parents[0])) {
            throw new Error("JSXGraph: JXG.createMetapostSpline: argument 1 'points' has to be array of points or coordinate pairs" + errStr);
        }
        if (!Type.exists(parents[1]) || !Type.isObject(parents[1])) {
            throw new Error("JSXGraph: JXG.createMetapostSpline: argument 2 'controls' has to be a JavaScript object'" + errStr);
        }

        attributes = Type.copyAttributes(attributes, board.options, 'curve');
        attributes = Type.copyAttributes(attributes, board.options, 'metapostspline');
        attributes.curvetype = 'parameter';

        p = parents[0];
        q = [];

        // given as [x[], y[]]
        if (!attributes.isarrayofcoordinates &&
            p.length === 2 && Type.isArray(p[0]) && Type.isArray(p[1]) &&
            p[0].length === p[1].length) {
            for (i = 0; i < p[0].length; i++) {
                q[i] = [];
                if (Type.isFunction(p[0][i])) {
                    q[i].push(p[0][i]());
                } else {
                    q[i].push(p[0][i]);
                }

                if (Type.isFunction(p[1][i])) {
                    q[i].push(p[1][i]());
                } else {
                    q[i].push(p[1][i]);
                }
            }
        } else {
            // given as [[x0, y0], [x1, y1], point, ...]
            for (i = 0; i < p.length; i++) {
                if (Type.isString(p[i])) {
                    q.push(board.select(p[i]));
                } else if (Type.isPoint(p[i])) {
                    q.push(p[i]);
                    // given as [[x0,y0], [x1, y2], ...]
                } else if (Type.isArray(p[i]) && p[i].length === 2) {
                    q[i] = [];
                    if (Type.isFunction(p[i][0])) {
                        q[i].push(p[i][0]());
                    } else {
                        q[i].push(p[i][0]);
                    }

                    if (Type.isFunction(p[i][1])) {
                        q[i].push(p[i][1]());
                    } else {
                        q[i].push(p[i][1]);
                    }
                } else if (Type.isFunction(p[i]) && p[i]().length === 2) {
                    q.push(parents[i]());
                }
            }
        }

        if (attributes.createpoints === true) {
            points = Type.providePoints(board, q, attributes, 'metapostspline', ['points']);
        } else {
            points = [];

            /**
             * @ignore
             */
            getPointLike = function (ii) {
                return {
                    X: function () { return q[ii][0]; },
                    Y: function () { return q[ii][1]; }
                };
            };

            for (i = 0; i < q.length; i++) {
                if (Type.isPoint(q[i])) {
                    points.push(q[i]);
                } else {
                    points.push(getPointLike);
                }
            }
        }

        controls = parents[1];

        el = new JXG.Curve(board, ['t', [], [], 0, p.length - 1], attributes);
        el.updateDataArray = function () {
            var res, i,
                len = points.length,
                p = [];

            for (i = 0; i < len; i++) {
                p.push([points[i].X(), points[i].Y()]);
            }

            res = JXG.Math.Metapost.curve(p, controls);
            this.dataX = res[0];
            this.dataY = res[1];
        };
        el.bezierDegree = 3;

        le = points.length;
        el.setParents(points);
        for (i = 0; i < le; i++) {
            if (Type.isPoint(points[i])) {
                points[i].addChild(el);
            }
        }
        el.elType = 'metapostspline';

        return el;
    };

    JXG.registerElement('metapostspline', JXG.createMetapostSpline);

    /**
     * @class This element is used to provide a constructor for Riemann sums, which is realized as a special curve.
     * The returned element has the method Value() which returns the sum of the areas of the bars.
     * @pseudo
     * @description
     * @name Riemannsum
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     * @param {function,array_number,function_string,function_function,number_function,number} f,n,type_,a_,b_ Parent elements of Riemannsum are a
     *         Either a function term f(x) describing the function graph which is filled by the Riemann bars, or
     *         an array consisting of two functions and the area between is filled by the Riemann bars.
     *         <p>
     *         n determines the number of bars, it is either a fixed number or a function.
     *         <p>
     *         type is a string or function returning one of the values:  'left', 'right', 'middle', 'lower', 'upper', 'random', 'simpson', or 'trapezoidal'.
     *         Default value is 'left'.
     *         <p>
     *         Further parameters are an optional number or function for the left interval border a,
     *         and an optional number or function for the right interval border b.
     *         <p>
     *         Default values are a=-10 and b=10.
     * @see JXG.Curve
     * @example
     * // Create Riemann sums for f(x) = 0.5*x*x-2*x.
     *   var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1});
     *   var f = function(x) { return 0.5*x*x-2*x; };
     *   var r = board.create('riemannsum',
     *               [f, function(){return s.Value();}, 'upper', -2, 5],
     *               {fillOpacity:0.4}
     *               );
     *   var g = board.create('functiongraph',[f, -2, 5]);
     *   var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]);
     * </pre><div class="jxgbox" id="JXG940f40cc-2015-420d-9191-c5d83de988cf" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function(){
     *   var board = JXG.JSXGraph.initBoard('JXG940f40cc-2015-420d-9191-c5d83de988cf', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     *   var f = function(x) { return 0.5*x*x-2*x; };
     *   var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1});
     *   var r = board.create('riemannsum', [f, function(){return s.Value();}, 'upper', -2, 5], {fillOpacity:0.4});
     *   var g = board.create('functiongraph', [f, -2, 5]);
     *   var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]);
     * })();
     * </script><pre>
     *
     * @example
     *   // Riemann sum between two functions
     *   var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1});
     *   var g = function(x) { return 0.5*x*x-2*x; };
     *   var f = function(x) { return -x*(x-4); };
     *   var r = board.create('riemannsum',
     *               [[g,f], function(){return s.Value();}, 'lower', 0, 4],
     *               {fillOpacity:0.4}
     *               );
     *   var f = board.create('functiongraph',[f, -2, 5]);
     *   var g = board.create('functiongraph',[g, -2, 5]);
     *   var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]);
     * </pre><div class="jxgbox" id="JXGf9a7ba38-b50f-4a32-a873-2f3bf9caee79" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function(){
     *   var board = JXG.JSXGraph.initBoard('JXGf9a7ba38-b50f-4a32-a873-2f3bf9caee79', {boundingbox: [-3, 7, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     *   var s = board.create('slider',[[0,4],[3,4],[0,4,10]],{snapWidth:1});
     *   var g = function(x) { return 0.5*x*x-2*x; };
     *   var f = function(x) { return -x*(x-4); };
     *   var r = board.create('riemannsum',
     *               [[g,f], function(){return s.Value();}, 'lower', 0, 4],
     *               {fillOpacity:0.4}
     *               );
     *   var f = board.create('functiongraph',[f, -2, 5]);
     *   var g = board.create('functiongraph',[g, -2, 5]);
     *   var t = board.create('text',[-2,-2, function(){ return 'Sum=' + JXG.toFixed(r.Value(), 4); }]);
     * })();
     * </script><pre>
     */
    JXG.createRiemannsum = function (board, parents, attributes) {
        var n, type, f, par, c, attr;

        attr = Type.copyAttributes(attributes, board.options, 'riemannsum');
        attr.curvetype = 'plot';

        f = parents[0];
        n = Type.createFunction(parents[1], board, '');

        if (!Type.exists(n)) {
            throw new Error("JSXGraph: JXG.createRiemannsum: argument '2' n has to be number or function." +
                "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]");
        }

        type = Type.createFunction(parents[2], board, '', false);
        if (!Type.exists(type)) {
            throw new Error("JSXGraph: JXG.createRiemannsum: argument 3 'type' has to be string or function." +
                "\nPossible parent types: [function,n:number|function,type,start:number|function,end:number|function]");
        }

        par = [[0], [0]].concat(parents.slice(3));

        c = board.create('curve', par, attr);

        c.sum = 0.0;
        /**
         * Returns the value of the Riemann sum, i.e. the sum of the (signed) areas of the rectangles.
         * @name Value
         * @memberOf Riemann.prototype
         * @function
         * @returns {Number} value of Riemann sum.
         */
        c.Value = function () {
            return this.sum;
        };

        /**
         * @ignore
         */
        c.updateDataArray = function () {
            var u = Numerics.riemann(f, n(), type(), this.minX(), this.maxX());
            this.dataX = u[0];
            this.dataY = u[1];

            // Update "Riemann sum"
            this.sum = u[2];
        };

        return c;
    };

    JXG.registerElement('riemannsum', JXG.createRiemannsum);

    /**
     * @class This element is used to provide a constructor for trace curve (simple locus curve), which is realized as a special curve.
     * @pseudo
     * @description
     * @name Tracecurve
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     * @param {Point,Point} Parent elements of Tracecurve are a
     *         glider point and a point whose locus is traced.
     * @see JXG.Curve
     * @example
     * // Create trace curve.
     * var c1 = board.create('circle',[[0, 0], [2, 0]]),
     * p1 = board.create('point',[-3, 1]),
     * g1 = board.create('glider',[2, 1, c1]),
     * s1 = board.create('segment',[g1, p1]),
     * p2 = board.create('midpoint',[s1]),
     * curve = board.create('tracecurve', [g1, p2]);
     *
     * </pre><div class="jxgbox" id="JXG5749fb7d-04fc-44d2-973e-45c1951e29ad" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var tc1_board = JXG.JSXGraph.initBoard('JXG5749fb7d-04fc-44d2-973e-45c1951e29ad', {boundingbox: [-4, 4, 4, -4], axis: false, showcopyright: false, shownavigation: false});
     *   var c1 = tc1_board.create('circle',[[0, 0], [2, 0]]),
     *       p1 = tc1_board.create('point',[-3, 1]),
     *       g1 = tc1_board.create('glider',[2, 1, c1]),
     *       s1 = tc1_board.create('segment',[g1, p1]),
     *       p2 = tc1_board.create('midpoint',[s1]),
     *       curve = tc1_board.create('tracecurve', [g1, p2]);
     * </script><pre>
     */
    JXG.createTracecurve = function (board, parents, attributes) {
        var c, glider, tracepoint, attr;

        if (parents.length !== 2) {
            throw new Error("JSXGraph: Can't create trace curve with given parent'" +
                "\nPossible parent types: [glider, point]");
        }

        glider = board.select(parents[0]);
        tracepoint = board.select(parents[1]);

        if (glider.type !== Const.OBJECT_TYPE_GLIDER || !Type.isPoint(tracepoint)) {
            throw new Error("JSXGraph: Can't create trace curve with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [glider, point]");
        }

        attr = Type.copyAttributes(attributes, board.options, 'tracecurve');
        attr.curvetype = 'plot';
        c = board.create('curve', [[0], [0]], attr);

        /**
         * @ignore
         */
         c.updateDataArray = function () {
            var i, step, t, el, pEl, x, y, from, savetrace,
                le = attr.numberpoints,
                savePos = glider.position,
                slideObj = glider.slideObject,
                mi = slideObj.minX(),
                ma = slideObj.maxX();

            // set step width
            step = (ma - mi) / le;
            this.dataX = [];
            this.dataY = [];

            /*
             * For gliders on circles and lines a closed curve is computed.
             * For gliders on curves the curve is not closed.
             */
            if (slideObj.elementClass !== Const.OBJECT_CLASS_CURVE) {
                le++;
            }

            // Loop over all steps
            for (i = 0; i < le; i++) {
                t = mi + i * step;
                x = slideObj.X(t) / slideObj.Z(t);
                y = slideObj.Y(t) / slideObj.Z(t);

                // Position the glider
                glider.setPositionDirectly(Const.COORDS_BY_USER, [x, y]);
                from = false;

                // Update all elements from the glider up to the trace element
                for (el in this.board.objects) {
                    if (this.board.objects.hasOwnProperty(el)) {
                        pEl = this.board.objects[el];

                        if (pEl === glider) {
                            from = true;
                        }

                        if (from && pEl.needsRegularUpdate) {
                            // Save the trace mode of the element
                            savetrace = pEl.visProp.trace;
                            pEl.visProp.trace = false;
                            pEl.needsUpdate = true;
                            pEl.update(true);

                            // Restore the trace mode
                            pEl.visProp.trace = savetrace;
                            if (pEl === tracepoint) {
                                break;
                            }
                        }
                    }
                }

                // Store the position of the trace point
                this.dataX[i] = tracepoint.X();
                this.dataY[i] = tracepoint.Y();
            }

            // Restore the original position of the glider
            glider.position = savePos;
            from = false;

            // Update all elements from the glider to the trace point
            for (el in this.board.objects) {
                if (this.board.objects.hasOwnProperty(el)) {
                    pEl = this.board.objects[el];
                    if (pEl === glider) {
                        from = true;
                    }

                    if (from && pEl.needsRegularUpdate) {
                        savetrace = pEl.visProp.trace;
                        pEl.visProp.trace = false;
                        pEl.needsUpdate = true;
                        pEl.update(true);
                        pEl.visProp.trace = savetrace;

                        if (pEl === tracepoint) {
                            break;
                        }
                    }
                }
            }
        };

        return c;
    };

    JXG.registerElement('tracecurve', JXG.createTracecurve);

    /**
     * @class This element is used to provide a constructor for step function, which is realized as a special curve.
     *
     * In case the data points should be updated after creation time, they can be accessed by curve.xterm and curve.yterm.
     * @pseudo
     * @description
     * @name Stepfunction
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     * @param {Array,Array|Function} Parent elements of Stepfunction are two arrays containing the coordinates.
     * @see JXG.Curve
     * @example
     * // Create step function.
     var curve = board.create('stepfunction', [[0,1,2,3,4,5], [1,3,0,2,2,1]]);

     * </pre><div class="jxgbox" id="JXG32342ec9-ad17-4339-8a97-ff23dc34f51a" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var sf1_board = JXG.JSXGraph.initBoard('JXG32342ec9-ad17-4339-8a97-ff23dc34f51a', {boundingbox: [-1, 5, 6, -2], axis: true, showcopyright: false, shownavigation: false});
     *   var curve = sf1_board.create('stepfunction', [[0,1,2,3,4,5], [1,3,0,2,2,1]]);
     * </script><pre>
     */
    JXG.createStepfunction = function (board, parents, attributes) {
        var c, attr;
        if (parents.length !== 2) {
            throw new Error("JSXGraph: Can't create step function with given parent'" +
                "\nPossible parent types: [array, array|function]");
        }

        attr = Type.copyAttributes(attributes, board.options, 'stepfunction');
        c = board.create('curve', parents, attr);
        /**
         * @ignore
         */
         c.updateDataArray = function () {
            var i, j = 0,
                len = this.xterm.length;

            this.dataX = [];
            this.dataY = [];

            if (len === 0) {
                return;
            }

            this.dataX[j] = this.xterm[0];
            this.dataY[j] = this.yterm[0];
            ++j;

            for (i = 1; i < len; ++i) {
                this.dataX[j] = this.xterm[i];
                this.dataY[j] = this.dataY[j - 1];
                ++j;
                this.dataX[j] = this.xterm[i];
                this.dataY[j] = this.yterm[i];
                ++j;
            }
        };

        return c;
    };

    JXG.registerElement('stepfunction', JXG.createStepfunction);

    /**
     * @class This element is used to provide a constructor for the graph showing
     * the (numerical) derivative of a given curve.
     *
     * @pseudo
     * @description
     * @name Derivative
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     * @param {JXG.Curve} Parent Curve for which the derivative is generated.
     * @see JXG.Curve
     * @example
     * var cu = board.create('cardinalspline', [[[-3,0], [-1,2], [0,1], [2,0], [3,1]], 0.5, 'centripetal'], {createPoints: false});
     * var d = board.create('derivative', [cu], {dash: 2});
     *
     * </pre><div id="JXGb9600738-1656-11e8-8184-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGb9600738-1656-11e8-8184-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var cu = board.create('cardinalspline', [[[-3,0], [-1,2], [0,1], [2,0], [3,1]], 0.5, 'centripetal'], {createPoints: false});
     *     var d = board.create('derivative', [cu], {dash: 2});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createDerivative = function (board, parents, attributes) {
        var c,
            curve, dx, dy,
            attr;

        if (parents.length !== 1 && parents[0].class !== Const.OBJECT_CLASS_CURVE) {
            throw new Error("JSXGraph: Can't create derivative curve with given parent'" +
                "\nPossible parent types: [curve]");
        }

        attr = Type.copyAttributes(attributes, board.options, 'curve');

        curve = parents[0];
        dx = Numerics.D(curve.X);
        dy = Numerics.D(curve.Y);

        c = board.create('curve', [
            function (t) { return curve.X(t); },
            function (t) { return dy(t) / dx(t); },
            curve.minX(), curve.maxX()
        ], attr);

        c.setParents(curve);

        return c;
    };

    JXG.registerElement('derivative', JXG.createDerivative);

    /**
     * @class Intersection of two closed path elements. The elements may be of type curve, circle, polygon, inequality.
     * If one element is a curve, it has to be closed.
     * The resulting element is of type curve.
     * @pseudo
     * @description
     * @name CurveIntersection
     * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve1 First element which is intersected
     * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve2 Second element which is intersected
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     *
     * @example
     * var f = board.create('functiongraph', ['cos(x)']);
     * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1});
     * var circ = board.create('circle', [[0,0], 4]);
     * var clip = board.create('curveintersection', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6});
     *
     * </pre><div id="JXGe2948257-8835-4276-9164-8acccb48e8d4" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGe2948257-8835-4276-9164-8acccb48e8d4',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var f = board.create('functiongraph', ['cos(x)']);
     *     var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1});
     *     var circ = board.create('circle', [[0,0], 4]);
     *     var clip = board.create('curveintersection', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createCurveIntersection = function (board, parents, attributes) {
        var c;

        if (parents.length !== 2) {
            throw new Error("JSXGraph: Can't create curve intersection with given parent'" +
                "\nPossible parent types: [array, array|function]");
        }

        c = board.create('curve', [[], []], attributes);
        /**
         * @ignore
         */
         c.updateDataArray = function () {
            var a = JXG.Math.Clip.intersection(parents[0], parents[1], this.board);
            this.dataX = a[0];
            this.dataY = a[1];
        };
        return c;
    };

    /**
     * @class Union of two closed path elements. The elements may be of type curve, circle, polygon, inequality.
     * If one element is a curve, it has to be closed.
     * The resulting element is of type curve.
     * @pseudo
     * @description
     * @name CurveUnion
     * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve1 First element defining the union
     * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve2 Second element defining the union
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     *
     * @example
     * var f = board.create('functiongraph', ['cos(x)']);
     * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1});
     * var circ = board.create('circle', [[0,0], 4]);
     * var clip = board.create('curveunion', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6});
     *
     * </pre><div id="JXGe2948257-8835-4276-9164-8acccb48e8d4" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGe2948257-8835-4276-9164-8acccb48e8d4',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var f = board.create('functiongraph', ['cos(x)']);
     *     var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1});
     *     var circ = board.create('circle', [[0,0], 4]);
     *     var clip = board.create('curveunion', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createCurveUnion = function (board, parents, attributes) {
        var c;

        if (parents.length !== 2) {
            throw new Error("JSXGraph: Can't create curve union with given parent'" +
                "\nPossible parent types: [array, array|function]");
        }

        c = board.create('curve', [[], []], attributes);
        /**
         * @ignore
         */
         c.updateDataArray = function () {
            var a = JXG.Math.Clip.union(parents[0], parents[1], this.board);
            this.dataX = a[0];
            this.dataY = a[1];
        };
        return c;
    };

    /**
     * @class Difference of two closed path elements. The elements may be of type curve, circle, polygon, inequality.
     * If one element is a curve, it has to be closed.
     * The resulting element is of type curve.
     * @pseudo
     * @description
     * @name CurveDifference
     * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve1 First element from which the second element is "subtracted"
     * @param {JXG.Curve|JXG.Polygon|JXG.Circle} curve2 Second element which is subtracted from the first element
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     *
     * @example
     * var f = board.create('functiongraph', ['cos(x)']);
     * var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1});
     * var circ = board.create('circle', [[0,0], 4]);
     * var clip = board.create('curvedifference', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6});
     *
     * </pre><div id="JXGe2948257-8835-4276-9164-8acccb48e8d4" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGe2948257-8835-4276-9164-8acccb48e8d4',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var f = board.create('functiongraph', ['cos(x)']);
     *     var ineq = board.create('inequality', [f], {inverse: true, fillOpacity: 0.1});
     *     var circ = board.create('circle', [[0,0], 4]);
     *     var clip = board.create('curvedifference', [ineq, circ], {fillColor: 'yellow', fillOpacity: 0.6});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createCurveDifference = function (board, parents, attributes) {
        var c;

        if (parents.length !== 2) {
            throw new Error("JSXGraph: Can't create curve difference with given parent'" +
                "\nPossible parent types: [array, array|function]");
        }

        c = board.create('curve', [[], []], attributes);
        /**
         * @ignore
         */
         c.updateDataArray = function () {
            var a = JXG.Math.Clip.difference(parents[0], parents[1], this.board);
            this.dataX = a[0];
            this.dataY = a[1];
        };
        return c;
    };

    JXG.registerElement('curvedifference', JXG.createCurveDifference);
    JXG.registerElement('curveintersection', JXG.createCurveIntersection);
    JXG.registerElement('curveunion', JXG.createCurveUnion);

    /**
     * @class Box plot curve. The direction of the box plot can be either vertical or horizontal which
     * is controlled by the attribute "dir".
     * @pseudo
     * @description
     * @name Boxplot
     * @param {Array} quantiles Array conatining at least five quantiles. The elements can be of type number, function or string.
     * @param {Number|Function} axis Axis position of the box plot
     * @param {Number|Function} width Width of the rectangle part of the box plot. The width of the first and 4th quantile
     * is relative to this width and can be controlled by the attribute "smallWidth".
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     *
     * @example
     * var Q = [ -1, 2, 3, 3.5, 5 ];
     *
     * var b = board.create('boxplot', [Q, 2, 4], {strokeWidth: 3});
     *
     * </pre><div id="JXG13eb23a1-a641-41a2-be11-8e03e400a947" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG13eb23a1-a641-41a2-be11-8e03e400a947',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var Q = [ -1, 2, 3, 3.5, 5 ];
     *     var b = board.create('boxplot', [Q, 2, 4], {strokeWidth: 3});
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * var Q = [ -1, 2, 3, 3.5, 5 ];
     * var b = board.create('boxplot', [Q, 3, 4], {dir: 'horizontal', smallWidth: 0.25, color:'red'});
     *
     * </pre><div id="JXG0deb9cb2-84bc-470d-a6db-8be9a5694813" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG0deb9cb2-84bc-470d-a6db-8be9a5694813',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var Q = [ -1, 2, 3, 3.5, 5 ];
     *     var b = board.create('boxplot', [Q, 3, 4], {dir: 'horizontal', smallWidth: 0.25, color:'red'});
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * var data = [57, 57, 57, 58, 63, 66, 66, 67, 67, 68, 69, 70, 70, 70, 70, 72, 73, 75, 75, 76, 76, 78, 79, 81];
     * var Q = [];
     *
     * Q[0] = JXG.Math.Statistics.min(data);
     * Q = Q.concat(JXG.Math.Statistics.percentile(data, [25, 50, 75]));
     * Q[4] = JXG.Math.Statistics.max(data);
     *
     * var b = board.create('boxplot', [Q, 0, 3]);
     *
     * </pre><div id="JXGef079e76-ae99-41e4-af29-1d07d83bf85a" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGef079e76-ae99-41e4-af29-1d07d83bf85a',
     *             {boundingbox: [-5,90,5,30], axis: true, showcopyright: false, shownavigation: false});
     *     var data = [57, 57, 57, 58, 63, 66, 66, 67, 67, 68, 69, 70, 70, 70, 70, 72, 73, 75, 75, 76, 76, 78, 79, 81];
     *     var Q = [];
     *
     *     Q[0] = JXG.Math.Statistics.min(data);
     *     Q = Q.concat(JXG.Math.Statistics.percentile(data, [25, 50, 75]));
     *     Q[4] = JXG.Math.Statistics.max(data);
     *
     *     var b = board.create('boxplot', [Q, 0, 3]);
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * var mi = board.create('glider', [0, -1, board.defaultAxes.y]);
     * var ma = board.create('glider', [0, 5, board.defaultAxes.y]);
     * var Q = [function() { return mi.Y(); }, 2, 3, 3.5, function() { return ma.Y(); }];
     *
     * var b = board.create('boxplot', [Q, 0, 2]);
     *
     * </pre><div id="JXG3b3225da-52f0-42fe-8396-be9016bf289b" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG3b3225da-52f0-42fe-8396-be9016bf289b',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var mi = board.create('glider', [0, -1, board.defaultAxes.y]);
     *     var ma = board.create('glider', [0, 5, board.defaultAxes.y]);
     *     var Q = [function() { return mi.Y(); }, 2, 3, 3.5, function() { return ma.Y(); }];
     *
     *     var b = board.create('boxplot', [Q, 0, 2]);
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createBoxPlot = function (board, parents, attributes) {
        var box, i, len, w2,
            attr = Type.copyAttributes(attributes, board.options, 'boxplot');

        if (parents.length !== 3) {
            throw new Error("JSXGraph: Can't create box plot with given parent'" +
                "\nPossible parent types: [array, number|function, number|function] containing quantiles, axis, width");
        }
        if (parents[0].length < 5) {
            throw new Error("JSXGraph: Can't create box plot with given parent[0]'" +
                "\nparent[0] has to conatin at least 5 quantiles.");
        }
        box = board.create('curve', [[],[]], attr);

        len = parents[0].length; // Quantiles
        box.Q = [];
        for (i = 0; i < len; i++) {
            box.Q[i] = Type.createFunction(parents[0][i], board, null, true);
        }
        box.x = Type.createFunction(parents[1], board, null, true);
        box.w = Type.createFunction(parents[2], board, null, true);

        box.updateDataArray = function() {
            var v1, v2, l1, l2, r1, r2, w2, dir, x;

            w2  = Type.evaluate(this.visProp.smallwidth);
            dir = Type.evaluate(this.visProp.dir);
            x = this.x();
            l1 = x - this.w() * 0.5;
            l2 = x - this.w() * 0.5 * w2;
            r1 = x + this.w() * 0.5;
            r2 = x + this.w() * 0.5 * w2;
            v1 = [x, l2, r2, x, x, l1, l1, r1, r1, x, NaN, l1, r1, NaN, x, x, l2, r2, x];
            v2 = [this.Q[0](),
                    this.Q[0](),
                    this.Q[0](),
                    this.Q[0](),
                    this.Q[1](),
                    this.Q[1](),
                    this.Q[3](),
                    this.Q[3](),
                    this.Q[1](),
                    this.Q[1](),
                    NaN,
                    this.Q[2](),
                    this.Q[2](),
                    NaN,
                    this.Q[3](),
                    this.Q[4](),
                    this.Q[4](),
                    this.Q[4](),
                    this.Q[4]()];
            if (dir === 'vertical') {
                this.dataX = v1;
                this.dataY = v2;
            } else {
                this.dataX = v2;
                this.dataY = v1;
            }
        };
        return box;
    };

    JXG.registerElement('boxplot', JXG.createBoxPlot);

    return {
        Curve: JXG.Curve,
        createCardinalSpline: JXG.createCardinalSpline,
        createCurve: JXG.createCurve,
        createCurveDifference: JXG.createCurveDifference,
        createCurveIntersection: JXG.createCurveIntersection,
        createCurveUnion: JXG.createCurveUnion,
        createDerivative: JXG.createDerivative,
        createFunctiongraph: JXG.createFunctiongraph,
        createMetapostSpline: JXG.createMetapostSpline,
        createPlot: JXG.createFunctiongraph,
        createSpline: JXG.createSpline,
        createRiemannsum: JXG.createRiemannsum,
        createStepfunction: JXG.createStepfunction,
        createTracecurve: JXG.createTracecurve
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/geometry
 math/math
 base/coords
 base/circle
 utils/type
 base/constants
  elements:
   curve
   midpoint
   circumcenter
 */

/**
 * @fileoverview In this file the geometry object Arc is defined. Arc stores all
 * style and functional properties that are required to draw an arc on a board.
 */

define('element/arc',[
    'jxg', 'math/geometry', 'math/math', 'base/coords', 'base/circle', 'utils/type', 'base/constants'
], function (JXG, Geometry, Mat, Coords, Circle, Type, Const) {

    "use strict";

    /**
     * @class An arc is a segment of the circumference of a circle. It is defined by a center, one point that
     * defines the radius, and a third point that defines the angle of the arc.
     *
     * @pseudo
     * @name Arc
     * @augments Curve
     * @constructor
     * @type JXG.Curve
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The result will be an arc of a circle around p1 through p2. The arc is drawn
     * counter-clockwise from p2 to p3.
     * @example
     * // Create an arc out of three free points
     * var p1 = board.create('point', [2.0, 2.0]);
     * var p2 = board.create('point', [1.0, 0.5]);
     * var p3 = board.create('point', [3.5, 1.0]);
     *
     * var a = board.create('arc', [p1, p2, p3]);
     * board.create('text',[1,6,function(){return 'arclength: '+Math.round(a.Value()*100)/100}])
     * </pre><div class="jxgbox" id="JXG114ef584-4a5e-4686-8392-c97501befb5b" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG114ef584-4a5e-4686-8392-c97501befb5b', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *       p1 = board.create('point', [2.0, 2.0]),
     *       p2 = board.create('point', [1.0, 0.5]),
     *       p3 = board.create('point', [3.5, 1.0]),
     *
     *       a = board.create('arc', [p1, p2, p3]);
     *       board.create('text',[1,6,function(){return 'arclength: '+Math.round(a.Value()*100)/100}])
     * })();
     * </script><pre>
     *
     * @example
     * var t = board.create('transform', [2, 1.5], {type: 'scale'});
     * var a1 = board.create('arc', [[1, 1], [0, 1], [1, 0]], {strokeColor: 'red'});
     * var a2 = board.create('curve', [a1, t], {strokeColor: 'red'});
     *
     * </pre><div id="JXG1949da46-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG1949da46-6339-11e8-9fb9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
     *     var a1 = board.create('arc', [[1, 1], [0, 1], [1, 0]], {strokeColor: 'red'});
     *     var a2 = board.create('curve', [a1, t], {strokeColor: 'red'});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createArc = function (board, parents, attributes) {
        var el, attr, points;

        points = Type.providePoints(board, parents, attributes, 'arc', ['center', 'radiusPoint', 'anglePoint']);
        if (points === false || points.length < 3) {
            throw new Error("JSXGraph: Can't create Arc with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" +
                (typeof parents[2]) + "'." +
                "\nPossible parent types: [point,point,point], [arc, transformation]");
        }

        attr = Type.copyAttributes(attributes, board.options, 'arc');
        el = board.create('curve', [[0], [0]], attr);

        el.elType = 'arc';
        el.setParents(points);

        /**
         * documented in JXG.GeometryElement
         * @ignore
         */
        el.type = Const.OBJECT_TYPE_ARC;

        /**
         * Center of the arc.
         * @memberOf Arc.prototype
         * @name center
         * @type JXG.Point
         */
        el.center = points[0];

        /**
         * Point defining the arc's radius.
         * @memberOf Arc.prototype
         * @name radiuspoint
         * @type JXG.Point
         */
        el.radiuspoint = points[1];
        el.point2 = el.radiuspoint;

        /**
         * The point defining the arc's angle.
         * @memberOf Arc.prototype
         * @name anglepoint
         * @type JXG.Point
         */
        el.anglepoint = points[2];
        el.point3 = el.anglepoint;

        // Add arc as child to defining points
        // or vice versa if the points are provided as coordinates
        if (Type.exists(el.center._is_new)) {
            el.addChild(el.center);
            delete el.center._is_new;
        } else {
            el.center.addChild(el);
        }
        if (Type.exists(el.radiuspoint._is_new)) {
            el.addChild(el.radiuspoint);
            delete el.radiuspoint._is_new;
        } else {
            el.radiuspoint.addChild(el);
        }
        if (Type.exists(el.anglepoint._is_new)) {
            el.addChild(el.anglepoint);
            delete el.anglepoint._is_new;
        } else {
            el.anglepoint.addChild(el);
        }

        // should be documented in options
        el.useDirection = attr.usedirection;

        // documented in JXG.Curve
        el.updateDataArray = function () {
            var ar, phi, det, p0c, p1c, p2c,
                sgn = 1,
                A = this.radiuspoint,
                B = this.center,
                C = this.anglepoint,
                ev_s = Type.evaluate(this.visProp.selection);

            phi = Geometry.rad(A, B, C);
            if ((ev_s === 'minor' && phi > Math.PI) ||
                    (ev_s === 'major' && phi < Math.PI)) {
                sgn = -1;
            }

            // This is true for circumCircleArcs. In that case there is
            // a fourth parent element: [center, point1, point3, point2]
            if (this.useDirection) {
                p0c = points[1].coords.usrCoords;
                p1c = points[3].coords.usrCoords;
                p2c = points[2].coords.usrCoords;
                det = (p0c[1] - p2c[1]) * (p0c[2] - p1c[2]) - (p0c[2] - p2c[2]) * (p0c[1] - p1c[1]);

                if (det < 0) {
                    this.radiuspoint = points[1];
                    this.anglepoint = points[2];
                } else {
                    this.radiuspoint = points[2];
                    this.anglepoint = points[1];
                }
            }

            A = A.coords.usrCoords;
            B = B.coords.usrCoords;
            C = C.coords.usrCoords;

            ar = Geometry.bezierArc(A, B, C, false, sgn);

            this.dataX = ar[0];
            this.dataY = ar[1];

            this.bezierDegree = 3;

            this.updateStdform();
            this.updateQuadraticform();
        };

        /**
         * Determines the arc's current radius. I.e. the distance between {@link Arc#center} and {@link Arc#radiuspoint}.
         * @memberOf Arc.prototype
         * @name Radius
         * @function
         * @returns {Number} The arc's radius
         */
        el.Radius = function () {
            return this.radiuspoint.Dist(this.center);
        };

        /**
         * @deprecated Use {@link Arc#Radius}
         * @memberOf Arc.prototype
         * @name getRadius
         * @function
         * @returns {Number}
         */
        el.getRadius = function () {
            JXG.deprecated('Arc.getRadius()', 'Arc.Radius()');
            return this.Radius();
        };

        /**
         * Returns the length of the arc.
         * @memberOf Arc.prototype
         * @name Value
         * @function
         * @returns {Number} The arc length
         */
        el.Value = function () {
            return this.Radius() * Geometry.rad(this.radiuspoint, this.center, this.anglepoint);
        };

        // documented in geometry element
        el.hasPoint = function (x, y) {
            var dist, checkPoint,
                has,
                invMat, c,
                prec, type,
                r = this.Radius();

            if (Type.evaluate(this.visProp.hasinnerpoints)) {
                return this.hasPointSector(x, y);
            }

            if (Type.isObject(Type.evaluate(this.visProp.precision))) {
                type = this.board._inputDevice;
                prec = Type.evaluate(this.visProp.precision[type]);
            } else {
                // 'inherit'
                prec = this.board.options.precision.hasPoint;
            }
            prec /= Math.min(this.board.unitX, this.board.unitY);
            checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);

            if (this.transformations.length > 0) {
                // Transform the mouse/touch coordinates
                // back to the original position of the curve.
                this.updateTransformMatrix();
                invMat = Mat.inverse(this.transformMat);
                c = Mat.matVecMult(invMat, checkPoint.usrCoords);
                checkPoint = new Coords(Const.COORDS_BY_USER, c, this.board);
            }

            dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint);
            has = (Math.abs(dist - r) < prec);

            /**
             * At that point we know that the user has touched the circle line.
             * Now, we have to check, if the user has hit the arc path.
             */
            if (has) {
                has = Geometry.coordsOnArc(this, checkPoint);
            }
            return has;
        };

        /**
         * Checks whether (x,y) is within the sector defined by the arc.
         * @memberOf Arc.prototype
         * @name hasPointSector
         * @function
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise.
         */
        el.hasPointSector = function (x, y) {
            var checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
                r = this.Radius(),
                dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint),
                has = (dist < r);

            if (has) {
                has = Geometry.coordsOnArc(this, checkPoint);
            }
            return has;
        };

        // documented in geometry element
        el.getTextAnchor = function () {
            return this.center.coords;
        };

        // documented in geometry element
        el.getLabelAnchor = function () {
            var coords, vec, vecx, vecy, len,
                angle = Geometry.rad(this.radiuspoint, this.center, this.anglepoint),
                dx = 10 / this.board.unitX,
                dy = 10 / this.board.unitY,
                p2c = this.point2.coords.usrCoords,
                pmc = this.center.coords.usrCoords,
                bxminusax = p2c[1] - pmc[1],
                byminusay = p2c[2] - pmc[2],
                ev_s = Type.evaluate(this.visProp.selection),
                l_vp = this.label ? this.label.visProp : this.visProp.label;

            // If this is uncommented, the angle label can not be dragged
            //if (Type.exists(this.label)) {
            //    this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
            //}

            if ((ev_s === 'minor' && angle > Math.PI) ||
                    (ev_s === 'major' && angle < Math.PI)) {
                angle = -(2 * Math.PI - angle);
            }

            coords = new Coords(Const.COORDS_BY_USER, [
                pmc[1] + Math.cos(angle * 0.5) * bxminusax - Math.sin(angle * 0.5) * byminusay,
                pmc[2] + Math.sin(angle * 0.5) * bxminusax + Math.cos(angle * 0.5) * byminusay
            ], this.board);

            vecx = coords.usrCoords[1] - pmc[1];
            vecy = coords.usrCoords[2] - pmc[2];

            len = Math.sqrt(vecx * vecx + vecy * vecy);
            vecx = vecx * (len + dx) / len;
            vecy = vecy * (len + dy) / len;
            vec = [pmc[1] + vecx, pmc[2] + vecy];

            l_vp.position = Geometry.calcLabelQuadrant(Geometry.rad([1,0],[0,0],vec));

            return new Coords(Const.COORDS_BY_USER, vec, this.board);
        };

        // documentation in jxg.circle
        el.updateQuadraticform = Circle.Circle.prototype.updateQuadraticform;

        // documentation in jxg.circle
        el.updateStdform = Circle.Circle.prototype.updateStdform;

        el.methodMap = JXG.deepCopy(el.methodMap, {
            getRadius: 'getRadius',
            radius: 'Radius',
            center: 'center',
            radiuspoint: 'radiuspoint',
            anglepoint: 'anglepoint',
            Value: 'Value'
        });

        el.prepareUpdate().update();
        return el;
    };

    JXG.registerElement('arc', JXG.createArc);

    /**
     * @class A semicircle is a special arc defined by two points. The arc hits both points.
     * @pseudo
     * @name Semicircle
     * @augments Arc
     * @constructor
     * @type Arc
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point} p1,p2 The result will be a composition of an arc drawn clockwise from <tt>p1</tt> and
     * <tt>p2</tt> and the midpoint of <tt>p1</tt> and <tt>p2</tt>.
     * @example
     * // Create an arc out of three free points
     * var p1 = board.create('point', [4.5, 2.0]);
     * var p2 = board.create('point', [1.0, 0.5]);
     *
     * var a = board.create('semicircle', [p1, p2]);
     * </pre><div class="jxgbox" id="JXG5385d349-75d7-4078-b732-9ae808db1b0e" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG5385d349-75d7-4078-b732-9ae808db1b0e', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *       p1 = board.create('point', [4.5, 2.0]),
     *       p2 = board.create('point', [1.0, 0.5]),
     *
     *       sc = board.create('semicircle', [p1, p2]);
     * })();
     * </script><pre>
     */
    JXG.createSemicircle = function (board, parents, attributes) {
        var el, mp, attr, points;

        // we need 2 points
        points = Type.providePoints(board, parents, attributes, 'point');
        if (points === false || points.length !== 2) {
            throw new Error("JSXGraph: Can't create Semicircle with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [point,point]");
        }

        attr = Type.copyAttributes(attributes, board.options, 'semicircle', 'center');
        mp = board.create('midpoint', points, attr);
        mp.dump = false;

        attr = Type.copyAttributes(attributes, board.options, 'semicircle');
        el = board.create('arc', [mp, points[1], points[0]], attr);
        el.elType = 'semicircle';
        el.setParents([points[0].id, points[1].id]);
        el.subs = {
            midpoint: mp
        };
        el.inherits.push(mp);

        /**
         * The midpoint of the two defining points.
         * @memberOf Semicircle.prototype
         * @name midpoint
         * @type Midpoint
         */
        el.midpoint = el.center = mp;

        return el;
    };

    JXG.registerElement('semicircle', JXG.createSemicircle);

    /**
     * @class A circumcircle arc is an {@link Arc} defined by three points. All three points lie on the arc.
     * @pseudo
     * @name CircumcircleArc
     * @augments Arc
     * @constructor
     * @type Arc
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The result will be a composition of an arc of the circumcircle of
     * <tt>p1</tt>, <tt>p2</tt>, and <tt>p3</tt> and the midpoint of the circumcircle of the three points. The arc is drawn
     * counter-clockwise from <tt>p1</tt> over <tt>p2</tt> to <tt>p3</tt>.
     * @example
     * // Create a circum circle arc out of three free points
     * var p1 = board.create('point', [2.0, 2.0]);
     * var p2 = board.create('point', [1.0, 0.5]);
     * var p3 = board.create('point', [3.5, 1.0]);
     *
     * var a = board.create('circumcirclearc', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXG87125fd4-823a-41c1-88ef-d1a1369504e3" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG87125fd4-823a-41c1-88ef-d1a1369504e3', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *       p1 = board.create('point', [2.0, 2.0]),
     *       p2 = board.create('point', [1.0, 0.5]),
     *       p3 = board.create('point', [3.5, 1.0]),
     *
     *       cca = board.create('circumcirclearc', [p1, p2, p3]);
     * })();
     * </script><pre>
     */
    JXG.createCircumcircleArc = function (board, parents, attributes) {
        var el, mp, attr, points;

        // We need three points
        points = Type.providePoints(board, parents, attributes, 'point');
        if (points === false || points.length !== 3) {
            throw new Error("JSXGraph: create Circumcircle Arc with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
                "\nPossible parent types: [point,point,point]");
        }

        attr = Type.copyAttributes(attributes, board.options, 'circumcirclearc', 'center');
        mp = board.create('circumcenter', points, attr);
        mp.dump = false;

        attr = Type.copyAttributes(attributes, board.options, 'circumcirclearc');
        attr.usedirection = true;
        el = board.create('arc', [mp, points[0], points[2], points[1]], attr);

        el.elType = 'circumcirclearc';
        el.setParents([points[0].id, points[1].id, points[2].id]);
        el.subs = {
            center: mp
        };
        el.inherits.push(mp);

        /**
         * The midpoint of the circumcircle of the three points defining the circumcircle arc.
         * @memberOf CircumcircleArc.prototype
         * @name center
         * @type Circumcenter
         */
        el.center = mp;

        return el;
    };

    JXG.registerElement('circumcirclearc', JXG.createCircumcircleArc);

    /**
     * @class A minor arc is a segment of the circumference of a circle having measure less than or equal to
     * 180 degrees (pi radians). It is defined by a center, one point that
     * defines the radius, and a third point that defines the angle of the arc.
     * @pseudo
     * @name MinorArc
     * @augments Curve
     * @constructor
     * @type JXG.Curve
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor arc is an arc of a circle around p1 having measure less than or equal to
     * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
     * @example
     * // Create an arc out of three free points
     * var p1 = board.create('point', [2.0, 2.0]);
     * var p2 = board.create('point', [1.0, 0.5]);
     * var p3 = board.create('point', [3.5, 1.0]);
     *
     * var a = board.create('arc', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXG64ba7ca2-8728-45f3-96e5-3c7a4414de2f" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG64ba7ca2-8728-45f3-96e5-3c7a4414de2f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *       p1 = board.create('point', [2.0, 2.0]),
     *       p2 = board.create('point', [1.0, 0.5]),
     *       p3 = board.create('point', [3.5, 1.0]),
     *
     *       a = board.create('minorarc', [p1, p2, p3]);
     * })();
     * </script><pre>
     */

    JXG.createMinorArc = function (board, parents, attributes) {
        attributes.selection = 'minor';
        return JXG.createArc(board, parents, attributes);
    };

    JXG.registerElement('minorarc', JXG.createMinorArc);

    /**
     * @class A major arc is a segment of the circumference of a circle having measure greater than or equal to
     * 180 degrees (pi radians). It is defined by a center, one point that
     * defines the radius, and a third point that defines the angle of the arc.
     * @pseudo
     * @name MajorArc
     * @augments Curve
     * @constructor
     * @type JXG.Curve
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Major arc is an arc of a circle around p1 having measure greater than or equal to
     * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
     * @example
     * // Create an arc out of three free points
     * var p1 = board.create('point', [2.0, 2.0]);
     * var p2 = board.create('point', [1.0, 0.5]);
     * var p3 = board.create('point', [3.5, 1.0]);
     *
     * var a = board.create('minorarc', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXG17a10d38-5629-40a4-b150-f41806edee9f" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG17a10d38-5629-40a4-b150-f41806edee9f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *       p1 = board.create('point', [2.0, 2.0]),
     *       p2 = board.create('point', [1.0, 0.5]),
     *       p3 = board.create('point', [3.5, 1.0]),
     *
     *       a = board.create('majorarc', [p1, p2, p3]);
     * })();
     * </script><pre>
     */
    JXG.createMajorArc = function (board, parents, attributes) {
        attributes.selection = 'major';
        return JXG.createArc(board, parents, attributes);
    };

    JXG.registerElement('majorarc', JXG.createMajorArc);

    return {
        createArc: JXG.createArc,
        createSemicircle: JXG.createSemicircle,
        createCircumcircleArc: JXG.createCircumcircleArc,
        createMinorArc: JXG.createMinorArc,
        createMajorArc: JXG.createMajorArc
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/geometry
 math/math
 base/coords
 base/constants
 utils/type
  elements:
   point
   curve
   circumcentre
   transform
 */

define('element/sector',[
    'jxg', 'math/geometry', 'math/math', 'math/statistics', 'base/coords', 'base/constants', 'utils/type'
], function (JXG, Geometry, Mat, Statistics, Coords, Const, Type) {

    "use strict";

    /**
     * @class A circular sector is a subarea of the area enclosed by a circle. It is enclosed by two radii and an arc.
     * @pseudo
     * @name Sector
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     *
     * First possiblity of input parameters are:
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 A sector is defined by three points: The sector's center <tt>p1</tt>,
     * a second point <tt>p2</tt> defining the radius and a third point <tt>p3</tt> defining the angle of the sector. The
     * Sector is always drawn counter clockwise from <tt>p2</tt> to <tt>p3</tt>
     * <p>
     * Second possibility of input parameters are:
     * @param {JXG.Line_JXG.Line_array,number_array,number_number,function} line, line2, coords1 or direction1, coords2 or direction2, radius The sector is defined by two lines.
     * The two legs which define the sector are given by two coordinates arrays which are project initially two the two lines or by two directions (+/- 1).
     * The last parameter is the radius of the sector.
     *
     *
     * @example
     * // Create a sector out of three free points
     * var p1 = board.create('point', [1.5, 5.0]),
     *     p2 = board.create('point', [1.0, 0.5]),
     *     p3 = board.create('point', [5.0, 3.0]),
     *
     *     a = board.create('sector', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXG49f59123-f013-4681-bfd9-338b89893156" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG49f59123-f013-4681-bfd9-338b89893156', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *     p1 = board.create('point', [1.5, 5.0]),
     *     p2 = board.create('point', [1.0, 0.5]),
     *     p3 = board.create('point', [5.0, 3.0]),
     *
     *     a = board.create('sector', [p1, p2, p3]);
     * })();
     * </script><pre>
     *
     * @example
     * // Create a sector out of two lines, two directions and a radius
     * var p1 = board.create('point', [-1, 4]),
     *  p2 = board.create('point', [4, 1]),
     *  q1 = board.create('point', [-2, -3]),
     *  q2 = board.create('point', [4,3]),
     *
     *  li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
     *  li2 = board.create('line', [q1,q2], {lastArrow:true}),
     *
     *  sec1 = board.create('sector', [li1, li2, [5.5, 0], [4, 3], 3]),
     *  sec2 = board.create('sector', [li1, li2, 1, -1, 4]);
     *
     * </pre><div class="jxgbox" id="JXGbb9e2809-9895-4ff1-adfa-c9c71d50aa53" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXGbb9e2809-9895-4ff1-adfa-c9c71d50aa53', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *     p1 = board.create('point', [-1, 4]),
     *     p2 = board.create('point', [4, 1]),
     *     q1 = board.create('point', [-2, -3]),
     *     q2 = board.create('point', [4,3]),
     *
     *     li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
     *     li2 = board.create('line', [q1,q2], {lastArrow:true}),
     *
     *     sec1 = board.create('sector', [li1, li2, [5.5, 0], [4, 3], 3]),
     *     sec2 = board.create('sector', [li1, li2, 1, -1, 4]);
     * })();
     * </script><pre>
     *
     * @example
     * var t = board.create('transform', [2, 1.5], {type: 'scale'});
     * var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], {
     *                 anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true},
     *                 fillColor: 'yellow', strokeColor: 'black'});
     * var s2 = board.create('curve', [s1, t], {fillColor: 'yellow', strokeColor: 'black'});
     *
     * </pre><div id="JXG2e70ee14-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG2e70ee14-6339-11e8-9fb9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
     *     var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], {
     *                     anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true},
     *                     fillColor: 'yellow', strokeColor: 'black'});
     *     var s2 = board.create('curve', [s1, t], {fillColor: 'yellow', strokeColor: 'black'});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createSector = function (board, parents, attributes) {
        var el, attr, i,
            type = 'invalid',
            s, v,
            attrPoints = ['center', 'radiusPoint', 'anglePoint'],
            points;

        // Three points?
        if (parents[0].elementClass === Const.OBJECT_CLASS_LINE &&
                parents[1].elementClass === Const.OBJECT_CLASS_LINE &&
                (Type.isArray(parents[2]) || Type.isNumber(parents[2])) &&
                (Type.isArray(parents[3]) || Type.isNumber(parents[3])) &&
                (Type.isNumber(parents[4]) || Type.isFunction(parents[4]) || Type.isString(parents[4]))) {

            type = '2lines';
        } else {
            points = Type.providePoints(board, parents, attributes, 'sector', attrPoints);
            if (points === false) {
                throw new Error("JSXGraph: Can't create Sector with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" +
                    (typeof parents[2]) + "'.");
            }
            type = '3points';
        }


        attr = Type.copyAttributes(attributes, board.options, 'sector');
        el = board.create('curve', [[0], [0]], attr);
        el.type = Const.OBJECT_TYPE_SECTOR;
        el.elType = 'sector';

        /**
         * Set a radius if the attribute `radius` has value 'auto'.
         * Sets a radius between 20 and 50 points, depending on the distance
         * between the center and the radius point.
         * This function is used in {@link Angle}.
         *
         * @returns {Number} returns a radius value in user coordinates.
         */
         el.autoRadius = function() {
            var r1 = 20  / el.board.unitX,  // 20px
                r2 = Infinity,
                r3 = 50  / el.board.unitX;  // 50px

            if (Type.isPoint(el.center)) {
                // This does not work for 2-lines sectors / angles
                r2 = el.center.Dist(el.point2) * 0.3333;
            }

            return Math.max(r1, Math.min(r2, r3));
        };

        if (type === '2lines') {
            /**
             * @ignore
             */
             el.Radius = function () {
                var r = Type.evaluate(parents[4]);
                if (r === 'auto') {
                    return this.autoRadius();
                }
                return r;
            };

            el.line1 = board.select(parents[0]);
            el.line2 = board.select(parents[1]);

            el.line1.addChild(el);
            el.line2.addChild(el);
            el.setParents(parents);

            el.point1 = {visProp: {}};
            el.point2 = {visProp: {}};
            el.point3 = {visProp: {}};

            /* Intersection point */
            s = Geometry.meetLineLine(el.line1.stdform, el.line2.stdform, 0, board);

            if (Type.isArray(parents[2])) {
                /* project p1 to l1 */
                if (parents[2].length === 2) {
                    parents[2] = [1].concat(parents[2]);
                }
                /*
                v = [0, el.line1.stdform[1], el.line1.stdform[2]];
                v = Mat.crossProduct(v, parents[2]);
                v = Geometry.meetLineLine(v, el.line1.stdform, 0, board);
                */
                v = Geometry.projectPointToLine({coords: {usrCoords: parents[2]}}, el.line1, board);
                v = Statistics.subtract(v.usrCoords, s.usrCoords);
                el.direction1 = (Mat.innerProduct(v, [0, el.line1.stdform[2], -el.line1.stdform[1]], 3) >= 0) ? +1 : -1;
            } else {
                el.direction1 = (parents[2] >= 0) ? 1 : -1;
            }

            if (Type.isArray(parents[3])) {
                /* project p2 to l2 */
                if (parents[3].length === 2) {
                    parents[3] = [1].concat(parents[3]);
                }
                /*
                v = [0, el.line2.stdform[1], el.line2.stdform[2]];
                v = Mat.crossProduct(v, parents[3]);
                v = Geometry.meetLineLine(v, el.line2.stdform, 0, board);
                */
                v = Geometry.projectPointToLine({coords: {usrCoords: parents[3]}}, el.line2, board);
                v = Statistics.subtract(v.usrCoords, s.usrCoords);
                el.direction2 = (Mat.innerProduct(v, [0, el.line2.stdform[2], -el.line2.stdform[1]], 3) >= 0) ? +1 : -1;
            } else {
                el.direction2 = (parents[3] >= 0) ? 1 : -1;
            }

            el.updateDataArray = function () {
                var r, l1, l2,
                    A = [0, 0, 0],
                    B = [0, 0, 0],
                    C = [0, 0, 0],
                    ar;

                l1 = this.line1;
                l2 = this.line2;

                // Intersection point of the lines
                B = Mat.crossProduct(l1.stdform, l2.stdform);

                if (Math.abs(B[0]) > Mat.eps * Mat.eps) {
                    B[1] /= B[0];
                    B[2] /= B[0];
                    B[0] /= B[0];
                }
                // First point
                r = this.direction1 * this.Radius();
                A = Statistics.add(B, [0, r * l1.stdform[2], -r * l1.stdform[1]]);

                // Second point
                r = this.direction2 * this.Radius();
                C = Statistics.add(B, [0, r * l2.stdform[2], -r * l2.stdform[1]]);

                this.point2.coords = new Coords(Const.COORDS_BY_USER, A, el.board);
                this.point1.coords = new Coords(Const.COORDS_BY_USER, B, el.board);
                this.point3.coords = new Coords(Const.COORDS_BY_USER, C, el.board);

                if (Math.abs(A[0]) < Mat.eps || Math.abs(B[0]) < Mat.eps || Math.abs(C[0]) < Mat.eps) {
                    this.dataX = [NaN];
                    this.dataY = [NaN];
                    return;
                }

                ar = Geometry.bezierArc(A, B, C, true, 1);

                this.dataX = ar[0];
                this.dataY = ar[1];

                this.bezierDegree = 3;
            };

            el.methodMap = JXG.deepCopy(el.methodMap, {
                radius: 'Radius',
                getRadius: 'Radius',
                setRadius: 'setRadius'
            });

        //    el.prepareUpdate().update();

        // end '2lines'

        } else if (type === '3points') {

            /**
            * Midpoint of the sector.
            * @memberOf Sector.prototype
            * @name point1
            * @type JXG.Point
            */
            el.point1 = points[0];

            /**
            * This point together with {@link Sector#point1} defines the radius..
            * @memberOf Sector.prototype
            * @name point2
            * @type JXG.Point
            */
            el.point2 = points[1];

            /**
            * Defines the sector's angle.
            * @memberOf Sector.prototype
            * @name point3
            * @type JXG.Point
            */
            el.point3 = points[2];

            /* Add arc as child to defining points */
            for (i = 0; i < 3; i++) {
                if (Type.exists(points[i]._is_new)) {
                    el.addChild(points[i]);
                    delete points[i]._is_new;
                } else {
                    points[i].addChild(el);
                }
            }

            // useDirection is necessary for circumCircleSectors
            el.useDirection = attributes.usedirection;
            el.setParents(points);

            /**
            * Defines the sectors orientation in case of circumCircleSectors.
            * @memberOf Sector.prototype
            * @name point4
            * @type JXG.Point
            */
            if (Type.exists(points[3])) {
                el.point4 = points[3];
                el.point4.addChild(el);
            }

            el.methodMap = JXG.deepCopy(el.methodMap, {
                arc: 'arc',
                center: 'center',
                radiuspoint: 'radiuspoint',
                anglepoint: 'anglepoint',
                radius: 'Radius',
                getRadius: 'Radius',
                setRadius: 'setRadius'
            });

            /**
            * documented in JXG.Curve
            * @ignore
            */
            el.updateDataArray = function () {
                var ar, det, p0c, p1c, p2c,
                    A = this.point2,
                    B = this.point1,
                    C = this.point3,
                    phi, sgn = 1,
                    vp_s = Type.evaluate(this.visProp.selection);

                if (!A.isReal || !B.isReal || !C.isReal) {
                    this.dataX = [NaN];
                    this.dataY = [NaN];
                    return;
                }

                phi = Geometry.rad(A, B, C);
                if ((vp_s === 'minor' && phi > Math.PI) ||
                        (vp_s === 'major' && phi < Math.PI)) {
                    sgn = -1;
                }

                // This is true for circumCircleSectors. In that case there is
                // a fourth parent element: [midpoint, point1, point3, point2]
                if (this.useDirection && Type.exists(this.point4)) {
                    p0c = this.point2.coords.usrCoords;
                    p1c = this.point4.coords.usrCoords;
                    p2c = this.point3.coords.usrCoords;
                    det = (p0c[1] - p2c[1]) * (p0c[2] - p1c[2]) - (p0c[2] - p2c[2]) * (p0c[1] - p1c[1]);

                    if (det >= 0.0) {
                        C = this.point2;
                        A = this.point3;
                    }
                }

                A = A.coords.usrCoords;
                B = B.coords.usrCoords;
                C = C.coords.usrCoords;

                ar = Geometry.bezierArc(A, B, C, true, sgn);

                this.dataX = ar[0];
                this.dataY = ar[1];
                this.bezierDegree = 3;
            };

            /**
            * Returns the radius of the sector.
            * @memberOf Sector.prototype
            * @name Radius
            * @function
            * @returns {Number} The distance between {@link Sector#point1} and {@link Sector#point2}.
            */
            el.Radius = function () {
                return this.point2.Dist(this.point1);
            };

            attr = Type.copyAttributes(attributes, board.options, 'sector', 'arc');
            attr.withLabel = false;
            attr.name += '_arc';
            el.arc = board.create('arc', [el.point1, el.point2, el.point3], attr);
            el.addChild(el.arc);
        }   // end '3points'

        el.center = el.point1;
        el.radiuspoint = el.point2;
        el.anglepoint = el.point3;

        // Default hasPoint method. Documented in geometry element
        el.hasPointCurve = function (x, y) {
            var angle, alpha, beta,
                prec, type,
                checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
                r = this.Radius(),
                dist = this.center.coords.distance(Const.COORDS_BY_USER, checkPoint),
                has,
                vp_s = Type.evaluate(this.visProp.selection);

            if (Type.isObject(Type.evaluate(this.visProp.precision))) {
                type = this.board._inputDevice;
                prec = Type.evaluate(this.visProp.precision[type]);
            } else {
                // 'inherit'
                prec = this.board.options.precision.hasPoint;
            }
            prec /= Math.min(this.board.unitX, this.board.unitY);
            has = (Math.abs(dist - r) < prec);
            if (has) {
                angle = Geometry.rad(this.point2, this.center, checkPoint.usrCoords.slice(1));
                alpha = 0;
                beta = Geometry.rad(this.point2, this.center, this.point3);

                if ((vp_s === 'minor' && beta > Math.PI) ||
                        (vp_s === 'major' && beta < Math.PI)) {
                    alpha = beta;
                    beta = 2 * Math.PI;
                }

                if (angle < alpha || angle > beta) {
                    has = false;
                }
            }

            return has;
        };

        /**
        * Checks whether (x,y) is within the area defined by the sector.
        * @memberOf Sector.prototype
        * @name hasPointSector
        * @function
        * @param {Number} x Coordinate in x direction, screen coordinates.
        * @param {Number} y Coordinate in y direction, screen coordinates.
        * @returns {Boolean} True if (x,y) is within the sector defined by the arc, False otherwise.
        */
        el.hasPointSector = function (x, y) {
            var angle,
                checkPoint = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board),
                r = this.Radius(),
                dist = this.point1.coords.distance(Const.COORDS_BY_USER, checkPoint),
                alpha,
                beta,
                has = (dist < r),
                vp_s = Type.evaluate(this.visProp.selection);

            if (has) {
                angle = Geometry.rad(this.radiuspoint, this.center, checkPoint.usrCoords.slice(1));
                alpha = 0.0;
                beta = Geometry.rad(this.radiuspoint, this.center, this.anglepoint);

                if ((vp_s === 'minor' && beta > Math.PI) ||
                        (vp_s === 'major' && beta < Math.PI)) {
                    alpha = beta;
                    beta = 2 * Math.PI;
                }
                //if (angle > Geometry.rad(this.point2, this.point1, this.point3)) {
                if (angle < alpha || angle > beta) {
                    has = false;
                }
            }
            return has;
        };

        el.hasPoint = function (x, y) {
            if (Type.evaluate(this.visProp.highlightonsector) ||
                    Type.evaluate(this.visProp.hasinnerpoints)) {
                return this.hasPointSector(x, y);
            }

            return this.hasPointCurve(x, y);
        };

        // documented in GeometryElement
        el.getTextAnchor = function () {
            return this.point1.coords;
        };

        // documented in GeometryElement
        // this method is very similar to arc.getLabelAnchor()
        // there are some additions in the arc version though, mainly concerning
        // "major" and "minor" arcs. but maybe these methods can be merged.
        el.getLabelAnchor = function () {
            var coords, vec, vecx, vecy, len,
                angle = Geometry.rad(this.point2, this.point1, this.point3),
                dx = 13 / this.board.unitX,
                dy = 13 / this.board.unitY,
                p2c = this.point2.coords.usrCoords,
                pmc = this.point1.coords.usrCoords,
                bxminusax = p2c[1] - pmc[1],
                byminusay = p2c[2] - pmc[2],
                vp_s = Type.evaluate(this.visProp.selection),
                l_vp = this.label ? this.label.visProp : this.visProp.label;

            // If this is uncommented, the angle label can not be dragged
            //if (Type.exists(this.label)) {
            //    this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
            //}

            if ((vp_s === 'minor' && angle > Math.PI) ||
                    (vp_s === 'major' && angle < Math.PI)) {
                angle = -(2 * Math.PI - angle);
            }

            coords = new Coords(Const.COORDS_BY_USER, [
                pmc[1] + Math.cos(angle * 0.5) * bxminusax - Math.sin(angle * 0.5) * byminusay,
                pmc[2] + Math.sin(angle * 0.5) * bxminusax + Math.cos(angle * 0.5) * byminusay
            ], this.board);

            vecx = coords.usrCoords[1] - pmc[1];
            vecy = coords.usrCoords[2] - pmc[2];

            len = Math.sqrt(vecx * vecx + vecy * vecy);
            vecx = vecx * (len + dx) / len;
            vecy = vecy * (len + dy) / len;
            vec = [pmc[1] + vecx, pmc[2] + vecy];

            l_vp.position = Geometry.calcLabelQuadrant(Geometry.rad([1,0],[0,0],vec));

            return new Coords(Const.COORDS_BY_USER, vec, this.board);
        };

        /**
         * Overwrite the Radius method of the sector.
         * Used in {@link GeometryElement#setAttribute}.
         * @param {Number, Function} value New radius.
         */
        el.setRadius = function (val) {
            /**
             * @ignore
             */
             el.Radius = function () {
                var r = Type.evaluate(val);
                if (r === 'auto') {
                    return this.autoRadius();
                }
                return r;
            };
        };

        /**
         * @deprecated
         * @ignore
         */
        el.getRadius = function () {
            JXG.deprecated('Sector.getRadius()', 'Sector.Radius()');
            return this.Radius();
        };

        /**
         * Moves the sector by the difference of two coordinates.
         * @param {Number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
         * @param {Array} coords coordinates in screen/user units
         * @param {Array} oldcoords previous coordinates in screen/user units
         * @returns {JXG.Curve} this element
         */
        if (type === '3points') {
            el.setPositionDirectly = function (method, coords, oldcoords) {
                var dc, t, i,
                    c = new Coords(method, coords, this.board),
                    oldc = new Coords(method, oldcoords, this.board);

                if (!el.point1.draggable() || !el.point2.draggable() || !el.point3.draggable()) {
                    return this;
                }

                dc = Statistics.subtract(c.usrCoords, oldc.usrCoords);
                t = this.board.create('transform', dc.slice(1), {type: 'translate'});
                t.applyOnce([el.point1, el.point2, el.point3]);

                return this;
            };
        }

        el.prepareUpdate().update();

        return el;
    };

    JXG.registerElement('sector', JXG.createSector);

    /**
     * @class A circumcircle sector is different from a {@link Sector} mostly in the way the parent elements are interpreted.
     * At first, the circum centre is determined from the three given points. Then the sector is drawn from <tt>p1</tt> through
     * <tt>p2</tt> to <tt>p3</tt>.
     * @pseudo
     * @name CircumcircleSector
     * @augments Sector
     * @constructor
     * @type Sector
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 A circumcircle sector is defined by the circumcircle which is determined
     * by these three given points. The circumcircle sector is always drawn from <tt>p1</tt> through <tt>p2</tt> to <tt>p3</tt>.
     * @example
     * // Create an arc out of three free points
     * var p1 = board.create('point', [1.5, 5.0]),
     *     p2 = board.create('point', [1.0, 0.5]),
     *     p3 = board.create('point', [5.0, 3.0]),
     *
     *     a = board.create('circumcirclesector', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXG695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
 *   var board = JXG.JSXGraph.initBoard('JXG695cf0d6-6d7a-4d4d-bfc9-34c6aa28cd04', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
 *     p1 = board.create('point', [1.5, 5.0]),
 *     p2 = board.create('point', [1.0, 0.5]),
 *     p3 = board.create('point', [5.0, 3.0]),
 *
 *     a = board.create('circumcirclesector', [p1, p2, p3]);
 * })();
     * </script><pre>
     */
    JXG.createCircumcircleSector = function (board, parents, attributes) {
        var el, mp, attr, points, i;

        points = Type.providePoints(board, parents, attributes, 'point');
        if (points === false) {
            throw new Error("JSXGraph: Can't create circumcircle sector with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'.");
        }

        mp = board.create('circumcenter', points.slice(0, 3), attr);
        mp.dump = false;

        attr = Type.copyAttributes(attributes, board.options, 'circumcirclesector');
        el = board.create('sector', [mp, points[0], points[2], points[1]], attr);

        el.elType = 'circumcirclesector';
        el.setParents(points);

        /**
         * Center of the circumcirclesector
         * @memberOf CircumcircleSector.prototype
         * @name center
         * @type Circumcenter
         */
        el.center = mp;
        el.subs = {
            center: mp
        };

        return el;
    };

    JXG.registerElement('circumcirclesector', JXG.createCircumcircleSector);

    /**
     * @class A minor sector is a sector of a circle having measure less than or equal to
     * 180 degrees (pi radians). It is defined by a center, one point that
     * defines the radius, and a third point that defines the angle of the sector.
     * @pseudo
     * @name MinorSector
     * @augments Curve
     * @constructor
     * @type JXG.Curve
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor sector is a sector of a circle around p1 having measure less than or equal to
     * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
     * @example
     * // Create sector out of three free points
     * var p1 = board.create('point', [2.0, 2.0]);
     * var p2 = board.create('point', [1.0, 0.5]);
     * var p3 = board.create('point', [3.5, 1.0]);
     *
     * var a = board.create('minorsector', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXGaf27ddcc-265f-428f-90dd-d31ace945800" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXGaf27ddcc-265f-428f-90dd-d31ace945800', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *       p1 = board.create('point', [2.0, 2.0]),
     *       p2 = board.create('point', [1.0, 0.5]),
     *       p3 = board.create('point', [3.5, 1.0]),
     *
     *       a = board.create('minorsector', [p1, p2, p3]);
     * })();
     * </script><pre>
     */
    JXG.createMinorSector = function (board, parents, attributes) {
        attributes.selection = 'minor';
        return JXG.createSector(board, parents, attributes);
    };

    JXG.registerElement('minorsector', JXG.createMinorSector);

    /**
     * @class A major sector is a sector of a circle having measure greater than or equal to
     * 180 degrees (pi radians). It is defined by a center, one point that
     * defines the radius, and a third point that defines the angle of the sector.
     * @pseudo
     * @name MajorSector
     * @augments Curve
     * @constructor
     * @type JXG.Curve
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Major sector is a sector of a circle around p1 having measure greater than or equal to
     * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
     * @example
     * // Create an arc out of three free points
     * var p1 = board.create('point', [2.0, 2.0]);
     * var p2 = board.create('point', [1.0, 0.5]);
     * var p3 = board.create('point', [3.5, 1.0]);
     *
     * var a = board.create('majorsector', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXG83c6561f-7561-4047-b98d-036248a00932" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG83c6561f-7561-4047-b98d-036248a00932', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *       p1 = board.create('point', [2.0, 2.0]),
     *       p2 = board.create('point', [1.0, 0.5]),
     *       p3 = board.create('point', [3.5, 1.0]),
     *
     *       a = board.create('majorsector', [p1, p2, p3]);
     * })();
     * </script><pre>
     */
    JXG.createMajorSector = function (board, parents, attributes) {
        attributes.selection = 'major';
        return JXG.createSector(board, parents, attributes);
    };

    JXG.registerElement('majorsector', JXG.createMajorSector);

    /**
     * @class The angle element is used to denote an angle defined by three points. Visually it is just a {@link Sector}
     * element with a radius not defined by the parent elements but by an attribute <tt>radius</tt>. As opposed to the sector,
     * an angle has two angle points and no radius point.
     * Sector is displayed if type=="sector".
     * If type=="square", instead of a sector a parallelogram is displayed.
     * In case of type=="auto", a square is displayed if the angle is near orthogonal.
     * If no name is provided the angle label is automatically set to a lower greek letter.
     * @pseudo
     * @name Angle
     * @augments Sector
     * @constructor
     * @type Sector
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * First possibility of input parameters are:
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p1 An angle is always drawn counterclockwise from <tt>p1</tt> to
     * <tt>p3</tt> around <tt>p2</tt>.
     *
     * Second possibility of input parameters are:
     * @param {JXG.Line_JXG.Line_array|number_array|number} line, line2, coords1 or direction1, coords2 or direction2, radius The angle is defined by two lines.
     * The two legs which define the angle are given by two coordinate arrays.
     * The points given by these coordinate arrays are projected initially (i.e. only once) onto the two lines.
     * The other possibility is to supply directions (+/- 1).
     *
     * @example
     * // Create an angle out of three free points
     * var p1 = board.create('point', [5.0, 3.0]),
     *     p2 = board.create('point', [1.0, 0.5]),
     *     p3 = board.create('point', [1.5, 5.0]),
     *
     *     a = board.create('angle', [p1, p2, p3]),
     *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
     * </pre><div class="jxgbox" id="JXGa34151f9-bb26-480a-8d6e-9b8cbf789ae5" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXGa34151f9-bb26-480a-8d6e-9b8cbf789ae5', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *     p1 = board.create('point', [5.0, 3.0]),
     *     p2 = board.create('point', [1.0, 0.5]),
     *     p3 = board.create('point', [1.5, 5.0]),
     *
     *     a = board.create('angle', [p1, p2, p3]),
     *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
     * })();
     * </script><pre>
     *
     * @example
     * // Create an angle out of two lines and two directions
     * var p1 = board.create('point', [-1, 4]),
     *  p2 = board.create('point', [4, 1]),
     *  q1 = board.create('point', [-2, -3]),
     *  q2 = board.create('point', [4,3]),
     *
     *  li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
     *  li2 = board.create('line', [q1,q2], {lastArrow:true}),
     *
     *  a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }),
     *  a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 });
     *
     *
     * </pre><div class="jxgbox" id="JXG3a667ddd-63dc-4594-b5f1-afac969b371f" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG3a667ddd-63dc-4594-b5f1-afac969b371f', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *     p1 = board.create('point', [-1, 4]),
     *     p2 = board.create('point', [4, 1]),
     *     q1 = board.create('point', [-2, -3]),
     *     q2 = board.create('point', [4,3]),
     *
     *     li1 = board.create('line', [p1,p2], {strokeColor:'black', lastArrow:true}),
     *     li2 = board.create('line', [q1,q2], {lastArrow:true}),
     *
     *     a1 = board.create('angle', [li1, li2, [5.5, 0], [4, 3]], { radius:1 }),
     *     a2 = board.create('angle', [li1, li2, 1, -1], { radius:2 });
     * })();
     * </script><pre>
     *
     *
     * @example
     * // Display the angle value instead of the name
     * var p1 = board.create('point', [0,2]);
     * var p2 = board.create('point', [0,0]);
     * var p3 = board.create('point', [-2,0.2]);
     *
     * var a = board.create('angle', [p1, p2, p3], {
     * 	 radius: 1,
     *   name: function() {
     *   	return JXG.Math.Geometry.trueAngle(p1, p2, p3).toFixed(1) + '°';
     *   }});
     *
     * </pre><div id="JXGc813f601-8dd3-4030-9892-25c6d8671512" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGc813f601-8dd3-4030-9892-25c6d8671512',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *
     *     var p1 = board.create('point', [0,2]);
     *     var p2 = board.create('point', [0,0]);
     *     var p3 = board.create('point', [-2,0.2]);
     *
     *     var a = board.create('angle', [p1, p2, p3], {
     *     	radius: 1,
     *       name: function() {
     *       	return JXG.Math.Geometry.trueAngle(p1, p2, p3).toFixed(1) + '°';
     *       }});
     *
     *     })();
     *
     * </script><pre>
     *
     *
     * @example
     * // Apply a transformation to an angle.
     * var t = board.create('transform', [2, 1.5], {type: 'scale'});
     * var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]);
     * var an2 = board.create('curve', [an1, t]);
     *
     * </pre><div id="JXG4c8d9ed8-6339-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG4c8d9ed8-6339-11e8-9fb9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var t = board.create('transform', [2, 1.5], {type: 'scale'});
     *     var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]);
     *     var an2 = board.create('curve', [an1, t]);
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createAngle = function (board, parents, attributes) {
        var el, radius, attr, attrsub,
            i, points,
            type = 'invalid';

        // Two lines or three points?
        if (parents[0].elementClass === Const.OBJECT_CLASS_LINE &&
                parents[1].elementClass === Const.OBJECT_CLASS_LINE &&
                (Type.isArray(parents[2]) || Type.isNumber(parents[2])) &&
                (Type.isArray(parents[3]) || Type.isNumber(parents[3]))) {

            type = '2lines';
        } else {
            points = Type.providePoints(board, parents, attributes, 'point');
            if (points === false) {
                throw new Error("JSXGraph: Can't create angle with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'.");
            }
            type = '3points';
        }

        attr = Type.copyAttributes(attributes, board.options, 'angle');

        //  If empty, create a new name
        if (!Type.exists(attr.name) || attr.name === '') {
            attr.name = board.generateName({type: Const.OBJECT_TYPE_ANGLE});
        }

        if (Type.exists(attr.radius)) {
            radius = attr.radius;
        } else {
            radius = 0;
        }

        if (type === '2lines') {
            parents.push(radius);
            el = board.create('sector', parents, attr);
            el.updateDataArraySector = el.updateDataArray;

            // TODO
            el.setAngle = function (val) {};
            el.free = function (val) {};

        } else {
            el = board.create('sector', [points[1], points[0], points[2]], attr);
            el.arc.visProp.priv = true;

            /**
             * The point defining the radius of the angle element.
             * Alias for {@link Sector#radiuspoint}.
             * @type JXG.Point
             * @name point
             * @memberOf Angle.prototype
             *
             */
            el.point = el.point2 = el.radiuspoint = points[0];

            /**
             * Helper point for angles of type 'square'.
             * @type JXG.Point
             * @name pointsquare
             * @memberOf Angle.prototype
             */
            el.pointsquare = el.point3 = el.anglepoint = points[2];

            /**
             * @ignore
             */
            el.Radius = function () {
                // Set the angle radius, also @see @link Sector#autoRadius
                var r = Type.evaluate(radius);
                if (r === 'auto') {
                    return el.autoRadius();
                }
                return r;
            };

            el.updateDataArraySector = function () {
                var A = this.point2,
                    B = this.point1,
                    C = this.point3,
                    r = this.Radius(),
                    d = B.Dist(A),
                    ar,
                    phi,
                    sgn = 1,
                    vp_s = Type.evaluate(this.visProp.selection);

                phi = Geometry.rad(A, B, C);
                if ((vp_s === 'minor' && phi > Math.PI) ||
                        (vp_s === 'major' && phi < Math.PI)) {
                    sgn = -1;
                }

                A = A.coords.usrCoords;
                B = B.coords.usrCoords;
                C = C.coords.usrCoords;

                A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d];
                C = [1, B[1] + (C[1] - B[1]) * r / d, B[2] + (C[2] - B[2]) * r / d];

                ar = Geometry.bezierArc(A, B, C, true, sgn);

                this.dataX = ar[0];
                this.dataY = ar[1];
                this.bezierDegree = 3;
            };

            /**
            * Set an angle to a prescribed value given in radians.
            * This is only possible if the third point of the angle, i.e.
            * the anglepoint is a free point.
            * Removing the constraint again is done by calling "angle.free()".
            *
            * Changing the angle requires to call the method "free()":
            *
            * <pre>
            * angle.setAngle(Math.PI / 6);
            * // ...
            * angle.free().setAngle(Math.PI / 4);
            * </pre>
            *
            * @name setAngle
            * @function
            * @param {Number|Function} val Number or Function which returns the size of the angle in Radians
            * @returns {Object} Pointer to the angle element..
            * @memberOf Angle.prototype
            * @see Angle#free
            *
            * @example
            * var p1, p2, p3, c, a, s;
            *
            * p1 = board.create('point',[0,0]);
            * p2 = board.create('point',[5,0]);
            * p3 = board.create('point',[0,5]);
            *
            * c1 = board.create('circle',[p1, p2]);
            *
            * a = board.create('angle',[p2, p1, p3], {radius:3});
            *
            * a.setAngle(function() {
            *     return Math.PI / 3;
            * });
            * board.update();
            *
            * </pre><div id="JXG987c-394f-11e6-af4a-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
            * <script type="text/javascript">
            *     (function() {
            *         var board = JXG.JSXGraph.initBoard('JXG987c-394f-11e6-af4a-901b0e1b8723',
            *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
            *     var p1, p2, p3, c, a, s;
            *
            *     p1 = board.create('point',[0,0]);
            *     p2 = board.create('point',[5,0]);
            *     p3 = board.create('point',[0,5]);
            *
            *     c1 = board.create('circle',[p1, p2]);
            *
            *     a = board.create('angle',[p2, p1, p3], {radius: 3});
            *
            *     a.setAngle(function() {
            *         return Math.PI / 3;
            *     });
            *     board.update();
            *
            *     })();
            *
            * </script><pre>
            *
            * @example
            * var p1, p2, p3, c, a, s;
            *
            * p1 = board.create('point',[0,0]);
            * p2 = board.create('point',[5,0]);
            * p3 = board.create('point',[0,5]);
            *
            * c1 = board.create('circle',[p1, p2]);
            *
            * a = board.create('angle',[p2, p1, p3], {radius:3});
            * s = board.create('slider',[[-2,1], [2,1], [0, Math.PI*0.5, 2*Math.PI]]);
            *
            * a.setAngle(function() {
            *     return s.Value();
            * });
            * board.update();
            *
            * </pre><div id="JXG99957b1c-394f-11e6-af4a-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
            * <script type="text/javascript">
            *     (function() {
            *         var board = JXG.JSXGraph.initBoard('JXG99957b1c-394f-11e6-af4a-901b0e1b8723',
            *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
            *     var p1, p2, p3, c, a, s;
            *
            *     p1 = board.create('point',[0,0]);
            *     p2 = board.create('point',[5,0]);
            *     p3 = board.create('point',[0,5]);
            *
            *     c1 = board.create('circle',[p1, p2]);
            *
            *     a = board.create('angle',[p2, p1, p3], {radius: 3});
            *     s = board.create('slider',[[-2,1], [2,1], [0, Math.PI*0.5, 2*Math.PI]]);
            *
            *     a.setAngle(function() {
            *         return s.Value();
            *     });
            *     board.update();
            *
            *     })();
            *
            * </script><pre>
            *
            */
            el.setAngle = function (val) {
                var t1, t2, val2,
                    p = this.anglepoint,
                    q = this.radiuspoint;

                if (p.draggable()) {
                    t1 = this.board.create('transform', [val, this.center], {type: 'rotate'});
                    p.addTransform(q, t1);
                    // Immediately apply the transformation.
                    // This prevents that jumping elements can be watched.
                    t1.update();
                    p.moveTo(Mat.matVecMult(t1.matrix, q.coords.usrCoords));

                    if (Type.isFunction(val)) {
                        /**
                         * @ignore
                         */
                        val2 = function() { return Math.PI * 2 - val(); };
                    } else {
                        /**
                         * @ignore
                         */
                         val2 = function() { return Math.PI * 2 - val; };
                    }
                    t2 = this.board.create('transform', [val2, this.center], {type: 'rotate'});
                    p.coords.on('update', function() {
                        t2.update();
                        q.moveTo(Mat.matVecMult(t2.matrix, p.coords.usrCoords));
                    });

                    p.setParents(q);
                }
                return this;
            };

            /**
            * Frees an angle from a prescribed value. This is only relevant if the angle size has been set by
            * "setAngle()" previously. The anglepoint is set to a free point.
            * @name free
            * @function
            * @returns {Object} Pointer to the angle element..
            * @memberOf Angle.prototype
            * @see Angle#setAngle
            */
            el.free = function () {
                var p = this.anglepoint;

                if (p.transformations.length > 0) {
                    p.transformations.pop();
                    p.isDraggable = true;
                    p.parents = [];

                    p.coords.off('update');
                }

                return this;
            };

            el.setParents(points); // Important: This overwrites the parents order in underlying sector

        } // end '3points'

        // GEONExT compatible labels.
        if (Type.exists(el.visProp.text)) {
            el.label.setText(Type.evaluate(el.visProp.text));
        }

        el.elType = 'angle';
        el.type = Const.OBJECT_TYPE_ANGLE;
        el.subs = {};

        el.updateDataArraySquare = function () {
            var A, B, C,
                r = this.Radius(),
                d1, d2,
                v, l1, l2;


            if (type === '2lines') {
                // This is necessary to update this.point1, this.point2, this.point3.
                this.updateDataArraySector();
            }

            A = this.point2;
            B = this.point1;
            C = this.point3;

            A = A.coords.usrCoords;
            B = B.coords.usrCoords;
            C = C.coords.usrCoords;

            d1 = Geometry.distance(A, B, 3);
            d2 = Geometry.distance(C, B, 3);

            // In case of type=='2lines' this is redundant, because r == d1 == d2
            A = [1, B[1] + (A[1] - B[1]) * r / d1, B[2] + (A[2] - B[2]) * r / d1];
            C = [1, B[1] + (C[1] - B[1]) * r / d2, B[2] + (C[2] - B[2]) * r / d2];

            v = Mat.crossProduct(C, B);
            l1 = [-A[1] * v[1] - A[2] * v[2], A[0] * v[1], A[0] * v[2]];
            v = Mat.crossProduct(A, B);
            l2 = [-C[1] * v[1] - C[2] * v[2], C[0] * v[1], C[0] * v[2]];

            v = Mat.crossProduct(l1, l2);
            v[1] /= v[0];
            v[2] /= v[0];

            this.dataX = [B[1], A[1], v[1], C[1], B[1]];
            this.dataY = [B[2], A[2], v[2], C[2], B[2]];

            this.bezierDegree = 1;
        };

        el.updateDataArrayNone = function () {
            this.dataX = [NaN];
            this.dataY = [NaN];
            this.bezierDegree = 1;
        };

        el.updateDataArray = function () {
            var type = Type.evaluate(this.visProp.type),
                deg = Geometry.trueAngle(this.point2, this.point1, this.point3),
                vp_s = Type.evaluate(this.visProp.selection);

            if ((vp_s === 'minor' && deg > 180.0) ||
                    (vp_s === 'major' && deg < 180.0)) {
                deg = 360.0 - deg;
            }

            if (Math.abs(deg - 90.0) < Type.evaluate(this.visProp.orthosensitivity) + Mat.eps) {
                type = Type.evaluate(this.visProp.orthotype);
            }

            if (type === 'none') {
                this.updateDataArrayNone();
            } else if (type === 'square') {
                this.updateDataArraySquare();
            } else if (type === 'sector') {
                this.updateDataArraySector();
            } else if (type === 'sectordot') {
                this.updateDataArraySector();
                if (!this.dot.visProp.visible) {
                    this.dot.setAttribute({visible: true});
                }
            }

            if (!this.visProp.visible || (type !== 'sectordot' && this.dot.visProp.visible)) {
                this.dot.setAttribute({visible: false});
            }
        };

        /**
         * Indicates a right angle. Invisible by default, use <tt>dot.visible: true</tt> to show.
         * Though this dot indicates a right angle, it can be visible even if the angle is not a right
         * one.
         * @type JXG.Point
         * @name dot
         * @memberOf Angle.prototype
         */
        attrsub = Type.copyAttributes(attributes, board.options, 'angle', 'dot');
        el.dot = board.create('point', [function () {
            var A, B, r, d, a2, co, si, mat,
                vp_s;

            if (Type.exists(el.dot) && !el.dot.visProp.visible) {
                return [0, 0];
            }

            A = el.point2.coords.usrCoords;
            B = el.point1.coords.usrCoords;
            r = el.Radius();
            d = Geometry.distance(A, B, 3);
            a2 = Geometry.rad(el.point2, el.point1, el.point3);

            vp_s = Type.evaluate(el.visProp.selection);
            if ((vp_s === 'minor' && a2 > Math.PI) ||
                    (vp_s === 'major' && a2 < Math.PI)) {
                a2 = -(2 * Math.PI - a2);
            }
            a2 *= 0.5;

            co = Math.cos(a2);
            si = Math.sin(a2);

            A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d];

            mat = [
                [1, 0, 0],
                [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5],
                [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5,  co * 0.5]
            ];
            return Mat.matVecMult(mat, A);
        }], attrsub);

        el.dot.dump = false;
        el.subs.dot = el.dot;

        if (type === '2lines') {
            for (i = 0; i < 2; i++) {
                board.select(parents[i]).addChild(el.dot);
            }
        } else {
            for (i = 0; i < 3; i++) {
                board.select(points[i]).addChild(el.dot);
            }
        }

        // documented in GeometryElement
        el.getLabelAnchor = function () {
            var vec, dx = 12,
                A, B, r, d, a2, co, si, mat,
                vp_s = Type.evaluate(el.visProp.selection),
                l_vp = this.label ? this.label.visProp : this.visProp.label;

            // If this is uncommented, the angle label can not be dragged
            //if (Type.exists(this.label)) {
            //    this.label.relativeCoords = new Coords(Const.COORDS_BY_SCREEN, [0, 0], this.board);
            //}

            if (Type.exists(this.label.visProp.fontSize)) {
                dx = Type.evaluate(this.label.visProp.fontSize);
            }
            dx /= this.board.unitX;

            A = el.point2.coords.usrCoords;
            B = el.point1.coords.usrCoords;
            r = el.Radius();
            d = Geometry.distance(A, B, 3);
            a2 = Geometry.rad(el.point2, el.point1, el.point3);
            if ((vp_s === 'minor' && a2 > Math.PI) ||
                    (vp_s === 'major' && a2 < Math.PI)) {
                a2 = -(2 * Math.PI - a2);
            }
            a2 *= 0.5;
            co = Math.cos(a2);
            si = Math.sin(a2);

            A = [1, B[1] + (A[1] - B[1]) * r / d, B[2] + (A[2] - B[2]) * r / d];

            mat = [
                [1, 0, 0],
                [B[1] - 0.5 * B[1] * co + 0.5 * B[2] * si, co * 0.5, -si * 0.5],
                [B[2] - 0.5 * B[1] * si - 0.5 * B[2] * co, si * 0.5,  co * 0.5]
            ];
            vec = Mat.matVecMult(mat, A);
            vec[1] /= vec[0];
            vec[2] /= vec[0];
            vec[0] /= vec[0];

            d = Geometry.distance(vec, B, 3);
            vec = [vec[0], B[1] + (vec[1] - B[1]) * (r + dx) / d,  B[2] + (vec[2] - B[2]) * (r + dx) / d];

            l_vp.position = Geometry.calcLabelQuadrant(Geometry.rad([1,0], [0,0], vec));

            return new Coords(Const.COORDS_BY_USER, vec, this.board);
        };

        /**
         * Returns the value of the angle in Radians.
         * @memberOf Angle.prototype
         * @name Value
         * @function
         * @returns {Number} The angle value in Radians
         */
        el.Value = function () {
            return Geometry.rad(this.point2, this.point1, this.point3);
        };

        el.methodMap = Type.deepCopy(el.methodMap, {
            Value: 'Value',
            setAngle: 'setAngle',
            free: 'free'
        });

        return el;
    };

    JXG.registerElement('angle', JXG.createAngle);

    /**
     * @class A non-reflex angle is the acute or obtuse instance of an angle.
     * It is defined by a center, one point that
     * defines the radius, and a third point that defines the angle of the sector.
     * @pseudo
     * @name NonReflexAngle
     * @augments Angle
     * @constructor
     * @type Sector
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor sector is a sector of a circle around p1 having measure less than or equal to
     * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
     * @example
     * // Create a non-reflex angle out of three free points
     * var p1 = board.create('point', [5.0, 3.0]),
     *     p2 = board.create('point', [1.0, 0.5]),
     *     p3 = board.create('point', [1.5, 5.0]),
     *
     *     a = board.create('nonreflexangle', [p1, p2, p3], {radius: 2}),
     *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
     * </pre><div class="jxgbox" id="JXGd0ab6d6b-63a7-48b2-8749-b02bb5e744f9" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXGd0ab6d6b-63a7-48b2-8749-b02bb5e744f9', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *     p1 = board.create('point', [5.0, 3.0]),
     *     p2 = board.create('point', [1.0, 0.5]),
     *     p3 = board.create('point', [1.5, 5.0]),
     *
     *     a = board.create('nonreflexangle', [p1, p2, p3], {radius: 2}),
     *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
     * })();
     * </script><pre>
     */
    JXG.createNonreflexAngle = function (board, parents, attributes) {
        var el;

        attributes.selection = 'minor';
        el = JXG.createAngle(board, parents, attributes);

        // Documented in createAngle
        el.Value = function () {
            var v = Geometry.rad(this.point2, this.point1, this.point3);
            return (v < Math.PI) ? v : 2.0 * Math.PI - v;
        };
        return el;
    };

    JXG.registerElement('nonreflexangle', JXG.createNonreflexAngle);

    /**
     * @class A reflex angle is the neither acute nor obtuse instance of an angle.
     * It is defined by a center, one point that
     * defines the radius, and a third point that defines the angle of the sector.
     * @pseudo
     * @name ReflexAngle
     * @augments Angle
     * @constructor
     * @type Sector
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 . Minor sector is a sector of a circle around p1 having measure less than or equal to
     * 180 degrees (pi radians) and starts at p2. The radius is determined by p2, the angle by p3.
     * @example
     * // Create a non-reflex angle out of three free points
     * var p1 = board.create('point', [5.0, 3.0]),
     *     p2 = board.create('point', [1.0, 0.5]),
     *     p3 = board.create('point', [1.5, 5.0]),
     *
     *     a = board.create('reflexangle', [p1, p2, p3], {radius: 2}),
     *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
     * </pre><div class="jxgbox" id="JXGf2a577f2-553d-4f9f-a895-2d6d4b8c60e8" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     * var board = JXG.JSXGraph.initBoard('JXGf2a577f2-553d-4f9f-a895-2d6d4b8c60e8', {boundingbox: [-1, 7, 7, -1], axis: true, showcopyright: false, shownavigation: false}),
     *     p1 = board.create('point', [5.0, 3.0]),
     *     p2 = board.create('point', [1.0, 0.5]),
     *     p3 = board.create('point', [1.5, 5.0]),
     *
     *     a = board.create('reflexangle', [p1, p2, p3], {radius: 2}),
     *     t = board.create('text', [4, 4, function() { return JXG.toFixed(a.Value(), 2); }]);
     * })();
     * </script><pre>
     */
    JXG.createReflexAngle = function (board, parents, attributes) {
        var el;

        attributes.selection = 'major';
        el = JXG.createAngle(board, parents, attributes);

        // Documented in createAngle
        el.Value = function () {
            var v = Geometry.rad(this.point2, this.point1, this.point3);
            return (v >= Math.PI) ? v : 2.0 * Math.PI - v;
        };
        return el;
    };

    JXG.registerElement('reflexangle', JXG.createReflexAngle);

    return {
        createSector: JXG.createSector,
        createCircumcircleSector: JXG.createCircumcircleSector,
        createMinorSector: JXG.createMinorSector,
        createMajorSector: JXG.createMajorSector,
        createAngle: JXG.createAngle,
        createReflexAngle: JXG.createReflexAngle,
        createNonreflexAngle: JXG.createNonreflexAngle
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/*depends:
 jxg
 base/constants
 math/math
 utils/type
 */

/**
 * @fileoverview This file contains code for transformations of geometrical objects.
 */

define('base/transformation',[
    'jxg', 'base/constants', 'math/math', 'utils/type'
], function (JXG, Const, Mat, Type) {

    "use strict";

    /**
     * A transformation consists of a 3x3 matrix, i.e. it is a projective transformation.
     * @class Creates a new transformation object. Do not use this constructor to create a transformation.
     * Use {@link JXG.Board#create} with
     * type {@link Transformation} instead.
     * @constructor
     * @param {JXG.Board} board The board the new circle is drawn on.
     * @param {String} type Can be
     * <ul><li> 'translate'
     * <li> 'scale'
     * <li> 'reflect'
     * <li> 'rotate'
     * <li> 'shear'
     * <li> 'generic'
     * </ul>
     * @param {Object} params The parameters depend on the transformation type
     *
     * <p>
     * Translation matrix:
     * <pre>
     * ( 1  0  0)   ( z )
     * ( a  1  0) * ( x )
     * ( b  0  1)   ( y )
     * </pre>
     *
     * <p>
     * Scale matrix:
     * <pre>
     * ( 1  0  0)   ( z )
     * ( 0  a  0) * ( x )
     * ( 0  0  b)   ( y )
     * </pre>
     *
     * <p>
     * A rotation matrix with angle a (in Radians)
     * <pre>
     * ( 1    0        0      )   ( z )
     * ( 0    cos(a)   -sin(a)) * ( x )
     * ( 0    sin(a)   cos(a) )   ( y )
     * </pre>
     *
     * <p>
     * Shear matrix:
     * <pre>
     * ( 1  0  0)   ( z )
     * ( 0  1  a) * ( x )
     * ( 0  b  1)   ( y )
     * </pre>
     *
     * <p>Generic transformation:
     * <pre>
     * ( a  b  c )   ( z )
     * ( d  e  f ) * ( x )
     * ( g  h  i )   ( y )
     * </pre>
     *
     */
    JXG.Transformation = function (board, type, params) {
        this.elementClass = Const.OBJECT_CLASS_OTHER;
        this.type = Const.OBJECT_TYPE_TRANSFORMATION;
        this.matrix = [
            [1, 0, 0],
            [0, 1, 0],
            [0, 0, 1]
        ];
        this.board = board;
        this.isNumericMatrix = false;
        this.setMatrix(board, type, params);

        this.methodMap = {
            apply: 'apply',
            applyOnce: 'applyOnce',
            bindTo: 'bindTo',
            bind: 'bindTo',
            melt: 'melt'
        };
    };

    JXG.Transformation.prototype = {};

    JXG.extend(JXG.Transformation.prototype, /** @lends JXG.Transformation.prototype */ {
        /**
         * Updates the numerical data for the transformation, i.e. the entry of the subobject matrix.
         * @returns {JXG.Transform} returns pointer to itself
         */
        update: function () {
            return this;
        },

        /**
         * Set the transformation matrix for different types of standard transforms.
         * @param {JXG.Board} board
         * @param {String} type   Transformation type, possible values are
         *                        'translate', 'scale', 'reflect', 'rotate',
         *                        'shear', 'generic'.
         * @param {Array} params Parameters for the various transformation types.
         *
         * <p>These are
         * @param {Array} x,y Shift vector (number or function) in case of 'translate'.
         * @param {Array} scale_x,scale_y Scale vector (number or function) in case of 'scale'.
         * @param {Array} line|point_pair|"four coordinates" In case of 'reflect' the parameters could
         *                be a line, a pair of points or four number (or functions) p_x, p_y, q_x, q_y,
         *                determining a line through points (p_x, p_y) and (q_x, q_y).
         * @param {Array} angle,x,y|angle,[x,y] In case of 'rotate' the parameters are an angle or angle function,
         *                returning the angle in Radians and - optionally - a coordinate pair or a point defining the
         *                rotation center. If the rotation center is not given, the transformation rotates around (0,0).
         * @param {Array} shear_x,shear_y Shear vector (number or function) in case of 'shear'.
         * @param {Array} a,b,c,d,e,f,g,h,i Nine matrix entries (numbers or functions) for a generic
         *                projective transformation  in case of 'generic'.
         *
         * <p>A transformation with a generic matrix looks like:
         * <pre>
         * ( a  b  c )   ( z )
         * ( d  e  f ) * ( x )
         * ( g  h  i )   ( y )
         * </pre>
         *
         */
        setMatrix: function (board, type, params) {
            var i;

            this.isNumericMatrix = true;

            for (i = 0; i < params.length; i++) {
                if (typeof params[i] !== 'number') {
                    this.isNumericMatrix = false;
                    break;
                }
            }

            if (type === 'translate') {
                if (params.length !== 2) {
                    throw new Error("JSXGraph: translate transformation needs 2 parameters.");
                }
                this.evalParam = Type.createEvalFunction(board, params, 2);
                this.update = function () {
                    this.matrix[1][0] = this.evalParam(0);
                    this.matrix[2][0] = this.evalParam(1);
                };
            } else if (type === 'scale') {
                if (params.length !== 2) {
                    throw new Error("JSXGraph: scale transformation needs 2 parameters.");
                }
                this.evalParam = Type.createEvalFunction(board, params, 2);
                this.update = function () {
                    this.matrix[1][1] = this.evalParam(0); // x
                    this.matrix[2][2] = this.evalParam(1); // y
                };
            // Input: line or two points
            } else if (type === 'reflect') {
                // line or two points
                if (params.length < 4) {
                    params[0] = board.select(params[0]);
                }

                // two points
                if (params.length === 2) {
                    params[1] = board.select(params[1]);
                }

                // 4 coordinates [px,py,qx,qy]
                if (params.length === 4) {
                    this.evalParam = Type.createEvalFunction(board, params, 4);
                }

                this.update = function () {
                    var x, y, z, xoff, yoff, d,
                        v, p;
                    // Determine homogeneous coordinates of reflections axis
                    // line
                    if (params.length === 1) {
                        v = params[0].stdform;
                    // two points
                    } else if (params.length === 2) {
                        v = Mat.crossProduct(params[1].coords.usrCoords, params[0].coords.usrCoords);
                    // two points coordinates [px,py,qx,qy]
                    } else if (params.length === 4) {
                        v = Mat.crossProduct(
                            [1, this.evalParam(2), this.evalParam(3)],
                            [1, this.evalParam(0), this.evalParam(1)]
                        );
                    }

                    // Project origin to the line.  This gives a finite point p
                    x = v[1];
                    y = v[2];
                    z = v[0];
                    p = [-z * x, -z * y, x * x + y * y];
                    d = p[2];

                    // Normalize p
                    xoff = p[0] / p[2];
                    yoff = p[1] / p[2];

                    // x, y is the direction of the line
                    x = -v[2];
                    y =  v[1];

                    this.matrix[1][1] = (x * x - y * y) / d;
                    this.matrix[1][2] = 2 * x * y / d;
                    this.matrix[2][1] = this.matrix[1][2];
                    this.matrix[2][2] = -this.matrix[1][1];
                    this.matrix[1][0] = xoff * (1 - this.matrix[1][1]) - yoff * this.matrix[1][2];
                    this.matrix[2][0] = yoff * (1 - this.matrix[2][2]) - xoff * this.matrix[2][1];
                };
            } else if (type === 'rotate') {
                // angle, x, y
                if (params.length === 3) {
                    this.evalParam = Type.createEvalFunction(board, params, 3);
                // angle, p or angle
                } else if (params.length > 0 && params.length <= 2) {
                    this.evalParam = Type.createEvalFunction(board, params, 1);

                    if (params.length === 2 && !Type.isArray(params[1])) {
                        params[1] = board.select(params[1]);
                    }
                }

                this.update = function () {
                    var x, y,
                        beta = this.evalParam(0),
                        co = Math.cos(beta),
                        si = Math.sin(beta);

                    this.matrix[1][1] =  co;
                    this.matrix[1][2] = -si;
                    this.matrix[2][1] =  si;
                    this.matrix[2][2] =  co;

                    // rotate around [x,y] otherwise rotate around [0,0]
                    if (params.length > 1) {
                        if (params.length === 3) {
                            x = this.evalParam(1);
                            y = this.evalParam(2);
                        } else {
                            if (Type.isArray(params[1])) {
                                x = params[1][0];
                                y = params[1][1];
                            } else {
                                x = params[1].X();
                                y = params[1].Y();
                            }
                        }
                        this.matrix[1][0] = x * (1 - co) + y * si;
                        this.matrix[2][0] = y * (1 - co) - x * si;
                    }
                };
            } else if (type === 'shear') {
                if (params.length !== 2) {
                    throw new Error("JSXGraph: shear transformation needs 2 parameters.");
                }

                this.evalParam = Type.createEvalFunction(board, params, 2);
                this.update = function () {
                    this.matrix[1][2] = this.evalParam(0);
                    this.matrix[2][1] = this.evalParam(1);
                };
            } else if (type === 'generic') {
                if (params.length !== 9) {
                    throw new Error("JSXGraph: generic transformation needs 9 parameters.");
                }

                this.evalParam = Type.createEvalFunction(board, params, 9);

                this.update = function () {
                    this.matrix[0][0] = this.evalParam(0);
                    this.matrix[0][1] = this.evalParam(1);
                    this.matrix[0][2] = this.evalParam(2);
                    this.matrix[1][0] = this.evalParam(3);
                    this.matrix[1][1] = this.evalParam(4);
                    this.matrix[1][2] = this.evalParam(5);
                    this.matrix[2][0] = this.evalParam(6);
                    this.matrix[2][1] = this.evalParam(7);
                    this.matrix[2][2] = this.evalParam(8);
                };
            }
        },

        /**
         * Transform a GeometryElement:
         * First, the transformation matrix is updated, then do the matrix-vector-multiplication.
         * @private
         * @param {JXG.GeometryElement} p element which is transformed
         * @param {String} 'self' Apply the transformation to the initialCoords instead of the coords if this is set.
         * @returns {Array}
         */
        apply: function (p, self) {
            this.update();

            if (Type.exists(self)) {
                return Mat.matVecMult(this.matrix, p.initialCoords.usrCoords);
            }
            return Mat.matVecMult(this.matrix, p.coords.usrCoords);
        },

        /**
         * Applies a transformation once to a GeometryElement or an array of elements.
         * If it is a free point, then it can be dragged around later
         * and will overwrite the transformed coordinates.
         * @param {JXG.Point,Array} p
         */
        applyOnce: function (p) {
            var c, len, i;

            if (!Type.isArray(p)) {
                p = [p];
            }

            len = p.length;

            for (i = 0; i < len; i++) {
                this.update();
                c = Mat.matVecMult(this.matrix, p[i].coords.usrCoords);
                p[i].coords.setCoordinates(Const.COORDS_BY_USER, c);
            }
        },

        /**
         * Binds a transformation to a GeometryElement or an array of elements. In every update of the
         * GeometryElement(s), the transformation is executed. That means, in order to immediately
         * apply the transformation, a call of board.update() has to follow.
         * @param  {Array,JXG.Object} p JXG.Object or array of JXG.Object to
         *                            which the transformation is bound to.
         */
        bindTo: function (p) {
            var i, len;
            if (Type.isArray(p)) {
                len = p.length;

                for (i = 0; i < len; i++) {
                    p[i].transformations.push(this);
                }
            } else {
                p.transformations.push(this);
            }
        },

        /**
         * Unused
         * @deprecated Use setAttribute
         * @param term
         */
        setProperty: function (term) {
            JXG.deprecated('Transformation.setProperty()', 'Transformation.setAttribute()');
        },

        /**
         * Empty method. Unused.
         * @param {Object} term Key-value pairs of the attributes.
         */
        setAttribute: function (term) { },

        /**
         * Combine two transformations to one transformation. This only works if
         * both of transformation matrices consist solely of numbers, and do not
         * contain functions.
         *
         * Multiplies the transformation with a transformation t from the left.
         * i.e. (this) = (t) join (this)
         * @param  {JXG.Transform} t Transformation which is the left multiplicand
         * @returns {JXG.Transform} the transformation object.
         */
        melt: function (t) {
            var res = [], i, len, len0, k, s, j;

            len = t.matrix.length;
            len0 = this.matrix[0].length;

            for (i = 0; i < len; i++) {
                res[i] = [];
            }

            this.update();
            t.update();

            for (i = 0; i < len; i++) {
                for (j = 0; j < len0; j++) {
                    s = 0;
                    for (k = 0; k < len; k++) {
                        s += t.matrix[i][k] * this.matrix[k][j];
                    }
                    res[i][j] = s;
                }
            }

            this.update = function () {
                var len = this.matrix.length,
                    len0 = this.matrix[0].length;

                for (i = 0; i < len; i++) {
                    for (j = 0; j < len0; j++) {
                        this.matrix[i][j] = res[i][j];
                    }
                }
            };
            return this;
        },

        // documented in element.js
        // Not yet, since transformations are not listed in board.objects.
        getParents: function () {
            var p = [[].concat.apply([], this.matrix)];

            if (this.parents.length !== 0) {
                p = this.parents;
            }

            return p;
        }

    });

    /**
     * @class This element is used to provide projective transformations.
     * @pseudo
     * @description A transformation consists of a 3x3 matrix, i.e. it is a projective transformation.
     * @name Transformation
     * @augments JXG.Transformation
     * @constructor
     * @type JXG.Transformation
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {numbers,functions} parameters The parameters depend on the transformation type, supplied as attribute 'type'.
     * Possible transformation types are
     * <ul><li> 'translate'
     * <li> 'scale'
     * <li> 'reflect'
     * <li> 'rotate'
     * <li> 'shear'
     * <li> 'generic'
     * </ul>
     * The transformation matrix then looks like:
     * <p>
     * Translation matrix:
     * <pre>
     * ( 1  0  0)   ( z )
     * ( a  1  0) * ( x )
     * ( b  0  1)   ( y )
     * </pre>
     *
     * <p>
     * Scale matrix:
     * <pre>
     * ( 1  0  0)   ( z )
     * ( 0  a  0) * ( x )
     * ( 0  0  b)   ( y )
     * </pre>
     *
     * <p>
     * A rotation matrix with angle a (in Radians)
     * <pre>
     * ( 1    0        0      )   ( z )
     * ( 0    cos(a)   -sin(a)) * ( x )
     * ( 0    sin(a)   cos(a) )   ( y )
     * </pre>
     *
     * <p>
     * Shear matrix:
     * <pre>
     * ( 1  0  0)   ( z )
     * ( 0  1  a) * ( x )
     * ( 0  b  1)   ( y )
     * </pre>
     *
     * <p>Generic transformation:
     * <pre>
     * ( a  b  c )   ( z )
     * ( d  e  f ) * ( x )
     * ( g  h  i )   ( y )
     * </pre>
     *
     * @see JXG.Transformation#setMatrix
     *
     * @example
     * // The point B is determined by taking twice the vector A from the origin
     *
     * var p0 = board.create('point', [0, 3], {name: 'A'}),
     *     t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type: 'translate'}),
     *     p1 = board.create('point', [p0, t], {color: 'blue'});
     *
     * </pre><div class="jxgbox" id="JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG14167b0c-2ad3-11e5-8dd9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var p0 = board.create('point', [0, 3], {name: 'A'}),
     *         t = board.create('transform', [function(){ return p0.X(); }, "Y(A)"], {type:'translate'}),
     *         p1 = board.create('point', [p0, t], {color: 'blue'});
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * // The point B is the result of scaling the point A with factor 2 in horizontal direction
     * // and with factor 0.5 in vertical direction.
     *
     * var p1 = board.create('point', [1, 1]),
     *     t = board.create('transform', [2, 0.5], {type: 'scale'}),
     *     p2 = board.create('point', [p1, t], {color: 'blue'});
     *
     * </pre><div class="jxgbox" id="JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGa6827a72-2ad3-11e5-8dd9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var p1 = board.create('point', [1, 1]),
     *         t = board.create('transform', [2, 0.5], {type: 'scale'}),
     *         p2 = board.create('point', [p1, t], {color: 'blue'});
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * // The point B is rotated around C which gives point D. The angle is determined
     * // by the vertical height of point A.
     *
     * var p0 = board.create('point', [0, 3], {name: 'A'}),
     *     p1 = board.create('point', [1, 1]),
     *     p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
     *
     *     // angle, rotation center:
     *     t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
     *     p3 = board.create('point', [p1, t], {color: 'blue'});
     *
     * </pre><div class="jxgbox" id="JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG747cf11e-2ad4-11e5-8dd9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var p0 = board.create('point', [0, 3], {name: 'A'}),
     *         p1 = board.create('point', [1, 1]),
     *         p2 = board.create('point', [2, 1], {name:'C', fixed: true}),
     *
     *         // angle, rotation center:
     *         t = board.create('transform', ['Y(A)', p2], {type: 'rotate'}),
     *         p3 = board.create('point', [p1, t], {color: 'blue'});
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * // A concatenation of several transformations.
     * var p1 = board.create('point', [1, 1]),
     *     t1 = board.create('transform', [-2, -1], {type: 'translate'}),
     *     t2 = board.create('transform', [Math.PI/4], {type: 'rotate'}),
     *     t3 = board.create('transform', [2, 1], {type: 'translate'}),
     *     p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
     *
     * </pre><div class="jxgbox" id="JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGf516d3de-2ad5-11e5-8dd9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var p1 = board.create('point', [1, 1]),
     *         t1 = board.create('transform', [-2, -1], {type:'translate'}),
     *         t2 = board.create('transform', [Math.PI/4], {type:'rotate'}),
     *         t3 = board.create('transform', [2, 1], {type:'translate'}),
     *         p2 = board.create('point', [p1, [t1, t2, t3]], {color: 'blue'});
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * // Reflection of point A
     * var p1 = board.create('point', [1, 1]),
     *     p2 = board.create('point', [1, 3]),
     *     p3 = board.create('point', [-2, 0]),
     *     l = board.create('line', [p2, p3]),
     *     t = board.create('transform', [l], {type: 'reflect'}),  // Possible are l, l.id, l.name
     *     p4 = board.create('point', [p1, t], {color: 'blue'});
     *
     * </pre><div class="jxgbox" id="JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG6f374a04-2ad6-11e5-8dd9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var p1 = board.create('point', [1, 1]),
     *         p2 = board.create('point', [1, 3]),
     *         p3 = board.create('point', [-2, 0]),
     *         l = board.create('line', [p2, p3]),
     *         t = board.create('transform', [l], {type:'reflect'}),  // Possible are l, l.id, l.name
     *         p4 = board.create('point', [p1, t], {color: 'blue'});
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * // One time application of a transform to points A, B
     * var p1 = board.create('point', [1, 1]),
     *     p2 = board.create('point', [1, 1]),
     *     t = board.create('transform', [3, 2], {type: 'shear'});
     * t.applyOnce([p1, p2]);
     *
     * </pre><div class="jxgbox" id="JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGb6cee1c4-2ad6-11e5-8dd9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var p1 = board.create('point', [1, 1]),
     *         p2 = board.create('point', [-1, -2]),
     *         t = board.create('transform', [3, 2], {type: 'shear'});
     *     t.applyOnce([p1, p2]);
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * // Construct a square of side length 2 with the
     * // help of transformations
     *     var sq = [],
     *         right = board.create('transform', [2, 0], {type: 'translate'}),
     *         up = board.create('transform', [0, 2], {type: 'translate'}),
     *         pol, rot, p0;
     *
     *     // The first point is free
     *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
     *
     *     // Construct the other free points by transformations
     *     sq[1] = board.create('point', [sq[0], right]),
     *     sq[2] = board.create('point', [sq[0], [right, up]]),
     *     sq[3] = board.create('point', [sq[0], up]),
     *
     *     // Polygon through these four points
     *     pol = board.create('polygon', sq, {
     *             fillColor:'blue',
     *             gradient:'radial',
     *             gradientsecondcolor:'white',
     *             gradientSecondOpacity:'0'
     *     }),
     *
     *     p0 = board.create('point', [0, 3], {name: 'angle'}),
     *     // Rotate the square around point sq[0] by dragging A
     *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
     *
     *     // Apply the rotation to all but the first point of the square
     *     rot.bindTo(sq.slice(1));
     *
     * </pre><div class="jxgbox" id="JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGc7f9097e-2ad7-11e5-8dd9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     // Construct a square of side length 2 with the
     *     // help of transformations
     *     var sq = [],
     *         right = board.create('transform', [2, 0], {type: 'translate'}),
     *         up = board.create('transform', [0, 2], {type: 'translate'}),
     *         pol, rot, p0;
     *
     *     // The first point is free
     *     sq[0] = board.create('point', [0, 0], {name: 'Drag me'}),
     *
     *     // Construct the other free points by transformations
     *     sq[1] = board.create('point', [sq[0], right]),
     *     sq[2] = board.create('point', [sq[0], [right, up]]),
     *     sq[3] = board.create('point', [sq[0], up]),
     *
     *     // Polygon through these four points
     *     pol = board.create('polygon', sq, {
     *             fillColor:'blue',
     *             gradient:'radial',
     *             gradientsecondcolor:'white',
     *             gradientSecondOpacity:'0'
     *     }),
     *
     *     p0 = board.create('point', [0, 3], {name: 'angle'}),
     *     // Rotate the square around point sq[0] by dragging A
     *     rot = board.create('transform', ['Y(angle)', sq[0]], {type: 'rotate'});
     *
     *     // Apply the rotation to all but the first point of the square
     *     rot.bindTo(sq.slice(1));
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createTransform = function (board, parents, attributes) {
        return new JXG.Transformation(board, attributes.type, parents);
    };

    JXG.registerElement('transform', JXG.createTransform);

    return {
        Transformation: JXG.Transformation,
        createTransform: JXG.createTransform
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/math
 math/geometry
 math/numerics
 math/statistics
 math/symbolic
 base/composition
 base/coords
 base/constants
 utils/type
  elements:
   line
   circle
   transform
   point
   glider
   text
   curve
 */

/**
 * @fileoverview This file contains our composition elements, i.e. these elements are mostly put together
 * from one or more {@link JXG.GeometryElement} but with a special meaning. E.g. the midpoint element is contained here
 * and this is just a {@link JXG.Point} with coordinates dependent from two other points. Currently in this file the
 * following compositions can be found: <ul>
 *   <li>{@link Arrowparallel} (currently private)</li>
 *   <li>{@link Bisector}</li>
 *   <li>{@link Msector}</li>
 *   <li>{@link Circumcircle}</li>
 *   <li>{@link Circumcirclemidpoint}</li>
 *   <li>{@link Integral}</li>
 *   <li>{@link Midpoint}</li>
 *   <li>{@link Mirrorpoint}</li>
 *   <li>{@link Normal}</li>
 *   <li>{@link Orthogonalprojection}</li>
 *   <li>{@link Parallel}</li>
 *   <li>{@link Perpendicular}</li>
 *   <li>{@link Perpendicularpoint}</li>
 *   <li>{@link Perpendicularsegment}</li>
 *   <li>{@link Reflection}</li></ul>
 */

define('element/composition',[
    'jxg', 'math/math', 'math/geometry', 'math/numerics', 'base/coords',
    'utils/type', 'base/constants', 'base/point', 'base/line', 'base/circle', 'base/transformation',
    'base/composition', 'base/curve', 'base/polygon'
], function (JXG, Mat, Geometry, Numerics, Coords,
    Type, Const, Point, Line, Circle, Transform,
    Composition, Curve, Polygon) {

    "use strict";

    /**
     * @class This is used to construct a point that is the orthogonal projection of a point to a line.
     * @pseudo
     * @description An orthogonal projection is given by a point and a line. It is determined by projecting the given point
     * orthogonal onto the given line.
     * @constructor
     * @name Orthogonalprojection
     * @type JXG.Point
     * @augments JXG.Point
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line_JXG.Point} p,l The constructed point is the orthogonal projection of p onto l.
     * @example
     * var p1 = board.create('point', [0.0, 4.0]);
     * var p2 = board.create('point', [6.0, 1.0]);
     * var l1 = board.create('line', [p1, p2]);
     * var p3 = board.create('point', [3.0, 3.0]);
     *
     * var pp1 = board.create('orthogonalprojection', [p3, l1]);
     * </pre><div class="jxgbox" id="JXG7708b215-39fa-41b6-b972-19d73d77d791" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var ppex1_board = JXG.JSXGraph.initBoard('JXG7708b215-39fa-41b6-b972-19d73d77d791', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var ppex1_p1 = ppex1_board.create('point', [0.0, 4.0]);
     *   var ppex1_p2 = ppex1_board.create('point', [6.0, 1.0]);
     *   var ppex1_l1 = ppex1_board.create('line', [ppex1_p1, ppex1_p2]);
     *   var ppex1_p3 = ppex1_board.create('point', [3.0, 3.0]);
     *   var ppex1_pp1 = ppex1_board.create('orthogonalprojection', [ppex1_p3, ppex1_l1]);
     * </script><pre>
     */
    JXG.createOrthogonalProjection = function (board, parents, attributes) {
        var l, p, t, attr;

        parents[0] = board.select(parents[0]);
        parents[1] = board.select(parents[1]);

        if (Type.isPointType(board, parents[0]) && parents[1].elementClass === Const.OBJECT_CLASS_LINE) {
            p = Type.providePoints(board, [parents[0]], attributes, 'point')[0];
            l = parents[1];
        } else if (Type.isPointType(board, parents[1]) && parents[0].elementClass === Const.OBJECT_CLASS_LINE) {
            p = Type.providePoints(board, [parents[1]], attributes, 'point')[0];
            l = parents[0];
        } else {
            throw new Error("JSXGraph: Can't create perpendicular point with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [point,line]");
        }

        attr = Type.copyAttributes(attributes, board.options, 'orthogonalprojection');

        t = board.create('point', [
            function () {
                return Geometry.projectPointToLine(p, l, board);
            }
        ], attr);

        if (Type.exists(p._is_new)) {
            t.addChild(p);
            delete p._is_new;
        } else {
            p.addChild(t);
        }
        l.addChild(t);

        t.elType = 'orthogonalprojection';
        t.setParents([p.id, t.id]);

        t.update();

        /**
         * Used to generate a polynomial for the orthogonal projection
         * @name Orthogonalprojection#generatePolynomial
         * @returns {Array} An array containing the generated polynomial.
         * @private
         */
         t.generatePolynomial = function () {
            /*
             *  Perpendicular takes point P and line L and creates point T and line M:
             *
             *                          | M
             *                          |
             *                          x P (p1,p2)
             *                          |
             *                          |
             *  L                       |
             *  ----------x-------------x------------------------x--------
             *            A (a1,a2)     |T (t1,t2)               B (b1,b2)
             *                          |
             *                          |
             *
             * So we have two conditions:
             *
             *   (a)  AT  || TB          (collinearity condition)
             *   (b)  PT _|_ AB          (orthogonality condition)
             *
             *      a2-t2       t2-b2
             *     -------  =  -------           (1)
             *      a1-t1       t1-b1
             *
             *      p2-t2         a1-b1
             *     -------  =  - -------         (2)
             *      p1-t1         a2-b2
             *
             * Multiplying (1) and (2) with denominators and simplifying gives
             *
             *    a2t1 - a2b1 + t2b1 - a1t2 + a1b2 - t1b2 = 0                  (1')
             *
             *    p2a2 - p2b2 - t2a2 + t2b2 + p1a1 - p1b1 - t1a1 + t1b1 = 0    (2')
             *
             */

            var a1 = l.point1.symbolic.x,
                a2 = l.point1.symbolic.y,
                b1 = l.point2.symbolic.x,
                b2 = l.point2.symbolic.y,

                p1 = p.symbolic.x,
                p2 = p.symbolic.y,
                t1 = t.symbolic.x,
                t2 = t.symbolic.y,

                poly1 = '(' + a2 + ')*(' + t1 + ')-(' + a2 + ')*(' + b1 + ')+(' + t2 + ')*(' + b1 + ')-(' +
                    a1 + ')*(' + t2 + ')+(' + a1 + ')*(' + b2 + ')-(' + t1 + ')*(' + b2 + ')',
                poly2 = '(' + p2 + ')*(' + a2 + ')-(' + p2 + ')*(' + b2 + ')-(' + t2 + ')*(' + a2 + ')+(' +
                    t2 + ')*(' + b2 + ')+(' + p1 + ')*(' + a1 + ')-(' + p1 + ')*(' + b1 + ')-(' + t1 + ')*(' +
                    a1 + ')+(' + t1 + ')*(' + b1 + ')';

            return [poly1, poly2];
        };

        return t;
    };

    /**

     * @class This element is used to provide a constructor for a perpendicular.
     * @pseudo
     * @description  A perpendicular is a composition of two elements: a line and a point. The line is orthogonal
     * to a given line and contains a given point.
     * @name Perpendicular
     * @constructor
     * @type JXG.Line
     * @augments Segment
     * @returns A {@link JXG.Line} object through the given point that is orthogonal to the given line.
     * @throws {Error} If the elements cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line_JXG.Point} l,p The perpendicular line will be orthogonal to l and
     * will contain p.
     * @example
     * // Create a perpendicular
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     * var l1 = board.create('line', [p1, p2]);
     *
     * var p3 = board.create('point', [3.0, 3.0]);
     * var perp1 = board.create('perpendicular', [l1, p3]);
     * </pre><div class="jxgbox" id="JXGd5b78842-7b27-4d37-b608-d02519e6cd03" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var pex1_board = JXG.JSXGraph.initBoard('JXGd5b78842-7b27-4d37-b608-d02519e6cd03', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var pex1_p1 = pex1_board.create('point', [0.0, 2.0]);
     *   var pex1_p2 = pex1_board.create('point', [2.0, 1.0]);
     *   var pex1_l1 = pex1_board.create('line', [pex1_p1, pex1_p2]);
     *   var pex1_p3 = pex1_board.create('point', [3.0, 3.0]);
     *   var pex1_perp1 = pex1_board.create('perpendicular', [pex1_l1, pex1_p3]);
     * </script><pre>
     */
    JXG.createPerpendicular = function (board, parents, attributes) {
        var p, l, pd, attr;

        parents[0] = board.select(parents[0]);
        parents[1] = board.select(parents[1]);

        if (Type.isPointType(board, parents[0]) && parents[1].elementClass === Const.OBJECT_CLASS_LINE) {
            l = parents[1];
            p = Type.providePoints(board, [parents[0]], attributes, 'point')[0];
        } else if (Type.isPointType(board, parents[1]) && parents[0].elementClass === Const.OBJECT_CLASS_LINE) {
            l = parents[0];
            p = Type.providePoints(board, [parents[1]], attributes, 'point')[0];
        } else {
            throw new Error("JSXGraph: Can't create perpendicular with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [line,point]");
        }

        attr = Type.copyAttributes(attributes, board.options, 'perpendicular');
        pd = Line.createLine(board, [
            function () {
                return l.stdform[2] * p.X() - l.stdform[1] * p.Y();
            },
            function () {
                return -l.stdform[2] * p.Z();
            },
            function () {
                return l.stdform[1] * p.Z();
            }
        ], attr);

        pd.elType = 'perpendicular';
        pd.setParents([l.id, p.id]);

        if (Type.exists(p._is_new)) {
            pd.addChild(p);
            delete p._is_new;
        } else {
            p.addChild(pd);
        }
        l.addChild(pd);

        return pd;
    };

    /**
     * @class This is used to construct a perpendicular point.
     * @pseudo
     * @description A perpendicular point is given by a point and a line. It is determined by projecting the given point
     * orthogonal onto the given line. This element should be used in GEONExTReader only. All other applications should
     * use orthogonal projection {@link Orthogonalprojection}.
     * @constructor
     * @name PerpendicularPoint
     * @type JXG.Point
     * @augments JXG.Point
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line_JXG.Point} p,l The constructed point is the orthogonal projection of p onto l.
     * @example
     * var p1 = board.create('point', [0.0, 4.0]);
     * var p2 = board.create('point', [6.0, 1.0]);
     * var l1 = board.create('line', [p1, p2]);
     * var p3 = board.create('point', [3.0, 3.0]);
     *
     * var pp1 = board.create('perpendicularpoint', [p3, l1]);
     * </pre><div class="jxgbox" id="JXGded148c9-3536-44c0-ab81-1bb8fa48f3f4" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var ppex1_board = JXG.JSXGraph.initBoard('JXGded148c9-3536-44c0-ab81-1bb8fa48f3f4', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var ppex1_p1 = ppex1_board.create('point', [0.0, 4.0]);
     *   var ppex1_p2 = ppex1_board.create('point', [6.0, 1.0]);
     *   var ppex1_l1 = ppex1_board.create('line', [ppex1_p1, ppex1_p2]);
     *   var ppex1_p3 = ppex1_board.create('point', [3.0, 3.0]);
     *   var ppex1_pp1 = ppex1_board.create('perpendicularpoint', [ppex1_p3, ppex1_l1]);
     * </script><pre>
     */
    JXG.createPerpendicularPoint = function (board, parents, attributes) {
        var l, p, t;

        parents[0] = board.select(parents[0]);
        parents[1] = board.select(parents[1]);
        if (Type.isPointType(board, parents[0]) && parents[1].elementClass === Const.OBJECT_CLASS_LINE) {
            p = Type.providePoints(board, [parents[0]], attributes, 'point')[0];
            l = parents[1];
        } else if (Type.isPointType(board, parents[1]) && parents[0].elementClass === Const.OBJECT_CLASS_LINE) {
            p = Type.providePoints(board, [parents[1]], attributes, 'point')[0];
            l = parents[0];
        } else {
            throw new Error("JSXGraph: Can't create perpendicular point with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [point,line]");
        }

        t = board.create('point', [
            function () {
                return Geometry.perpendicular(l, p, board)[0];
            }
        ], attributes);

        if (Type.exists(p._is_new)) {
            t.addChild(p);
            delete p._is_new;
        } else {
            p.addChild(t);
        }
        l.addChild(t);

        t.elType = 'perpendicularpoint';
        t.setParents([p.id, l.id]);

        t.update();

        /**
         * Used to generate a polynomial for the perpendicular point
         * @name PerpendicularPoint#generatePolynomial
         * @returns {Array} An array containing the generated polynomial.
         * @private
         */
         t.generatePolynomial = function () {
            /*
             *  Perpendicular takes point P and line L and creates point T and line M:
             *
             *                          | M
             *                          |
             *                          x P (p1,p2)
             *                          |
             *                          |
             *  L                       |
             *  ----------x-------------x------------------------x--------
             *            A (a1,a2)     |T (t1,t2)               B (b1,b2)
             *                          |
             *                          |
             *
             * So we have two conditions:
             *
             *   (a)  AT  || TB          (collinearity condition)
             *   (b)  PT _|_ AB          (orthogonality condition)
             *
             *      a2-t2       t2-b2
             *     -------  =  -------           (1)
             *      a1-t1       t1-b1
             *
             *      p2-t2         a1-b1
             *     -------  =  - -------         (2)
             *      p1-t1         a2-b2
             *
             * Multiplying (1) and (2) with denominators and simplifying gives
             *
             *    a2t1 - a2b1 + t2b1 - a1t2 + a1b2 - t1b2 = 0                  (1')
             *
             *    p2a2 - p2b2 - t2a2 + t2b2 + p1a1 - p1b1 - t1a1 + t1b1 = 0    (2')
             *
             */
            var a1 = l.point1.symbolic.x,
                a2 = l.point1.symbolic.y,
                b1 = l.point2.symbolic.x,
                b2 = l.point2.symbolic.y,
                p1 = p.symbolic.x,
                p2 = p.symbolic.y,
                t1 = t.symbolic.x,
                t2 = t.symbolic.y,

                poly1 = '(' + a2 + ')*(' + t1 + ')-(' + a2 + ')*(' + b1 + ')+(' + t2 + ')*(' + b1 + ')-(' +
                    a1 + ')*(' + t2 + ')+(' + a1 + ')*(' + b2 + ')-(' + t1 + ')*(' + b2 + ')',
                poly2 = '(' + p2 + ')*(' + a2 + ')-(' + p2 + ')*(' + b2 + ')-(' + t2 + ')*(' + a2 + ')+(' +
                    t2 + ')*(' + b2 + ')+(' + p1 + ')*(' + a1 + ')-(' + p1 + ')*(' + b1 + ')-(' + t1 + ')*(' +
                    a1 + ')+(' + t1 + ')*(' + b1 + ')';

            return [poly1, poly2];
        };

        return t;
    };

    /**
     * @class This element is used to provide a constructor for a perpendicular segment.
     * @pseudo
     * @description  A perpendicular is a composition of two elements: a line segment and a point. The line segment is orthogonal
     * to a given line and contains a given point and meets the given line in the perpendicular point.
     * @name PerpendicularSegment
     * @constructor
     * @type JXG.Line
     * @augments Segment
     * @returns An array containing two elements: A {@link JXG.Line} object in the first component and a
     * {@link JXG.Point} element in the second component. The line segment is orthogonal to the given line and meets it
     * in the returned point.
     * @throws {Error} If the elements cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line_JXG.Point} l,p The perpendicular line will be orthogonal to l and
     * will contain p. The perpendicular point is the intersection point of the two lines.
     * @example
     * // Create a perpendicular
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     * var l1 = board.create('line', [p1, p2]);
     *
     * var p3 = board.create('point', [3.0, 3.0]);
     * var perp1 = board.create('perpendicularsegment', [l1, p3]);
     * </pre><div class="jxgbox" id="JXG037a6eb2-781d-4b71-b286-763619a63f22" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var pex1_board = JXG.JSXGraph.initBoard('JXG037a6eb2-781d-4b71-b286-763619a63f22', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var pex1_p1 = pex1_board.create('point', [0.0, 2.0]);
     *   var pex1_p2 = pex1_board.create('point', [2.0, 1.0]);
     *   var pex1_l1 = pex1_board.create('line', [pex1_p1, pex1_p2]);
     *   var pex1_p3 = pex1_board.create('point', [3.0, 3.0]);
     *   var pex1_perp1 = pex1_board.create('perpendicularsegment', [pex1_l1, pex1_p3]);
     * </script><pre>
     */
    JXG.createPerpendicularSegment = function (board, parents, attributes) {
        var p, l, pd, t, attr;

        parents[0] = board.select(parents[0]);
        parents[1] = board.select(parents[1]);
        if (Type.isPointType(board, parents[0]) && parents[1].elementClass === Const.OBJECT_CLASS_LINE) {
            l = parents[1];
            p = Type.providePoints(board, [parents[0]], attributes, 'point')[0];
        } else if (Type.isPointType(board, parents[1]) && parents[0].elementClass === Const.OBJECT_CLASS_LINE) {
            l = parents[0];
            p = Type.providePoints(board, [parents[1]], attributes, 'point')[0];
        } else {
            throw new Error("JSXGraph: Can't create perpendicular with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [line,point]");
        }
        attr = Type.copyAttributes(attributes, board.options, 'perpendicularsegment', 'point');
        t = JXG.createPerpendicularPoint(board, [l, p], attr);
        t.dump = false;

        if (!Type.exists(attributes.layer)) {
            attributes.layer = board.options.layer.line;
        }

        attr = Type.copyAttributes(attributes, board.options, 'perpendicularsegment');
        pd = Line.createLine(board, [
            function () {
                return (Geometry.perpendicular(l, p, board)[1] ? [t, p] : [p, t]);
            }
        ], attr);

        /**
         * Helper point
         * @memberOf PerpendicularSegment.prototype
         * @type PerpendicularPoint
         * @name point
         */
        pd.point = t;

        if (Type.exists(p._is_new)) {
            pd.addChild(p);
            delete p._is_new;
        } else {
            p.addChild(pd);
        }
        l.addChild(pd);

        pd.elType = 'perpendicularsegment';
        pd.setParents([p.id, l.id]);
        pd.subs = {
            point: t
        };
        pd.inherits.push(t);

        return pd;
    };

    /**
     * @class The midpoint element constructs a point in the middle of two given points.
     * @pseudo
     * @description A midpoint is given by two points. It is collinear to the given points and the distance
     * is the same to each of the given points, i.e. it is in the middle of the given points.
     * @constructor
     * @name Midpoint
     * @type JXG.Point
     * @augments JXG.Point
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point} p1,p2 The constructed point will be in the middle of p1 and p2.
     * @param {JXG.Line} l The midpoint will be in the middle of {@link JXG.Line#point1} and {@link JXG.Line#point2} of
     * the given line l.
     * @example
     * // Create base elements: 2 points and 1 line
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     * var l1 = board.create('segment', [[0.0, 3.0], [3.0, 3.0]]);
     *
     * var mp1 = board.create('midpoint', [p1, p2]);
     * var mp2 = board.create('midpoint', [l1]);
     * </pre><div class="jxgbox" id="JXG7927ef86-24ae-40cc-afb0-91ff61dd0de7" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var mpex1_board = JXG.JSXGraph.initBoard('JXG7927ef86-24ae-40cc-afb0-91ff61dd0de7', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var mpex1_p1 = mpex1_board.create('point', [0.0, 2.0]);
     *   var mpex1_p2 = mpex1_board.create('point', [2.0, 1.0]);
     *   var mpex1_l1 = mpex1_board.create('segment', [[0.0, 3.0], [3.0, 3.0]]);
     *   var mpex1_mp1 = mpex1_board.create('midpoint', [mpex1_p1, mpex1_p2]);
     *   var mpex1_mp2 = mpex1_board.create('midpoint', [mpex1_l1]);
     * </script><pre>
     */
    JXG.createMidpoint = function (board, parents, attributes) {
        var a, b, t, i,
            attr;

        for (i = 0; i < parents.length; ++i) {
            parents[i] = board.select(parents[i]);
        }
        if (parents.length === 2 && Type.isPointType(board, parents[0]) && Type.isPointType(board, parents[1])) {
            parents = Type.providePoints(board, parents, attributes, 'point');
            a = parents[0];
            b = parents[1];
        } else if (parents.length === 1 && parents[0].elementClass === Const.OBJECT_CLASS_LINE) {
            a = parents[0].point1;
            b = parents[0].point2;
        } else {
            throw new Error("JSXGraph: Can't create midpoint." +
                "\nPossible parent types: [point,point], [line]");
        }

        attr = Type.copyAttributes(attributes, board.options, 'midpoint');
        t = board.create('point', [
            function () {
                var x = a.coords.usrCoords[1] + b.coords.usrCoords[1];
                if (isNaN(x) || Math.abs(a.coords.usrCoords[0]) < Mat.eps || Math.abs(b.coords.usrCoords[0]) < Mat.eps) {
                    return NaN;
                }

                return x * 0.5;
            },
            function () {
                var y = a.coords.usrCoords[2] + b.coords.usrCoords[2];
                if (isNaN(y) || Math.abs(a.coords.usrCoords[0]) < Mat.eps || Math.abs(b.coords.usrCoords[0]) < Mat.eps) {
                    return NaN;
                }

                return y * 0.5;
            }], attr);
        if (Type.exists(a._is_new)) {
            t.addChild(a);
            delete a._is_new;
        } else {
            a.addChild(t);
        }
        if (Type.exists(b._is_new)) {
            t.addChild(b);
            delete b._is_new;
        } else {
            b.addChild(t);
        }




        t.elType = 'midpoint';
        t.setParents([a.id, b.id]);

        t.prepareUpdate().update();

        /**
         * Used to generate a polynomial for the midpoint.
         * @name Midpoint#generatePolynomial
         * @returns {Array} An array containing the generated polynomial.
         * @private
         */
         t.generatePolynomial = function () {
            /*
             *  Midpoint takes two point A and B or line L (with points P and Q) and creates point T:
             *
             *  L (not necessarily)
             *  ----------x------------------x------------------x--------
             *            A (a1,a2)          T (t1,t2)          B (b1,b2)
             *
             * So we have two conditions:
             *
             *   (a)   AT  ||  TB           (collinearity condition)
             *   (b)  [AT] == [TB]          (equidistant condition)
             *
             *      a2-t2       t2-b2
             *     -------  =  -------                                         (1)
             *      a1-t1       t1-b1
             *
             *     (a1 - t1)^2 + (a2 - t2)^2 = (b1 - t1)^2 + (b2 - t2)^2       (2)
             *
             *
             * Multiplying (1) with denominators and simplifying (1) and (2) gives
             *
             *    a2t1 - a2b1 + t2b1 - a1t2 + a1b2 - t1b2 = 0                      (1')
             *
             *    a1^2 - 2a1t1 + a2^2 - 2a2t2 - b1^2 + 2b1t1 - b2^2 + 2b2t2 = 0    (2')
             *
             */
            var a1 = a.symbolic.x,
                a2 = a.symbolic.y,
                b1 = b.symbolic.x,
                b2 = b.symbolic.y,
                t1 = t.symbolic.x,
                t2 = t.symbolic.y,

                poly1 = '(' + a2 + ')*(' + t1 + ')-(' + a2 + ')*(' + b1 + ')+(' + t2 + ')*(' + b1 + ')-(' +
                    a1 + ')*(' + t2 + ')+(' + a1 + ')*(' + b2 + ')-(' + t1 + ')*(' + b2 + ')',
                poly2 = '(' + a1 + ')^2 - 2*(' + a1 + ')*(' + t1 + ')+(' + a2 + ')^2-2*(' + a2 + ')*(' +
                    t2 + ')-(' + b1 + ')^2+2*(' + b1 + ')*(' + t1 + ')-(' + b2 + ')^2+2*(' + b2 + ')*(' + t2 + ')';

            return [poly1, poly2];
        };

        return t;
    };

    /**
     * @class This element is used to construct a parallel point.
     * @pseudo
     * @description A parallel point is given by three points. Taking the Euclidean vector from the first to the
     * second point, the parallel point is determined by adding that vector to the third point.
     * The line determined by the first two points is parallel to the line determined by the third point and the constructed point.
     * @constructor
     * @name Parallelpoint
     * @type JXG.Point
     * @augments JXG.Point
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 Taking the Euclidean vector <tt>v=p2-p1</tt> the parallel point is determined by
     * <tt>p4 = p3+v</tt>
     * @param {JXG.Line_JXG.Point} l,p The resulting point will together with p specify a line which is parallel to l.
     * @example
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     * var p3 = board.create('point', [3.0, 3.0]);
     *
     * var pp1 = board.create('parallelpoint', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXG488c4be9-274f-40f0-a469-c5f70abe1f0e" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var ppex1_board = JXG.JSXGraph.initBoard('JXG488c4be9-274f-40f0-a469-c5f70abe1f0e', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var ppex1_p1 = ppex1_board.create('point', [0.0, 2.0]);
     *   var ppex1_p2 = ppex1_board.create('point', [2.0, 1.0]);
     *   var ppex1_p3 = ppex1_board.create('point', [3.0, 3.0]);
     *   var ppex1_pp1 = ppex1_board.create('parallelpoint', [ppex1_p1, ppex1_p2, ppex1_p3]);
     * </script><pre>
     */
    JXG.createParallelPoint = function (board, parents, attributes) {
        var a, b, c, p, i;

        for (i = 0; i < parents.length; ++i) {
            parents[i] = board.select(parents[i]);
        }
        if (parents.length === 3 &&
                Type.isPointType(board, parents[0]) &&
                Type.isPointType(board, parents[1]) &&
                Type.isPointType(board, parents[2])) {
            parents = Type.providePoints(board, parents, attributes, 'point');
            a = parents[0];
            b = parents[1];
            c = parents[2];
        } else if (Type.isPointType(board, parents[0]) &&
                parents[1].elementClass === Const.OBJECT_CLASS_LINE) {
            c = Type.providePoints(board, [parents[0]], attributes, 'point')[0];
            a = parents[1].point1;
            b = parents[1].point2;
        } else if (Type.isPointType(board, parents[1]) &&
                parents[0].elementClass === Const.OBJECT_CLASS_LINE) {
            c = Type.providePoints(board, [parents[1]], attributes, 'point')[0];
            a = parents[0].point1;
            b = parents[0].point2;
        } else {
            throw new Error("JSXGraph: Can't create parallel point with parent types '" +
                (typeof parents[0]) + "', '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
                "\nPossible parent types: [line,point], [point,point,point]");
        }

        p = board.create('point', [
            function () {
                return c.coords.usrCoords[1] + b.coords.usrCoords[1] - a.coords.usrCoords[1];
            },
            function () {
                return c.coords.usrCoords[2] + b.coords.usrCoords[2] - a.coords.usrCoords[2];
            }
        ], attributes);

        // required for algorithms requiring dependencies between elements
        if (Type.exists(a._is_new)) {
            p.addChild(a);
            delete a._is_new;
        } else {
            a.addChild(p);
        }
        if (Type.exists(b._is_new)) {
            p.addChild(b);
            delete b._is_new;
        } else {
            b.addChild(p);
        }
        if (Type.exists(c._is_new)) {
            p.addChild(c);
            delete c._is_new;
        } else {
            c.addChild(p);
        }

        p.elType = 'parallelpoint';
        p.setParents([a.id, b.id, c.id]);

        // required to set the coordinates because functions are considered as constraints. hence, the coordinates get set first after an update.
        // can be removed if the above issue is resolved.
        p.prepareUpdate().update();

        p.generatePolynomial = function () {
            /*
             *  Parallelpoint takes three points A, B and C or line L (with points B and C) and creates point T:
             *
             *
             *                     C (c1,c2)                             T (t1,t2)
             *                      x                                     x
             *                     /                                     /
             *                    /                                     /
             *                   /                                     /
             *                  /                                     /
             *                 /                                     /
             *                /                                     /
             *               /                                     /
             *              /                                     /
             *  L (opt)    /                                     /
             *  ----------x-------------------------------------x--------
             *            A (a1,a2)                             B (b1,b2)
             *
             * So we have two conditions:
             *
             *   (a)   CT  ||  AB           (collinearity condition I)
             *   (b)   BT  ||  AC           (collinearity condition II)
             *
             * The corresponding equations are
             *
             *    (b2 - a2)(t1 - c1) - (t2 - c2)(b1 - a1) = 0         (1)
             *    (t2 - b2)(a1 - c1) - (t1 - b1)(a2 - c2) = 0         (2)
             *
             * Simplifying (1) and (2) gives
             *
             *    b2t1 - b2c1 - a2t1 + a2c1 - t2b1 + t2a1 + c2b1 - c2a1 = 0      (1')
             *    t2a1 - t2c1 - b2a1 + b2c1 - t1a2 + t1c2 + b1a2 - b1c2 = 0      (2')
             *
             */
            var a1 = a.symbolic.x,
                a2 = a.symbolic.y,
                b1 = b.symbolic.x,
                b2 = b.symbolic.y,
                c1 = c.symbolic.x,
                c2 = c.symbolic.y,
                t1 = p.symbolic.x,
                t2 = p.symbolic.y,

                poly1 =  '(' + b2 + ')*(' + t1 + ')-(' + b2 + ')*(' + c1 + ')-(' + a2 + ')*(' + t1 + ')+(' +
                    a2 + ')*(' + c1 + ')-(' + t2 + ')*(' + b1 + ')+(' + t2 + ')*(' + a1 + ')+(' + c2 + ')*(' +
                    b1 + ')-(' + c2 + ')*(' + a1 + ')',
                poly2 =  '(' + t2 + ')*(' + a1 + ')-(' + t2 + ')*(' + c1 + ')-(' + b2 + ')*(' + a1 + ')+(' +
                    b2 + ')*(' + c1 + ')-(' + t1 + ')*(' + a2 + ')+(' + t1 + ')*(' + c2 + ')+(' + b1 + ')*(' +
                    a2 + ')-(' + b1 + ')*(' + c2 + ')';

            return [poly1, poly2];
        };

        return p;
    };

    /**
     * @class A parallel is a line through a given point with the same slope as a given line.
     * @pseudo
     * @name Parallel
     * @augments Line
     * @constructor
     * @type JXG.Line
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line_JXG.Point} l,p The constructed line contains p and has the same slope as l.
     * @example
     * // Create a parallel
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     * var l1 = board.create('line', [p1, p2]);
     *
     * var p3 = board.create('point', [3.0, 3.0]);
     * var pl1 = board.create('parallel', [l1, p3]);
     * </pre><div class="jxgbox" id="JXG24e54f9e-5c4e-4afb-9228-0ef27a59d627" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var plex1_board = JXG.JSXGraph.initBoard('JXG24e54f9e-5c4e-4afb-9228-0ef27a59d627', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var plex1_p1 = plex1_board.create('point', [0.0, 2.0]);
     *   var plex1_p2 = plex1_board.create('point', [2.0, 1.0]);
     *   var plex1_l1 = plex1_board.create('line', [plex1_p1, plex1_p2]);
     *   var plex1_p3 = plex1_board.create('point', [3.0, 3.0]);
     *   var plex1_pl1 = plex1_board.create('parallel', [plex1_l1, plex1_p3]);
     * </script><pre>
     */
    JXG.createParallel = function (board, parents, attributes) {
        var p, pp, pl, li, i, attr;

        for (i = 0; i < parents.length; ++i) {
            parents[i] = board.select(parents[i]);
        }
        p = null;
        if (parents.length === 3) {
            parents = Type.providePoints(board, parents, attributes, 'point');
            // line through point parents[2] which is parallel to line through parents[0] and parents[1]
            p = parents[2];
            /** @ignore */
            li = function () {
                return Mat.crossProduct(parents[0].coords.usrCoords, parents[1].coords.usrCoords);
            };
        } else if (Type.isPointType(board, parents[0])) {
            // Parallel to line parents[1] through point parents[0]
            p = Type.providePoints(board, [parents[0]], attributes, 'point')[0];
            /** @ignore */
            li = function () {
                return parents[1].stdform;
            };
        } else if (Type.isPointType(board, parents[1])) {
            // Parallel to line parents[0] through point parents[1]
            p = Type.providePoints(board, [parents[1]], attributes, 'point')[0];
            /** @ignore */
            li = function () {
                return parents[0].stdform;
            };
        }

        if (!Type.exists(attributes.layer)) {
            attributes.layer = board.options.layer.line;
        }

        attr = Type.copyAttributes(attributes, board.options, 'parallel', 'point');
        pp = board.create('point', [
            function () {
                return Mat.crossProduct([1, 0, 0], li());
            }
        ], attr);
        pp.isDraggable = true;

        attr = Type.copyAttributes(attributes, board.options, 'parallel');
        // line creator also calls addChild
        pl = board.create('line', [p, pp], attr);

        pl.elType = 'parallel';
        pl.subs = {
            point: pp
        };

        pl.inherits.push(pp);
        pl.setParents([parents[0].id, parents[1].id]);
        if (parents.length === 3) {
            pl.addParents(parents[2].id);
        }

        // p.addChild(pl);

        /**
         * Helper point used to create the parallel line. This point lies on the line at infinity, hence it's not visible,
         * not even with visible set to <tt>true</tt>. Creating another line through this point would make that other line
         * parallel to the create parallel.
         * @memberOf Parallel.prototype
         * @name point
         * @type JXG.Point
         */
        pl.point = pp;

        return pl;
    };

    /**
     * @class An arrow parallel is a parallel segment with an arrow attached.
     * @pseudo
     * @constructor
     * @name Arrowparallel
     * @type Parallel
     * @augments Parallel
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line_JXG.Point} l,p The constructed arrow contains p and has the same slope as l.
     * @example
     * // Create a parallel
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     * var l1 = board.create('line', [p1, p2]);
     *
     * var p3 = board.create('point', [3.0, 3.0]);
     * var pl1 = board.create('arrowparallel', [l1, p3]);
     * </pre><div class="jxgbox" id="JXGeeacdf99-036f-4e83-aeb6-f7388423e369" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var plex1_board = JXG.JSXGraph.initBoard('JXGeeacdf99-036f-4e83-aeb6-f7388423e369', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var plex1_p1 = plex1_board.create('point', [0.0, 2.0]);
     *   var plex1_p2 = plex1_board.create('point', [2.0, 1.0]);
     *   var plex1_l1 = plex1_board.create('line', [plex1_p1, plex1_p2]);
     *   var plex1_p3 = plex1_board.create('point', [3.0, 3.0]);
     *   var plex1_pl1 = plex1_board.create('arrowparallel', [plex1_l1, plex1_p3]);
     * })();
     * </script><pre>
     */
    JXG.createArrowParallel = function (board, parents, attributes) {
        var p;

        /* parallel arrow point polynomials are done in createParallelPoint */
        try {
            attributes.firstArrow = false;
            attributes.lastArrow = true;
            p = JXG.createParallel(board, parents, attributes).setAttribute({straightFirst: false, straightLast: false});
            p.elType = 'arrowparallel';

            // parents are set in createParallel

            return p;
        } catch (e) {
            throw new Error("JSXGraph: Can't create arrowparallel with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [line,point], [point,point,point]");
        }
    };

    /**
     * @class Constructs a normal.
     * @pseudo
     * @description A normal is a line through a given point on a element of type line, circle, curve, or turtle and orthogonal to that object.
     * @constructor
     * @name Normal
     * @type JXG.Line
     * @augments JXG.Line
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line,JXG.Circle,JXG.Curve,JXG.Turtle_JXG.Point} o,p The constructed line contains p which lies on the object and is orthogonal
     * to the tangent to the object in the given point.
     * @param {Glider} p Works like above, however the object is given by {@link JXG.CoordsElement#slideObject}.
     * @example
     * // Create a normal to a circle.
     * var p1 = board.create('point', [2.0, 2.0]);
     * var p2 = board.create('point', [3.0, 2.0]);
     * var c1 = board.create('circle', [p1, p2]);
     *
     * var norm1 = board.create('normal', [c1, p2]);
     * </pre><div class="jxgbox" id="JXG4154753d-3d29-40fb-a860-0b08aa4f3743" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var nlex1_board = JXG.JSXGraph.initBoard('JXG4154753d-3d29-40fb-a860-0b08aa4f3743', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var nlex1_p1 = nlex1_board.create('point', [2.0, 2.0]);
     *   var nlex1_p2 = nlex1_board.create('point', [3.0, 2.0]);
     *   var nlex1_c1 = nlex1_board.create('circle', [nlex1_p1, nlex1_p2]);
     *
     *   // var nlex1_p3 = nlex1_board.create('point', [1.0, 2.0]);
     *   var nlex1_norm1 = nlex1_board.create('normal', [nlex1_c1, nlex1_p2]);
     * </script><pre>
     */
    JXG.createNormal = function (board, parents, attributes) {
        var p, c, l, i, g, f, attr, pp, attrp;

        for (i = 0; i < parents.length; ++i) {
            parents[i] = board.select(parents[i]);
        }
        // One arguments: glider on line, circle or curve
        if (parents.length === 1) {
            p = parents[0];
            c = p.slideObject;
        // Two arguments: (point,line), (point,circle), (line,point) or (circle,point)
        } else if (parents.length === 2) {
            if (Type.isPointType(board, parents[0])) {
                p = Type.providePoints(board, [parents[0]], attributes, 'point')[0];
                c = parents[1];
            } else if (Type.isPointType(board, parents[1])) {
                c = parents[0];
                p = Type.providePoints(board, [parents[1]], attributes, 'point')[0];
            } else {
                throw new Error("JSXGraph: Can't create normal with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                    "\nPossible parent types: [point,line], [point,circle], [glider]");
            }
        } else {
            throw new Error("JSXGraph: Can't create normal with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [point,line], [point,circle], [glider]");
        }

        attr = Type.copyAttributes(attributes, board.options, 'normal');
        if (c.elementClass === Const.OBJECT_CLASS_LINE) {
            // Private point
            attrp = Type.copyAttributes(attributes, board.options, 'normal', 'point');
            pp = board.create('point', [
                function () {
                    var p = Mat.crossProduct([1, 0, 0], c.stdform);
                    return [p[0], -p[2], p[1]];
                }
            ], attrp);
            pp.isDraggable = true;

            l = board.create('line', [p, pp], attr);

            /**
             * A helper point used to create a normal to a {@link JXG.Line} object. For normals to circles or curves this
             * element is <tt>undefined</tt>.
             * @type JXG.Point
             * @name point
             * @memberOf Normal.prototype
             */
            l.point = pp;
            l.subs = {
                point: pp
            };
            l.inherits.push(pp);
        } else if (c.elementClass === Const.OBJECT_CLASS_CIRCLE) {
            l = board.create('line', [c.midpoint, p], attr);
        } else if (c.elementClass === Const.OBJECT_CLASS_CURVE) {
            if (Type.evaluate(c.visProp.curvetype) !== 'plot') {
                g = c.X;
                f = c.Y;
                l = board.create('line', [
                    function () {
                        return -p.X() * Numerics.D(g)(p.position) - p.Y() * Numerics.D(f)(p.position);
                    },
                    function () {
                        return Numerics.D(g)(p.position);
                    },
                    function () {
                        return Numerics.D(f)(p.position);
                    }
                ], attr);
            } else {                         // curveType 'plot'
                l = board.create('line', [
                    function () {
                        var i = Math.floor(p.position),
                            lbda = p.position - i,
                            p1, p2, t, A, B, C, D, dx, dy, d;

                        if (c.bezierdegree === 1) {
                            if (i === c.numberPoints - 1) {
                                i -= 1;
                                lbda = 1;
                            }
                        } else if (c.bezierDegree === 3) {
                            // i is start of the Bezier segment
                            // t is the position in the Bezier segment
                            i = Math.floor(p.position * (c.numberPoints - 1) / 3) * 3;
                            t = (p.position * (c.numberPoints - 1) - i) / 3;
                            if (i >= c.numberPoints - 1) {
                                i = c.numberPoints - 4;
                                t = 1;
                            }
                        } else {
                            return 0;
                        }

                        if (i < 0) {
                            return 1;
                        }

                        if (c.bezierDegree === 1) {
                            return (c.Y(i) + lbda * (c.Y(i + 1) - c.Y(i))) * (c.Y(i) - c.Y(i + 1)) - (c.X(i) + lbda * (c.X(i + 1) - c.X(i))) * (c.X(i + 1) - c.X(i));
                        } else {
                            A = c.points[i].usrCoords;
                            B = c.points[i + 1].usrCoords;
                            C = c.points[i + 2].usrCoords;
                            D = c.points[i + 3].usrCoords;
                            dx = (1 - t) * (1 - t) * (B[1] - A[1]) + 2 * (1 - t) * t * (C[1] - B[1]) + t * t * (D[1]- C[1]);
                            dy = (1 - t) * (1 - t) * (B[2] - A[2]) + 2 * (1 - t) * t * (C[2] - B[2]) + t * t * (D[2]- C[2]);
                            d = Math.sqrt(dx * dx + dy * dy);
                            dx /= d;
                            dy /= d;
                            p1 = p.coords.usrCoords;
                            p2 = [1, p1[1] - dy, p1[2] + dx];
                            return p1[2] * p2[1] - p1[1] * p2[2];
                        }
                    },
                    function () {
                        var i = Math.floor(p.position),
                            p1, p2, t, A, B, C, D, dx, dy, d;

                        if (c.bezierdegree === 1) {
                            if (i === c.numberPoints - 1) {
                                i -= 1;
                            }
                        } else if (c.bezierDegree === 3) {
                            // i is start of the Bezier segment
                            // t is the position in the Bezier segment
                            i = Math.floor(p.position * (c.numberPoints - 1) / 3) * 3;
                            t = (p.position * (c.numberPoints - 1) - i) / 3;
                            if (i >= c.numberPoints - 1) {
                                i = c.numberPoints - 4;
                                t = 1;
                            }
                        } else {
                            return 0;
                        }

                        if (i < 0) {
                            return 0;
                        }
                        if (c.bezierDegree === 1) {
                            return c.X(i + 1) - c.X(i);
                        } else {
                            A = c.points[i].usrCoords;
                            B = c.points[i + 1].usrCoords;
                            C = c.points[i + 2].usrCoords;
                            D = c.points[i + 3].usrCoords;
                            dx = (1 - t) * (1 - t) * (B[1] - A[1]) + 2 * (1 - t) * t * (C[1] - B[1]) + t * t * (D[1]- C[1]);
                            dy = (1 - t) * (1 - t) * (B[2] - A[2]) + 2 * (1 - t) * t * (C[2] - B[2]) + t * t * (D[2]- C[2]);
                            d = Math.sqrt(dx * dx + dy * dy);
                            dx /= d;
                            dy /= d;
                            p1 = p.coords.usrCoords;
                            p2 = [1, p1[1] - dy, p1[2] + dx];
                            return p2[2] - p1[2];
                        }

                    },
                    function () {
                        var i = Math.floor(p.position),
                            p1, p2, t, A, B, C, D, dx, dy, d;

                        if (c.bezierdegree === 1) {
                            if (i === c.numberPoints - 1) {
                                i -= 1;
                            }
                        } else if (c.bezierDegree === 3) {
                            // i is start of the Bezier segment
                            // t is the position in the Bezier segment
                            i = Math.floor(p.position * (c.numberPoints - 1) / 3) * 3;
                            t = (p.position * (c.numberPoints - 1) - i) / 3;
                            if (i >= c.numberPoints - 1) {
                                i = c.numberPoints - 4;
                                t = 1;
                            }
                        } else {
                            return 0;
                        }

                        if (i < 0) {
                            return 0;
                        }

                        if (c.bezierDegree === 1) {
                            return c.Y(i + 1) - c.Y(i);
                        } else {
                            A = c.points[i].usrCoords;
                            B = c.points[i + 1].usrCoords;
                            C = c.points[i + 2].usrCoords;
                            D = c.points[i + 3].usrCoords;
                            dx = (1 - t) * (1 - t) * (B[1] - A[1]) + 2 * (1 - t) * t * (C[1] - B[1]) + t * t * (D[1]- C[1]);
                            dy = (1 - t) * (1 - t) * (B[2] - A[2]) + 2 * (1 - t) * t * (C[2] - B[2]) + t * t * (D[2]- C[2]);
                            d = Math.sqrt(dx * dx + dy * dy);
                            dx /= d;
                            dy /= d;
                            p1 = p.coords.usrCoords;
                            p2 = [1, p1[1] - dy, p1[2] + dx];
                            return p1[1] - p2[1];
                        }
                    }
                ], attr);
            }
        } else if (c.type === Const.OBJECT_TYPE_TURTLE) {
            l = board.create('line', [
                function () {
                    var el, j,
                        i = Math.floor(p.position),
                        lbda = p.position - i;

                    // run through all curves of this turtle
                    for (j = 0; j < c.objects.length; j++) {
                        el = c.objects[j];

                        if (el.type === Const.OBJECT_TYPE_CURVE) {
                            if (i < el.numberPoints) {
                                break;
                            }

                            i -= el.numberPoints;
                        }
                    }

                    if (i === el.numberPoints - 1) {
                        i -= 1;
                        lbda = 1;
                    }

                    if (i < 0) {
                        return 1;
                    }

                    return (el.Y(i) + lbda * (el.Y(i + 1) - el.Y(i))) * (el.Y(i) - el.Y(i + 1)) - (el.X(i) + lbda * (el.X(i + 1) - el.X(i))) * (el.X(i + 1) - el.X(i));
                },
                function () {
                    var el, j,
                        i = Math.floor(p.position);

                    // run through all curves of this turtle
                    for (j = 0; j < c.objects.length; j++) {
                        el = c.objects[j];
                        if (el.type === Const.OBJECT_TYPE_CURVE) {
                            if (i < el.numberPoints) {
                                break;
                            }

                            i -= el.numberPoints;
                        }
                    }

                    if (i === el.numberPoints - 1) {
                        i -=  1;
                    }

                    if (i < 0) {
                        return 0;
                    }

                    return el.X(i + 1) - el.X(i);
                },
                function () {
                    var el, j,
                        i = Math.floor(p.position);

                    // run through all curves of this turtle
                    for (j = 0; j < c.objects.length; j++) {
                        el = c.objects[j];
                        if (el.type === Const.OBJECT_TYPE_CURVE) {
                            if (i < el.numberPoints) {
                                break;
                            }

                            i -= el.numberPoints;
                        }
                    }

                    if (i === el.numberPoints - 1) {
                        i -= 1;
                    }

                    if (i < 0) {
                        return 0;
                    }

                    return el.Y(i + 1) - el.Y(i);
                }
            ], attr);
        } else {
            throw new Error("JSXGraph: Can't create normal with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [point,line], [point,circle], [glider]");
        }

        l.elType = 'normal';
        l.setParents(parents);

        if (Type.exists(p._is_new)) {
            l.addChild(p);
            delete p._is_new;
        } else {
            p.addChild(l);
        }
        c.addChild(l);

        return l;
    };

    /**
     * @class A bisector is a line which divides an angle into two equal angles. It is given by three points A, B, and
     * C and divides the angle ABC into two equal sized parts.
     * @pseudo
     * @constructor
     * @name Bisector
     * @type JXG.Line
     * @augments JXG.Line
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The angle described by <tt>p1</tt>, <tt>p2</tt> and <tt>p3</tt> will
     * be divided into two equal angles.
     * @example
     * var p1 = board.create('point', [6.0, 4.0]);
     * var p2 = board.create('point', [3.0, 2.0]);
     * var p3 = board.create('point', [1.0, 7.0]);
     *
     * var bi1 = board.create('bisector', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXG0d58cea8-b06a-407c-b27c-0908f508f5a4" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG0d58cea8-b06a-407c-b27c-0908f508f5a4', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var p1 = board.create('point', [6.0, 4.0]);
     *   var p2 = board.create('point', [3.0, 2.0]);
     *   var p3 = board.create('point', [1.0, 7.0]);
     *   var bi1 = board.create('bisector', [p1, p2, p3]);
     * })();
     * </script><pre>
     */
    JXG.createBisector = function (board, parents, attributes) {
        var p, l, i, attr;

        parents = Type.providePoints(board, parents, attributes, 'point');
        if (Type.isPoint(parents[0]) && Type.isPoint(parents[1]) && Type.isPoint(parents[2])) {
            // hidden and fixed helper
            attr = Type.copyAttributes(attributes, board.options, 'bisector', 'point');
            attr.snapToGrid = false;

            p = board.create('point', [
                function () {
                    return Geometry.angleBisector(parents[0], parents[1], parents[2], board);
                }
            ], attr);
            p.dump = false;

            for (i = 0; i < 3; i++) {
                // required for algorithm requiring dependencies between elements
                if (Type.exists(parents[i]._is_new)) {
                    p.addChild(parents[i]);
                    delete parents[i]._is_new;
                } else {
                    parents[i].addChild(p);
                }
            }

            if (!Type.exists(attributes.layer)) {
                attributes.layer = board.options.layer.line;
            }

            attr = Type.copyAttributes(attributes, board.options, 'bisector');
            l = Line.createLine(board, [parents[1], p], attr);

            /**
             * Helper point
             * @memberOf Bisector.prototype
             * @type Point
             * @name point
             */
            l.point = p;

            l.elType = 'bisector';
            l.setParents(parents);
            l.subs = {
                point: p
            };
            l.inherits.push(p);

            return l;
        }

        throw new Error("JSXGraph: Can't create angle bisector with parent types '" +
            (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
            "\nPossible parent types: [point,point,point]");
    };

    /**
     * @class Bisector lines are similar to {@link Bisector} but takes two lines as parent elements. The resulting element is
     * a composition of two lines.
     * @pseudo
     * @constructor
     * @name Bisectorlines
     * @type JXG.Composition
     * @augments JXG.Composition
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line_JXG.Line} l1,l2 The four angles described by the lines <tt>l1</tt> and <tt>l2</tt> will each
     * be divided into two equal angles.
     * @example
     * var p1 = board.create('point', [6.0, 4.0]);
     * var p2 = board.create('point', [3.0, 2.0]);
     * var p3 = board.create('point', [1.0, 7.0]);
     * var p4 = board.create('point', [3.0, 0.0]);
     * var l1 = board.create('line', [p1, p2]);
     * var l2 = board.create('line', [p3, p4]);
     *
     * var bi1 = board.create('bisectorlines', [l1, l2]);
     * </pre><div class="jxgbox" id="JXG3121ff67-44f0-4dda-bb10-9cda0b80bf18" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG3121ff67-44f0-4dda-bb10-9cda0b80bf18', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var p1 = board.create('point', [6.0, 4.0]);
     *   var p2 = board.create('point', [3.0, 2.0]);
     *   var p3 = board.create('point', [1.0, 7.0]);
     *   var p4 = board.create('point', [3.0, 0.0]);
     *   var l1 = board.create('line', [p1, p2]);
     *   var l2 = board.create('line', [p3, p4]);
     *   var bi1 = board.create('bisectorlines', [l1, l2]);
     * })();
     * </script><pre>
     */
    JXG.createAngularBisectorsOfTwoLines = function (board, parents, attributes) {
        // The angular bisectors of two line [c1,a1,b1] and [c2,a2,b2] are determined by the equation:
        // (a1*x+b1*y+c1*z)/sqrt(a1^2+b1^2) = +/- (a2*x+b2*y+c2*z)/sqrt(a2^2+b2^2)

        var g1, g2, attr, ret,
            l1 = board.select(parents[0]),
            l2 = board.select(parents[1]);

        if (l1.elementClass !== Const.OBJECT_CLASS_LINE || l2.elementClass !== Const.OBJECT_CLASS_LINE) {
            throw new Error("JSXGraph: Can't create angle bisectors of two lines with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [line,line]");
        }

        if (!Type.exists(attributes.layer)) {
            attributes.layer = board.options.layer.line;
        }

        attr = Type.copyAttributes(attributes, board.options, 'bisectorlines', 'line1');
        g1 = board.create('line', [
            function () {
                var d1 = Math.sqrt(l1.stdform[1] * l1.stdform[1] + l1.stdform[2] * l1.stdform[2]),
                    d2 = Math.sqrt(l2.stdform[1] * l2.stdform[1] + l2.stdform[2] * l2.stdform[2]);

                return l1.stdform[0] / d1 - l2.stdform[0] / d2;
            },
            function () {
                var d1 = Math.sqrt(l1.stdform[1] * l1.stdform[1] + l1.stdform[2] * l1.stdform[2]),
                    d2 = Math.sqrt(l2.stdform[1] * l2.stdform[1] + l2.stdform[2] * l2.stdform[2]);

                return l1.stdform[1] / d1 - l2.stdform[1] / d2;
            },
            function () {
                var d1 = Math.sqrt(l1.stdform[1] * l1.stdform[1] + l1.stdform[2] * l1.stdform[2]),
                    d2 = Math.sqrt(l2.stdform[1] * l2.stdform[1] + l2.stdform[2] * l2.stdform[2]);

                return l1.stdform[2] / d1 - l2.stdform[2] / d2;
            }
        ], attr);

        if (!Type.exists(attributes.layer)) {
            attributes.layer = board.options.layer.line;
        }
        attr = Type.copyAttributes(attributes, board.options, 'bisectorlines', 'line2');
        g2 = board.create('line', [
            function () {
                var d1 = Math.sqrt(l1.stdform[1] * l1.stdform[1] + l1.stdform[2] * l1.stdform[2]),
                    d2 = Math.sqrt(l2.stdform[1] * l2.stdform[1] + l2.stdform[2] * l2.stdform[2]);

                return l1.stdform[0] / d1 + l2.stdform[0] / d2;
            },
            function () {
                var d1 = Math.sqrt(l1.stdform[1] * l1.stdform[1] + l1.stdform[2] * l1.stdform[2]),
                    d2 = Math.sqrt(l2.stdform[1] * l2.stdform[1] + l2.stdform[2] * l2.stdform[2]);

                return l1.stdform[1] / d1 + l2.stdform[1] / d2;
            },
            function () {
                var d1 = Math.sqrt(l1.stdform[1] * l1.stdform[1] + l1.stdform[2] * l1.stdform[2]),
                    d2 = Math.sqrt(l2.stdform[1] * l2.stdform[1] + l2.stdform[2] * l2.stdform[2]);

                return l1.stdform[2] / d1 + l2.stdform[2] / d2;
            }
        ], attr);

        // documentation
        /**
         * First line.
         * @memberOf Bisectorlines.prototype
         * @name line1
         * @type Line
         */

        /**
         * Second line.
         * @memberOf Bisectorlines.prototype
         * @name line2
         * @type Line
         */

        ret = new Composition({line1: g1, line2: g2});

        g1.dump = false;
        g2.dump = false;

        ret.elType = 'bisectorlines';
        ret.setParents([l1.id, l2.id]);
        ret.subs = {
            line1: g1,
            line2: g2
        };
        // ret.inherits.push(g1, g2);

        return ret;
    };

    // /**
    //  * @class An m-sector is a line which divides an angle into two angles. It is given by three points A, B, and
    //  * C and a real number m, and divides an angle into two angles, an angle with amplitude m and an angle with
    //  * amplitude (1-m)
    //  * @pseudo
    //  * @constructor
    //  * @name Msector
    //  * @type JXG.Line
    //  * @augments JXG.Line
    //  * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
    //  * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The angle described by <tt>p1</tt>, <tt>p2</tt> and <tt>p3</tt> will
    //  * be divided into two angles according to the value of <tt>m</tt>.
    //  * @example
    //  * var p1 = board.create('point', [6.0, 4.0]);
    //  * var p2 = board.create('point', [3.0, 2.0]);
    //  * var p3 = board.create('point', [1.0, 7.0]);
    //  *
    //  * var bi1 = board.create('msector', [p1, p2, p3], 1/5);
    //  * </pre><div id="JXG0d58cea8-b06a-407c-b27c-0908f508f5a4" style="width: 400px; height: 400px;"></div>
    //  * <script type="text/javascript">
    //  * (function () {
    //  *   var board = JXG.JSXGraph.initBoard('JXG0d58cea8-b06a-407c-b27c-0908f508f5a4', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
    //  *   var p1 = board.create('point', [6.0, 4.0]);
    //  *   var p2 = board.create('point', [3.0, 2.0]);
    //  *   var p3 = board.create('point', [1.0, 7.0]);
    //  *   var bi1 = board.create('msector', [p1, p2, p3], 1/5);
    //  * })();
    //  * </script><pre>
    //  */
    // JXG.createMsector = function (board, parents, attributes) {
    //     var p, l, i, attr;

    //     if (parents[0].elementClass === Const.OBJECT_CLASS_POINT &&
    //             parents[1].elementClass === Const.OBJECT_CLASS_POINT &&
    //             parents[2].elementClass === Const.OBJECT_CLASS_POINT) {
    //         // hidden and fixed helper
    //         attr = Type.copyAttributes(attributes, board.options, 'msector', 'point');
    //         p = board.create('point', [
    //             function () {
    //                 return Geometry.angleMsector(parents[0], parents[1], parents[2], parents[3], board);
    //             }
    //         ], attr);
    //         p.dump = false;

    //         for (i = 0; i < 3; i++) {
    //             // required for algorithm requiring dependencies between elements
    //             parents[i].addChild(p);
    //         }

    //         if (!Type.exists(attributes.layer)) {
    //             attributes.layer = board.options.layer.line;
    //         }

    //         attr = Type.copyAttributes(attributes, board.options, 'msector');
    //         l = Line.createLine(board, [parents[1], p], attr);

    //         /**
    //          * Helper point
    //          * @memberOf Msector.prototype
    //          * @type Point
    //          * @name point
    //          */
    //         l.point = p;

    //         l.elType = 'msector';
    //         l.parents = [parents[0].id, parents[1].id, parents[2].id];
    //         l.subs = {
    //             point: p
    //         };
    //         l.inherits.push(p);

    //         return l;
    //     }

    //     throw new Error("JSXGraph: Can't create angle msector with parent types '" +
    //         (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    //         "\nPossible parent types: [point,point,point,Number]");
    // };

    /**
     * @class Constructs the midpoint of a {@link Circumcircle}. Like the circumcircle the circumcenter
     * is constructed by providing three points.
     * @pseudo
     * @description A circumcenter is given by three points which are all lying on the circle with the
     * constructed circumcenter as the midpoint.
     * @constructor
     * @name Circumcenter
     * @type JXG.Point
     * @augments JXG.Point
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The constructed point is the midpoint of the circle determined
     * by p1, p2, and p3.
     * @example
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     * var p3 = board.create('point', [3.0, 3.0]);
     *
     * var cc1 = board.create('circumcenter', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXGe8a40f95-bf30-4eb4-88a8-f4d5495261fd" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var ccmex1_board = JXG.JSXGraph.initBoard('JXGe8a40f95-bf30-4eb4-88a8-f4d5495261fd', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var ccmex1_p1 = ccmex1_board.create('point', [0.0, 2.0]);
     *   var ccmex1_p2 = ccmex1_board.create('point', [6.0, 1.0]);
     *   var ccmex1_p3 = ccmex1_board.create('point', [3.0, 7.0]);
     *   var ccmex1_cc1 = ccmex1_board.create('circumcenter', [ccmex1_p1, ccmex1_p2, ccmex1_p3]);
     * </script><pre>
     */
    JXG.createCircumcenter = function (board, parents, attributes) {
        var p, i, a, b, c;

        parents = Type.providePoints(board, parents, attributes, 'point');
        if (Type.isPoint(parents[0]) && Type.isPoint(parents[1]) && Type.isPoint(parents[2])) {

            a = parents[0];
            b = parents[1];
            c = parents[2];

            p = Point.createPoint(board, [
                function () {
                    return Geometry.circumcenter(a, b, c, board);
                }
            ], attributes);

            for (i = 0; i < 3; i++) {
                if (Type.exists(parents[i]._is_new)) {
                    p.addChild(parents[i]);
                    delete parents[i]._is_new;
                } else {
                    parents[i].addChild(p);
                }
            }

            p.elType = 'circumcenter';
            p.setParents(parents);

            p.generatePolynomial = function () {
                /*
                 *  CircumcircleMidpoint takes three points A, B and C  and creates point M, which is the circumcenter of A, B, and C.
                 *
                 *
                 * So we have two conditions:
                 *
                 *   (a)   CT  ==  AT           (distance condition I)
                 *   (b)   BT  ==  AT           (distance condition II)
                 *
                 */
                var a1 = a.symbolic.x,
                    a2 = a.symbolic.y,
                    b1 = b.symbolic.x,
                    b2 = b.symbolic.y,
                    c1 = c.symbolic.x,
                    c2 = c.symbolic.y,
                    t1 = p.symbolic.x,
                    t2 = p.symbolic.y,

                    poly1 = ['((', t1, ')-(', a1, '))^2+((', t2, ')-(', a2, '))^2-((', t1, ')-(', b1, '))^2-((', t2, ')-(', b2, '))^2'].join(''),
                    poly2 = ['((', t1, ')-(', a1, '))^2+((', t2, ')-(', a2, '))^2-((', t1, ')-(', c1, '))^2-((', t2, ')-(', c2, '))^2'].join('');

                return [poly1, poly2];
            };

            return p;
        }

        throw new Error("JSXGraph: Can't create circumcircle midpoint with parent types '" +
            (typeof parents[0]) + "', '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
            "\nPossible parent types: [point,point,point]");
    };

    /**
     * @class Constructs the incenter of the triangle described by the three given points.{@link http://mathworld.wolfram.com/Incenter.html}
     * @pseudo
     * @constructor
     * @name Incenter
     * @type JXG.Point
     * @augments JXG.Point
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The constructed point is the incenter of the triangle described
     * by p1, p2, and p3.
     * @example
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     * var p3 = board.create('point', [3.0, 3.0]);
     *
     * var ic1 = board.create('incenter', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXGe8a40f95-bf30-4eb4-88a8-a2d5495261fd" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var icmex1_board = JXG.JSXGraph.initBoard('JXGe8a40f95-bf30-4eb4-88a8-a2d5495261fd', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var icmex1_p1 = icmex1_board.create('point', [0.0, 2.0]);
     *   var icmex1_p2 = icmex1_board.create('point', [6.0, 1.0]);
     *   var icmex1_p3 = icmex1_board.create('point', [3.0, 7.0]);
     *   var icmex1_ic1 = icmex1_board.create('incenter', [icmex1_p1, icmex1_p2, icmex1_p3]);
     * </script><pre>
     */
    JXG.createIncenter = function (board, parents, attributes) {
        var p, A, B, C, i;

        parents = Type.providePoints(board, parents, attributes, 'point');
        if (parents.length >= 3 && Type.isPoint(parents[0]) && Type.isPoint(parents[1]) && Type.isPoint(parents[2])) {
            A = parents[0];
            B = parents[1];
            C = parents[2];

            p = board.create('point', [function () {
                var a, b, c;

                a = Math.sqrt((B.X() - C.X()) * (B.X() - C.X()) + (B.Y() - C.Y()) * (B.Y() - C.Y()));
                b = Math.sqrt((A.X() - C.X()) * (A.X() - C.X()) + (A.Y() - C.Y()) * (A.Y() - C.Y()));
                c = Math.sqrt((B.X() - A.X()) * (B.X() - A.X()) + (B.Y() - A.Y()) * (B.Y() - A.Y()));

                return new Coords(Const.COORDS_BY_USER, [(a * A.X() + b * B.X() + c * C.X()) / (a + b + c), (a * A.Y() + b * B.Y() + c * C.Y()) / (a + b + c)], board);
            }], attributes);

            for (i = 0; i < 3; i++) {
                if (Type.exists(parents[i]._is_new)) {
                    p.addChild(parents[i]);
                    delete parents[i]._is_new;
                } else {
                    parents[i].addChild(p);
                }
            }

            p.elType = 'incenter';
            p.setParents(parents);

        } else {
            throw new Error("JSXGraph: Can't create incenter with parent types '" +
                (typeof parents[0]) + "', '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
                "\nPossible parent types: [point,point,point]");
        }

        return p;
    };

    /**
     * @class A circumcircle is given by three points which are all lying on the circle.
     * @pseudo
     * @constructor
     * @name Circumcircle
     * @type JXG.Circle
     * @augments JXG.Circle
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The constructed element is the circle determined by <tt>p1</tt>, <tt>p2</tt>, and <tt>p3</tt>.
     * @example
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     * var p3 = board.create('point', [3.0, 3.0]);
     *
     * var cc1 = board.create('circumcircle', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXGe65c9861-0bf0-402d-af57-3ab11962f5ac" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var ccex1_board = JXG.JSXGraph.initBoard('JXGe65c9861-0bf0-402d-af57-3ab11962f5ac', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var ccex1_p1 = ccex1_board.create('point', [0.0, 2.0]);
     *   var ccex1_p2 = ccex1_board.create('point', [6.0, 1.0]);
     *   var ccex1_p3 = ccex1_board.create('point', [3.0, 7.0]);
     *   var ccex1_cc1 = ccex1_board.create('circumcircle', [ccex1_p1, ccex1_p2, ccex1_p3]);
     * </script><pre>
     */
    JXG.createCircumcircle = function (board, parents, attributes) {
        var p, c, attr, i;

        parents = Type.providePoints(board, parents, attributes, 'point');
        if (parents === false) {
            throw new Error("JSXGraph: Can't create circumcircle with parent types '" +
                (typeof parents[0]) + "', '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
                "\nPossible parent types: [point,point,point]");
        }

        try {
            attr = Type.copyAttributes(attributes, board.options, 'circumcircle', 'center');
            p = JXG.createCircumcenter(board, parents, attr);

            p.dump = false;

            if (!Type.exists(attributes.layer)) {
                attributes.layer = board.options.layer.circle;
            }
            attr = Type.copyAttributes(attributes, board.options, 'circumcircle');
            c = Circle.createCircle(board, [p, parents[0]], attr);

            c.elType = 'circumcircle';
            c.setParents(parents);
            c.subs = {
                center: p
            };
            c.inherits.push(c);
            for (i = 0; i < 3; i++) {
                if (Type.exists(parents[i]._is_new)) {
                    c.addChild(parents[i]);
                    delete parents[i]._is_new;
                } else {
                    parents[i].addChild(c);
                }
            }

        } catch (e) {
            throw new Error("JSXGraph: Can't create circumcircle with parent types '" +
                (typeof parents[0]) + "', '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
                "\nPossible parent types: [point,point,point]");
        }

        // p is already stored as midpoint in c so there's no need to store it explicitly.

        return c;
    };

    /**
     * @class An incircle is given by three points.
     * @pseudo
     * @constructor
     * @name Incircle
     * @type JXG.Circle
     * @augments JXG.Circle
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point_JXG.Point} p1,p2,p3 The constructed point is the midpoint of the incircle of
     * <tt>p1</tt>, <tt>p2</tt>, and <tt>p3</tt>.
     * @example
     * var p1 = board.create('point', [0.0, 2.0]);
     * var p2 = board.create('point', [2.0, 1.0]);
     * var p3 = board.create('point', [3.0, 3.0]);
     *
     * var ic1 = board.create('incircle', [p1, p2, p3]);
     * </pre><div class="jxgbox" id="JXGe65c9861-0bf0-402d-af57-2ab12962f8ac" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var icex1_board = JXG.JSXGraph.initBoard('JXGe65c9861-0bf0-402d-af57-2ab12962f8ac', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var icex1_p1 = icex1_board.create('point', [0.0, 2.0]);
     *   var icex1_p2 = icex1_board.create('point', [6.0, 1.0]);
     *   var icex1_p3 = icex1_board.create('point', [3.0, 7.0]);
     *   var icex1_ic1 = icex1_board.create('incircle', [icex1_p1, icex1_p2, icex1_p3]);
     * </script><pre>
     */
    JXG.createIncircle = function (board, parents, attributes) {
        var i, p, c, attr;

        parents = Type.providePoints(board, parents, attributes, 'point');
        if (parents === false) {
            throw new Error("JSXGraph: Can't create circumcircle with parent types '" +
                (typeof parents[0]) + "', '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
                "\nPossible parent types: [point,point,point]");
        }
        try {
            attr = Type.copyAttributes(attributes, board.options, 'incircle', 'center');
            p = JXG.createIncenter(board, parents, attr);

            p.dump = false;

            if (!Type.exists(attributes.layer)) {
                attributes.layer = board.options.layer.circle;
            }
            attr = Type.copyAttributes(attributes, board.options, 'incircle');
            c = Circle.createCircle(board, [p, function () {
                var a = Math.sqrt((parents[1].X() - parents[2].X()) * (parents[1].X() - parents[2].X()) + (parents[1].Y() - parents[2].Y()) * (parents[1].Y() - parents[2].Y())),
                    b = Math.sqrt((parents[0].X() - parents[2].X()) * (parents[0].X() - parents[2].X()) + (parents[0].Y() - parents[2].Y()) * (parents[0].Y() - parents[2].Y())),
                    c = Math.sqrt((parents[1].X() - parents[0].X()) * (parents[1].X() - parents[0].X()) + (parents[1].Y() - parents[0].Y()) * (parents[1].Y() - parents[0].Y())),
                    s = (a + b + c) / 2;

                return Math.sqrt(((s - a) * (s - b) * (s - c)) / s);
            }], attr);

            c.elType = 'incircle';
            c.setParents(parents);
            for (i = 0; i < 3; i++) {
                if (Type.exists(parents[i]._is_new)) {
                    c.addChild(parents[i]);
                    delete parents[i]._is_new;
                } else {
                    parents[i].addChild(c);
                }
            }

            /**
             * The center of the incircle
             * @memberOf Incircle.prototype
             * @type Incenter
             * @name center
             */
            c.center = p;

            c.subs = {
                center: c.center
            };
            c.inherits.push(p);

        } catch (e) {
            throw new Error("JSXGraph: Can't create circumcircle with parent types '" +
                (typeof parents[0]) + "', '" + (typeof parents[1]) + "' and '" + (typeof parents[2]) + "'." +
                "\nPossible parent types: [point,point,point]");
        }

        // p is already stored as midpoint in c so there's no need to store it explicitly.

        return c;
    };

    /**
     * @class This element is used to construct reflected elements (points, lines, circles, curves, polygons).
     * @pseudo
     * @description A reflected element (point, polygon, line or curve) is given by a given
     * object of the same type and a line of reflection.
     * It is determined by the reflection of the given element
     * across the given line.
     * @constructor
     * @name Reflection
     * @type JXG.GeometryElement
     * @augments JXG.GeometryElement
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point|JXG.Line|JXG.Curve|JXG.Polygon_JXG.Line} p,l The reflection element is the reflection of p across the line l.
     * @example
     * var p1 = board.create('point', [0.0, 4.0]);
     * var p2 = board.create('point', [6.0, 1.0]);
     * var l1 = board.create('line', [p1, p2]);
     * var p3 = board.create('point', [3.0, 3.0]);
     *
     * var rp1 = board.create('reflection', [p3, l1]);
     * </pre><div class="jxgbox" id="JXG087a798e-a36a-4f52-a2b4-29a23a69393b" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var rpex1_board = JXG.JSXGraph.initBoard('JXG087a798e-a36a-4f52-a2b4-29a23a69393b', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var rpex1_p1 = rpex1_board.create('point', [0.0, 4.0]);
     *   var rpex1_p2 = rpex1_board.create('point', [6.0, 1.0]);
     *   var rpex1_l1 = rpex1_board.create('line', [rpex1_p1, rpex1_p2]);
     *   var rpex1_p3 = rpex1_board.create('point', [3.0, 3.0]);
     *   var rpex1_rp1 = rpex1_board.create('reflection', [rpex1_p3, rpex1_l1]);
     * </script><pre>
     * @example
     *         // Reflection of more elements
     *         // reflection line
     *         var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
     *
     *         var p1 = board.create('point', [-3,-1], {name: "A"});
     *         var q1 = board.create('reflection', [p1, li], {name: "A'"});
     *
     *         var l1 = board.create('line', [1,-5,1]);
     *         var l2 = board.create('reflection', [l1, li]);
     *
     *         var cu1 = board.create('curve', [[-3, -3, -2.5, -3, -3, -2.5], [-3, -2, -2, -2, -2.5, -2.5]], {strokeWidth:3});
     *         var cu2 = board.create('reflection', [cu1, li], {strokeColor: 'red', strokeWidth:3});
     *
     *         var pol1 = board.create('polygon', [[-6,-3], [-4,-5], [-5,-1.5]]);
     *         var pol2 = board.create('reflection', [pol1, li]);
     *
     *         var c1 = board.create('circle', [[-2,-2], [-2, -1]]);
     *         var c2 = board.create('reflection', [c1, li]);
     *
     *         var a1 = board.create('arc', [[1, 1], [0, 1], [1, 0]], {strokeColor: 'red'});
     *         var a2 = board.create('reflection', [a1, li], {strokeColor: 'red'});
     *
     *         var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], {
     *                           anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true},
     *                           fillColor: 'yellow', strokeColor: 'black'});
     *         var s2 = board.create('reflection', [s1, li], {fillColor: 'yellow', strokeColor: 'black', fillOpacity: 0.5});
     *
     *         var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]);
     *         var an2 = board.create('reflection', [an1, li]);
     *
     * </pre><div id="JXG8f763af4-d449-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG8f763af4-d449-11e7-93b3-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *             // reflection line
     *             var li = board.create('line', [1,1,1], {strokeColor: '#aaaaaa'});
     *
     *             var p1 = board.create('point', [-3,-1], {name: "A"});
     *             var q1 = board.create('reflection', [p1, li], {name: "A'"});
     *
     *             var l1 = board.create('line', [1,-5,1]);
     *             var l2 = board.create('reflection', [l1, li]);
     *
     *             var cu1 = board.create('curve', [[-3, -3, -2.5, -3, -3, -2.5], [-3, -2, -2, -2, -2.5, -2.5]], {strokeWidth:3});
     *             var cu2 = board.create('reflection', [cu1, li], {strokeColor: 'red', strokeWidth:3});
     *
     *             var pol1 = board.create('polygon', [[-6,-3], [-4,-5], [-5,-1.5]]);
     *             var pol2 = board.create('reflection', [pol1, li]);
     *
     *             var c1 = board.create('circle', [[-2,-2], [-2, -1]]);
     *             var c2 = board.create('reflection', [c1, li]);
     *
     *         var a1 = board.create('arc', [[1, 1], [0, 1], [1, 0]], {strokeColor: 'red'});
     *         var a2 = board.create('reflection', [a1, li], {strokeColor: 'red'});
     *
     *         var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], {
     *                           anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true},
     *                           fillColor: 'yellow', strokeColor: 'black'});
     *         var s2 = board.create('reflection', [s1, li], {fillColor: 'yellow', strokeColor: 'black', fillOpacity: 0.5});
     *
     *         var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]);
     *         var an2 = board.create('reflection', [an1, li]);
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createReflection = function (board, parents, attributes) {
        var l, org, r, r_c, t, i,
            attr, attr2,
            errStr = "\nPossible parent types: [point|line|curve|polygon|circle|arc|sector, line]";

        for (i = 0; i < parents.length; ++i) {
            parents[i] = board.select(parents[i]);
        }

        attr = Type.copyAttributes(attributes, board.options, 'reflection');

        if (Type.isPoint(parents[0])) {
            org = Type.providePoints(board, [parents[0]], attr2)[0];
        } else if (parents[0].elementClass === Const.OBJECT_CLASS_CURVE ||
                    parents[0].elementClass === Const.OBJECT_CLASS_LINE ||
                    parents[0].type === Const.OBJECT_TYPE_POLYGON ||
                    parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE) {
            org = parents[0];
        } else {
            throw new Error("JSXGraph: Can't create reflection element with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + errStr);
        }

        if (parents[1].elementClass === Const.OBJECT_CLASS_LINE) {
            l = parents[1];
        } else {
            throw new Error("JSXGraph: Can't create reflected element with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + errStr);
        }

        t = Transform.createTransform(board, [l], {type: 'reflect'});
        if (Type.isPoint(org)) {
            r = Point.createPoint(board, [org, t], attr);
        // Arcs and sectors are treated as curves
        } else if (org.elementClass === Const.OBJECT_CLASS_CURVE){
            r = Curve.createCurve(board, [org, t], attr);
        } else if (org.elementClass === Const.OBJECT_CLASS_LINE){
            r = Line.createLine(board, [org, t], attr);
        } else if (org.type === Const.OBJECT_TYPE_POLYGON){
            r = Polygon.createPolygon(board, [org, t], attr);
        } else if (org.elementClass === Const.OBJECT_CLASS_CIRCLE) {
            if (attr.type.toLowerCase() === 'euclidean') {
                // Create a circle element from a circle and a Euclidean transformation
                attr2 = Type.copyAttributes(attributes, board.options, 'reflection', 'center');
                r_c = Point.createPoint(board, [org.center, t], attr2);
                r_c.prepareUpdate().update().updateVisibility(Type.evaluate(r_c.visProp.visible)).updateRenderer();
                r = Circle.createCircle(board, [r_c, function() {return org.Radius(); }], attr);
            } else {
                // Create a conic element from a circle and a projective transformation
                r = Circle.createCircle(board, [org, t], attr);
            }
        } else {
            throw new Error("JSXGraph: Can't create reflected element with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + errStr);
        }
        if (Type.exists(org._is_new)) {
            r.addChild(org);
            delete org._is_new;
        } else {
            // org.addChild(r);
        }
        l.addChild(r);

        r.elType = 'reflection';
        r.addParents(l);
        r.prepareUpdate().update(); //.updateVisibility(Type.evaluate(r.visProp.visible)).updateRenderer();

        if (Type.isPoint(r)) {
            r.generatePolynomial = function () {
                /*
                 *  Reflection takes a point R and a line L and creates point P, which is the reflection of R on L.
                 *  L is defined by two points A and B.
                 *
                 * So we have two conditions:
                 *
                 *   (a)   RP  _|_  AB            (orthogonality condition)
                 *   (b)   AR  ==   AP            (distance condition)
                 *
                 */
                var a1 = l.point1.symbolic.x,
                    a2 = l.point1.symbolic.y,
                    b1 = l.point2.symbolic.x,
                    b2 = l.point2.symbolic.y,
                    p1 = org.symbolic.x,
                    p2 = org.symbolic.y,
                    r1 = r.symbolic.x,
                    r2 = r.symbolic.y,

                    poly1 = ['((', r2, ')-(', p2, '))*((', a2, ')-(', b2, '))+((', a1, ')-(', b1, '))*((', r1, ')-(', p1, '))'].join(''),
                    poly2 = ['((', r1, ')-(', a1, '))^2+((', r2, ')-(', a2, '))^2-((', p1, ')-(', a1, '))^2-((', p2, ')-(', a2, '))^2'].join('');

                return [poly1, poly2];
            };
        }

        return r;
    };

    /**
     * @class A mirror element of a point, line, circle, curve, polygon will be constructed.
     * @pseudo
     * @description A mirror element is determined by the reflection of a given point, line, circle, curve, polygon across another given point.
     * @constructor
     * @name Mirrorelement
     * @type JXG.GeometryElement
     * @augments JXG.GeometryElement
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point|JXG.Line|JXG.Curve|JXG.Ppolygon_JXG.Point} p1,p2 The constructed element is the mirror image of p2 across p1.
     * @example
     *         // point of reflection
     *         var mirr = board.create('point', [-1,-1], {color: '#aaaaaa'});
     *
     *         var p1 = board.create('point', [-3,-1], {name: "A"});
     *         var q1 = board.create('mirrorelement', [p1, mirr], {name: "A'"});
     *
     *         var l1 = board.create('line', [1, -5, 1]);
     *         var l2 = board.create('mirrorelement', [l1, mirr]);
     *
     *         var cu1 = board.create('curve', [[-3, -3, -2.5, -3, -3, -2.5], [-3, -2, -2, -2, -2.5, -2.5]], {strokeWidth:3});
     *         var cu2 = board.create('mirrorelement', [cu1, mirr], {strokeColor: 'red', strokeWidth:3});
     *
     *         var pol1 = board.create('polygon', [[-6,-2], [-4,-4], [-5,-0.5]]);
     *         var pol2 = board.create('mirrorelement', [pol1, mirr]);
     *
     *         var c1 = board.create('circle', [[-6,-6], [-6, -5]]);
     *         var c2 = board.create('mirrorelement', [c1, mirr]);
     *
     *         var a1 = board.create('arc', [[1, 1], [0, 1], [1, 0]], {strokeColor: 'red'});
     *         var a2 = board.create('mirrorelement', [a1, mirr], {strokeColor: 'red'});
     *
     *         var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], {
     *                           anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true},
     *                           fillColor: 'yellow', strokeColor: 'black'});
     *         var s2 = board.create('mirrorelement', [s1, mirr], {fillColor: 'yellow', strokeColor: 'black', fillOpacity: 0.5});
     *
     *         var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]);
     *         var an2 = board.create('mirrorelement', [an1, mirr]);
     *
     *
     * </pre><div id="JXG026c779c-d8d9-11e7-93b3-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG026c779c-d8d9-11e7-93b3-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *             // point of reflection
     *             var mirr = board.create('point', [-1,-1], {color: '#aaaaaa'});
     *
     *             var p1 = board.create('point', [-3,-1], {name: "A"});
     *             var q1 = board.create('mirrorelement', [p1, mirr], {name: "A'"});
     *
     *             var l1 = board.create('line', [1,-5, 1]);
     *             var l2 = board.create('mirrorelement', [l1, mirr]);
     *
     *             var cu1 = board.create('curve', [[-3, -3, -2.5, -3, -3, -2.5], [-3, -2, -2, -2, -2.5, -2.5]], {strokeWidth:3});
     *             var cu2 = board.create('mirrorelement', [cu1, mirr], {strokeColor: 'red', strokeWidth:3});
     *
     *             var pol1 = board.create('polygon', [[-6,-2], [-4,-4], [-5,-0.5]]);
     *             var pol2 = board.create('mirrorelement', [pol1, mirr]);
     *
     *             var c1 = board.create('circle', [[-6,-6], [-6, -5]]);
     *             var c2 = board.create('mirrorelement', [c1, mirr]);
     *
     *         var a1 = board.create('arc', [[1, 1], [0, 1], [1, 0]], {strokeColor: 'red'});
     *         var a2 = board.create('mirrorelement', [a1, mirr], {strokeColor: 'red'});
     *
     *         var s1 = board.create('sector', [[-3.5,-3], [-3.5, -2], [-3.5,-4]], {
     *                           anglePoint: {visible:true}, center: {visible: true}, radiusPoint: {visible: true},
     *                           fillColor: 'yellow', strokeColor: 'black'});
     *         var s2 = board.create('mirrorelement', [s1, mirr], {fillColor: 'yellow', strokeColor: 'black', fillOpacity: 0.5});
     *
     *         var an1 = board.create('angle', [[-4,3.9], [-3, 4], [-3, 3]]);
     *         var an2 = board.create('mirrorelement', [an1, mirr]);
     *
     *     })();
     *
     * </script><pre>
     */
    JXG.createMirrorElement = function (board, parents, attributes) {
        var org, i, m, r, r_c, t,
            attr, attr2,
            errStr = "\nPossible parent types: [point|line|curve|polygon|circle|arc|sector, point]";

        for (i = 0; i < parents.length; ++i) {
            parents[i] = board.select(parents[i]);
        }

        attr = Type.copyAttributes(attributes, board.options, 'mirrorelement');
        if (Type.isPoint(parents[0])) {
            // Create point to be mirrored if supplied by coords array.
            org = Type.providePoints(board, [parents[0]], attr)[0];
        } else if (parents[0].elementClass === Const.OBJECT_CLASS_CURVE ||
                    parents[0].elementClass === Const.OBJECT_CLASS_LINE ||
                    parents[0].type === Const.OBJECT_TYPE_POLYGON ||
                    parents[0].elementClass === Const.OBJECT_CLASS_CIRCLE) {
            org = parents[0];
        } else {
            throw new Error("JSXGraph: Can't create mirror element with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + errStr);
        }

        if (Type.isPoint(parents[1])) {
            attr2 = Type.copyAttributes(attributes, board.options, 'mirrorelement', 'point');
            // Create mirror point if supplied by coords array.
            m = Type.providePoints(board, [parents[1]], attr2)[0];
        } else {
            throw new Error("JSXGraph: Can't create mirror element with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + errStr);
        }

        t = Transform.createTransform(board, [Math.PI, m], {type: 'rotate'});
        if (Type.isPoint(org)) {
            r = Point.createPoint(board, [org, t], attr);

        // Arcs and sectors are treated as curves
        } else if (org.elementClass === Const.OBJECT_CLASS_CURVE){
            r = Curve.createCurve(board, [org, t], attr);
        } else if (org.elementClass === Const.OBJECT_CLASS_LINE){
            r = Line.createLine(board, [org, t], attr);
        }  else if (org.type === Const.OBJECT_TYPE_POLYGON){
            r = Polygon.createPolygon(board, [org, t], attr);
        } else if (org.elementClass === Const.OBJECT_CLASS_CIRCLE){
            if (attr.type.toLowerCase() === 'euclidean') {
                // Create a circle element from a circle and a Euclidean transformation
                attr2 = Type.copyAttributes(attributes, board.options, 'mirrorelement', 'center');
                r_c = Point.createPoint(board, [org.center, t], attr2);
                r_c.prepareUpdate().update().updateVisibility(Type.evaluate(r_c.visProp.visible)).updateRenderer();
                r = Circle.createCircle(board, [r_c, function() {return org.Radius(); }], attr);
            } else {
                // Create a conic element from a circle and a projective transformation
                r = Circle.createCircle(board, [org, t], attr);
            }
        } else {
            throw new Error("JSXGraph: Can't create mirror element with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." + errStr);
        }

        if (Type.exists(org._is_new)) {
            r.addChild(org);
            delete org._is_new;
        } else {
            // org.addChild(r);
        }
        m.addChild(r);

        r.elType = 'mirrorelement';
        r.addParents(m);
        r.prepareUpdate().update();

        return r;
    };

    /**
     * @class A mirror point will be constructed.
     * @pseudo
     * @description A mirror point is determined by the reflection of a given point against another given point.
     * @constructor
     * @name Mirrorpoint
     * @type JXG.Point
     * @augments JXG.Point
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point_JXG.Point} p1,p2 The constructed point is the reflection of p2 against p1.
     *
     * This method is superseeded by the more general {@link JXG.createMirrorElement}.
     * @example
     * var p1 = board.create('point', [3.0, 3.0]);
     * var p2 = board.create('point', [6.0, 1.0]);
     *
     * var mp1 = board.create('mirrorpoint', [p1, p2]);
     * </pre><div class="jxgbox" id="JXG7eb2a814-6c4b-4caa-8cfa-4183a948d25b" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var mpex1_board = JXG.JSXGraph.initBoard('JXG7eb2a814-6c4b-4caa-8cfa-4183a948d25b', {boundingbox: [-1, 9, 9, -1], axis: true, showcopyright: false, shownavigation: false});
     *   var mpex1_p1 = mpex1_board.create('point', [3.0, 3.0]);
     *   var mpex1_p2 = mpex1_board.create('point', [6.0, 1.0]);
     *   var mpex1_mp1 = mpex1_board.create('mirrorpoint', [mpex1_p1, mpex1_p2]);
     * </script><pre>
     */
    JXG.createMirrorPoint = function (board, parents, attributes) {
        var el = JXG.createMirrorElement(board, parents, attributes);
        el.elType = 'mirrorpoint';
        return el;
    };

    /**
     * @class This element is used to visualize the integral of a given curve over a given interval.
     * @pseudo
     * @description The Integral element is used to visualize the area under a given curve over a given interval
     * and to calculate the area's value. For that a polygon and gliders are used. The polygon displays the area,
     * the gliders are used to change the interval dynamically.
     * @constructor
     * @name Integral
     * @type JXG.Curve
     * @augments JXG.Curve
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {Array_JXG.Curve} i,c The constructed element covers the area between the curve <tt>c</tt> and the x-axis
     * within the interval <tt>i</tt>.
     * @example
     * var c1 = board.create('functiongraph', [function (t) { return t*t*t; }]);
     * var i1 = board.create('integral', [[-2.0, 2.0], c1]);
     * </pre><div class="jxgbox" id="JXGd45d7188-6624-4d6e-bebb-1efa2a305c8a" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var intex1_board = JXG.JSXGraph.initBoard('JXGd45d7188-6624-4d6e-bebb-1efa2a305c8a', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false});
     *   var intex1_c1 = intex1_board.create('functiongraph', [function (t) { return Math.cos(t)*t; }]);
     *   var intex1_i1 = intex1_board.create('integral', [[-2.0, 2.0], intex1_c1]);
     * </script><pre>
     */
    JXG.createIntegral = function (board, parents, attributes) {
        var interval, curve, attr,
            start, end, startx, starty, endx, endy,
            pa_on_curve, pa_on_axis, pb_on_curve, pb_on_axis,
            t = null, p;

        if (Type.isArray(parents[0]) && parents[1].elementClass === Const.OBJECT_CLASS_CURVE) {
            interval = parents[0];
            curve = parents[1];
        } else if (Type.isArray(parents[1]) && parents[0].elementClass === Const.OBJECT_CLASS_CURVE) {
            interval = parents[1];
            curve = parents[0];
        } else {
            throw new Error("JSXGraph: Can't create integral with parent types '" +
                (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                "\nPossible parent types: [[number|function,number|function],curve]");
        }

        attr = Type.copyAttributes(attributes, board.options, 'integral');
        attr.withLabel = false;  // There is a custom 'label' below.
        p = board.create('curve', [[0], [0]], attr);

        // Correct the interval if necessary - NOT ANYMORE, GGB's fault
        start = interval[0];
        end = interval[1];

        if (Type.isFunction(start)) {
            startx = start;
            starty = function () { return curve.Y(startx()); };
            start = startx();
        } else {
            startx = start;
            starty = curve.Y(start);
        }

        if (Type.isFunction(end)) {
            endx = end;
            endy = function () { return curve.Y(endx()); };
            end = endx();
        } else {
            endx = end;
            endy = curve.Y(end);
        }

        attr = Type.copyAttributes(attributes, board.options, 'integral', 'curveLeft');
        pa_on_curve = board.create('glider', [startx, starty, curve], attr);
        if (Type.isFunction(startx)) {
            pa_on_curve.hideElement();
        }

        attr = Type.copyAttributes(attributes, board.options, 'integral', 'baseLeft');
        pa_on_axis = board.create('point', [
            function () {
                if (Type.evaluate(p.visProp.axis) === 'y') {
                    return 0;
                }

                return pa_on_curve.X();
            },
            function () {
                if (Type.evaluate(p.visProp.axis) === 'y') {
                    return pa_on_curve.Y();
                }

                return 0;
            }
        ], attr);

        attr = Type.copyAttributes(attributes, board.options, 'integral', 'curveRight');
        pb_on_curve = board.create('glider', [endx, endy, curve], attr);
        if (Type.isFunction(endx)) {
            pb_on_curve.hideElement();
        }

        attr = Type.copyAttributes(attributes, board.options, 'integral', 'baseRight');
        pb_on_axis = board.create('point', [
            function () {
                if (Type.evaluate(p.visProp.axis) === 'y') {
                    return 0;
                }
                return pb_on_curve.X();
            },
            function () {
                if (Type.evaluate(p.visProp.axis) === 'y') {
                    return pb_on_curve.Y();
                }

                return 0;
            }
        ], attr);

        attr = Type.copyAttributes(attributes, board.options, 'integral');
        if (attr.withlabel !== false && attr.axis !== 'y') {
            attr = Type.copyAttributes(attributes, board.options, 'integral', 'label');
            attr = Type.copyAttributes(attr, board.options, 'label');

            t = board.create('text', [
                function () {
                    var off = new Coords(Const.COORDS_BY_SCREEN, [
                            Type.evaluate(this.visProp.offset[0]) + this.board.origin.scrCoords[1],
                            0
                        ], this.board, false),
                        bb = this.board.getBoundingBox(),
                        dx = (bb[2] - bb[0]) * 0.1,
                        x = pb_on_curve.X();

                    if (x < bb[0]) {
                        x = bb[0] + dx;
                    } else if (x > bb[2]) {
                        x = bb[2] - dx;
                    }

                    return x + off.usrCoords[1];
                },
                function () {
                    var off = new Coords(Const.COORDS_BY_SCREEN, [
                            0,
                            Type.evaluate(this.visProp.offset[1]) + this.board.origin.scrCoords[2]
                        ], this.board, false),
                        bb = this.board.getBoundingBox(),
                        dy = (bb[1] - bb[3]) * 0.1,
                        y = pb_on_curve.Y();

                    if (y > bb[1]) {
                        y = bb[1] - dy;
                    } else if (y < bb[3]) {
                        y = bb[3] + dy;
                    }

                    return y + off.usrCoords[2];
                },
                function () {
                    var Int = Numerics.NewtonCotes([pa_on_axis.X(), pb_on_axis.X()], curve.Y);
                    return '&int; = ' + Type.toFixed(Int, 4);
                }
            ], attr);

            t.dump = false;

            pa_on_curve.addChild(t);
            pb_on_curve.addChild(t);
        }

        // dump stuff
        pa_on_curve.dump = false;
        pa_on_axis.dump = false;

        pb_on_curve.dump = false;
        pb_on_axis.dump = false;

        p.elType = 'integral';
        p.setParents([curve.id, interval]);
        p.subs = {
            curveLeft: pa_on_curve,
            baseLeft: pa_on_axis,
            curveRight: pb_on_curve,
            baseRight: pb_on_axis
        };
        p.inherits.push(pa_on_curve, pa_on_axis, pb_on_curve, pb_on_axis);

        if (attr.withLabel) {
            p.subs.label = t;
            p.inherits.push(t);
        }

        /**
         * Returns the current value of the integral.
         * @memberOf Integral
         * @name Value
         * @function
         * @returns {Number}
         */
        p.Value = function () {
            return Numerics.I([pa_on_axis.X(), pb_on_axis.X()], curve.Y);
        };

        /**
         * documented in JXG.Curve
         * @ignore
         */
        p.updateDataArray = function () {
            var x, y,
                i, left, right,
                lowx, upx,
                lowy, upy;

            if (Type.evaluate(this.visProp.axis) === 'y') {
                if (pa_on_curve.Y() < pb_on_curve.Y()) {
                    lowx = pa_on_curve.X();
                    lowy = pa_on_curve.Y();
                    upx = pb_on_curve.X();
                    upy = pb_on_curve.Y();
                } else {
                    lowx = pb_on_curve.X();
                    lowy = pb_on_curve.Y();
                    upx = pa_on_curve.X();
                    upy = pa_on_curve.Y();
                }
                left = Math.min(lowx, upx);
                right = Math.max(lowx, upx);

                x = [0, lowx];
                y = [lowy, lowy];

                for (i = 0; i < curve.numberPoints; i++) {
                    if (lowy <= curve.points[i].usrCoords[2] &&
                            left <= curve.points[i].usrCoords[1] &&
                            curve.points[i].usrCoords[2] <= upy  &&
                            curve.points[i].usrCoords[1] <= right) {
                        x.push(curve.points[i].usrCoords[1]);
                        y.push(curve.points[i].usrCoords[2]);
                    }
                }
                x.push(upx);
                y.push(upy);
                x.push(0);
                y.push(upy);

                // close the curve
                x.push(0);
                y.push(lowy);
            } else {
                if (pa_on_axis.X() < pb_on_axis.X()) {
                    left = pa_on_axis.X();
                    right = pb_on_axis.X();
                } else {
                    left = pb_on_axis.X();
                    right = pa_on_axis.X();
                }

                x = [left, left];
                y = [0, curve.Y(left)];

                for (i = 0; i < curve.numberPoints; i++) {
                    if ((left <= curve.points[i].usrCoords[1]) && (curve.points[i].usrCoords[1] <= right)) {
                        x.push(curve.points[i].usrCoords[1]);
                        y.push(curve.points[i].usrCoords[2]);
                    }
                }
                x.push(right);
                y.push(curve.Y(right));
                x.push(right);
                y.push(0);

                // close the curve
                x.push(left);
                y.push(0);
            }

            this.dataX = x;
            this.dataY = y;
        };

        pa_on_curve.addChild(p);
        pb_on_curve.addChild(p);
        pa_on_axis.addChild(p);
        pb_on_axis.addChild(p);

        /**
         * The point on the axis initially corresponding to the lower value of the interval.
         *
         * @name baseLeft
         * @memberOf Integral
         * @type JXG.Point
         */
        p.baseLeft = pa_on_axis;

        /**
         * The point on the axis initially corresponding to the higher value of the interval.
         *
         * @name baseRight
         * @memberOf Integral
         * @type JXG.Point
         */
        p.baseRight = pb_on_axis;

        /**
         * The glider on the curve corresponding to the lower value of the interval.
         *
         * @name curveLeft
         * @memberOf Integral
         * @type Glider
         */
        p.curveLeft = pa_on_curve;

        /**
         * The glider on the axis corresponding to the higher value of the interval.
         *
         * @name curveRight
         * @memberOf Integral
         * @type Glider
         */
        p.curveRight = pb_on_curve;

        p.methodMap = JXG.deepCopy(p.methodMap, {
            curveLeft: 'curveLeft',
            baseLeft: 'baseLeft',
            curveRight: 'curveRight',
            baseRight: 'baseRight',
            Value: 'Value'
        });

        /**
         * documented in GeometryElement
         * @ignore
         */
        p.label = t;

        return p;
    };

    /**
     * @class Creates a grid to support the user with element placement.
     * @pseudo
     * @description A grid is a set of vertical and horizontal lines to support the user with element placement. This method
     * draws such a grid on the given board. This method does not
     * take any parent elements. It is usually instantiated on the board's creation via the attribute <tt>grid</tt> set
     * to true.
     * @parameter None.
     * @constructor
     * @name Grid
     * @type JXG.Curve
     * @augments JXG.Curve
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @example
     * grid = board.create('grid', []);
     * </pre><div class="jxgbox" id="JXGa9a0671f-7a51-4fa2-8697-241142c00940" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     * (function () {
     *  board = JXG.JSXGraph.initBoard('JXGa9a0671f-7a51-4fa2-8697-241142c00940', {boundingbox:[-4, 6, 10, -6], axis: false, grid: false, keepaspectratio: true});
     *  grid = board.create('grid', []);
     * })();
     * </script><pre>
     */
    JXG.createGrid = function (board, parents, attributes) {
        var c, attr;

        attr = Type.copyAttributes(attributes, board.options, 'grid');
        c = board.create('curve', [[null], [null]], attr);

        c.elType = 'grid';
        c.type = Const.OBJECT_TYPE_GRID;

        /**
         * @ignore
         */
         c.updateDataArray = function () {
            var start, end, i, topLeft, bottomRight,
                gridX = Type.evaluate(this.visProp.gridx),
                gridY = Type.evaluate(this.visProp.gridy);

            if (Type.isArray(this.visProp.topleft)) {
                topLeft = new Coords(Type.evaluate(this.visProp.tltype) || Const.COORDS_BY_USER,
                                    this.visProp.topleft, board);
            } else {
                topLeft = new Coords(Const.COORDS_BY_SCREEN, [0, 0], board);
            }

            if (Type.isArray(this.visProp.bottomright)) {
                bottomRight = new Coords(Type.evaluate(this.visProp.brtype) || Const.COORDS_BY_USER,
                                    this.visProp.bottomright, board);
            } else {
                bottomRight = new Coords(Const.COORDS_BY_SCREEN, [board.canvasWidth, board.canvasHeight], board);
            }


            //
            //      |         |         |
            //  ----+---------+---------+-----
            //      |        /|         |
            //      |    gridY|     <---+------   Grid Cell
            //      |        \|         |
            //  ----+---------+---------+-----
            //      |         |\ gridX /|
            //      |         |         |
            //
            // uc: usercoordinates
            //
            // currently one grid cell is 1/JXG.Options.grid.gridX uc wide and 1/JXG.Options.grid.gridY uc high.
            // this may work perfectly with GeonextReader (#readGeonext, initialization of gridX and gridY) but it
            // is absolutely not user friendly when it comes to use it as an API interface.
            // i changed this to use gridX and gridY as the actual width and height of the grid cell. for this i
            // had to refactor these methods:
            //
            //  DONE JXG.Board.calculateSnapSizes (init p1, p2)
            //  DONE JXG.GeonextReader.readGeonext (init gridX, gridY)
            //

            board.options.grid.hasGrid = true;

			// fix_grid: adding integer function to calculation of start and end values, and adding to calculation of start and end values below
			// To allow this:
			// (axes on the outside, min value of grid = 0.25)
            //
            //      |    |         |          |
            // 1.5 -+----+---------+----------+-----
            //      |    |         |          |
            //      |    |         |          |
            //      |    |         |          |
            //   1 -+----+---------+----------+-----
            //      |    |         |          |
            //      |    |         |          |
            //      |    |         |          |
            // 0.5 -+----+---------+----------+-----
            //      |    |         |          |
            //      +----+---------+----------+-----
            //           |         |          |
            //          0.5        1         1.5
            //
            // fix_grid: these lines disabled:
            // topLeft.setCoordinates(Const.COORDS_BY_USER, [Math.ceil(topLeft.usrCoords[1] / gridX) * gridX, Math.floor(topLeft.usrCoords[2] / gridY) * gridY]);
            // bottomRight.setCoordinates(Const.COORDS_BY_USER, [Math.floor(bottomRight.usrCoords[1] / gridX) * gridX, Math.ceil(bottomRight.usrCoords[2] / gridY) * gridY]);

            c.dataX = [];
            c.dataY = [];

            // Sometimes the bounding box is used to invert the axis. We have to take this into account here.
            // fix_grid: adding integer function to calculation of start and end values
            start = Math.floor(topLeft.usrCoords[2] / gridY) * gridY;
            end = Math.ceil(bottomRight.usrCoords[2] / gridY) * gridY;

            if (topLeft.usrCoords[2] < bottomRight.usrCoords[2]) {
                start = Math.ceil(bottomRight.usrCoords[2] / gridY) * gridY; // bottomRight.usrCoords[2];
                end = Math.floor(topLeft.usrCoords[2] / gridY) * gridY;
            }

            // start with the horizontal grid:
            for (i = start; i > end - gridY; i -= gridY) {
                c.dataX.push(topLeft.usrCoords[1], bottomRight.usrCoords[1], NaN);
                c.dataY.push(i, i, NaN);
            }

            // fix_grid: adding integer function to calculation of start and end values
            start = Math.ceil(topLeft.usrCoords[1] / gridX) * gridX;
            end = Math.floor(bottomRight.usrCoords[1] / gridX) * gridX;

            if (topLeft.usrCoords[1] > bottomRight.usrCoords[1]) {
				start = Math.floor(bottomRight.usrCoords[1] / gridX) * gridX;
				end = Math.ceil(topLeft.usrCoords[1] / gridX) * gridX;
            }

            // build vertical grid
            for (i = start; i < end + gridX; i += gridX) {
                c.dataX.push(i, i, NaN);
                c.dataY.push(topLeft.usrCoords[2], bottomRight.usrCoords[2], NaN);
            }

        };

        // we don't care about highlighting so we turn it off completely to save a lot of
        // time on every mouse move
        c.hasPoint = function () {
            return false;
        };

        board.grids.push(c);

        return c;
    };

    /**
     * @class Creates an area indicating the solution of a linear inequality or an inequality
     * of a function graph, i.e. an inequality of type y <= f(x).
     * @pseudo
     * @description Display the solution set of a linear inequality (less than or equal to).
     * To be precise, the solution set of the inequality <i>y <= b/a * x + c/a</i> is shown.
     * In case <i>a = 0</i>, that is if the equation of the line is <i>bx + c = 0</i>,
     * the area of the inequality <i>bx + c <= 0</i> is shown.
     * <p>
     * For function graphs the area below the function graph is filled, i.e. the
     * area of the inequality y <= f(x).
     * With the attribute inverse:true the area of the inequality y >= f(x) is filled.
     *
     * @param {JXG.Line} l The area drawn will be the area below this line. With the attribute
     * inverse:true, the inequality 'greater than or equal to' is shown.
     * @constructor
     * @name Inequality
     * @type JXG.Curve
     * @augments JXG.Curve
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @example
     * var p = board.create('point', [1, 3]),
     *     q = board.create('point', [-2, -4]),
     *     l = board.create('line', [p, q]),
     *     ineq = board.create('inequality', [l]);
     * ineq = board.create('inequality', [l]);
     * </pre><div class="jxgbox" id="JXG2b703006-fd98-11e1-b79e-ef9e591c002e" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     * (function () {
     *  var board = JXG.JSXGraph.initBoard('JXG2b703006-fd98-11e1-b79e-ef9e591c002e', {boundingbox:[-4, 6, 10, -6], axis: false, grid: false, keepaspectratio: true}),
     *      p = board.create('point', [1, 3]),
     *      q = board.create('point', [-2, -4]),
     *      l = board.create('line', [p, q]),
     *      ineq = board.create('inequality', [l]);
     * })();
     * </script><pre>
     *
     * @example
     * // Plot the inequality
     * //     y >= 2/3 x + 1
     * // or
     * //     0 >= -3y + 2x +1
     * var l = board.create('line', [1, 2, -3]),
     *     ineq = board.create('inequality', [l], {inverse:true});
     * </pre><div class="jxgbox" id="JXG1ded3812-2da4-4323-abaf-1db4bad1bfbd" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     * (function () {
     *  var board = JXG.JSXGraph.initBoard('JXG1ded3812-2da4-4323-abaf-1db4bad1bfbd', {boundingbox:[-4, 6, 10, -6], axis: false, grid: false, keepaspectratio: true}),
     *      l = board.create('line', [1, 2, -3]),
     *      ineq = board.create('inequality', [l], {inverse:true});
     * })();
     * </script><pre>
     *
     * @example
     * var f = board.create('functiongraph', ['sin(x)', -2*Math.PI, 2*Math.PI]);
     *
     * var ineq_lower = board.create('inequality', [f]);
     * var ineq_greater = board.create('inequality', [f], {inverse: true, fillColor: 'yellow'});
     *
     *
     * </pre><div id="JXGdb68c574-414c-11e8-839a-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGdb68c574-414c-11e8-839a-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var f = board.create('functiongraph', ['sin(x)', -2*Math.PI, 2*Math.PI]);
     *
     *     var ineq_lower = board.create('inequality', [f]);
     *     var ineq_greater = board.create('inequality', [f], {inverse: true, fillColor: 'yellow'});
     *
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createInequality = function (board, parents, attributes) {
        var f, a, attr;

        attr = Type.copyAttributes(attributes, board.options, 'inequality');
        if (parents[0].elementClass === Const.OBJECT_CLASS_LINE) {
            a = board.create('curve', [[], []], attr);
            a.hasPoint = function () {
                return false;
            };
            a.updateDataArray = function () {
                var i1, i2,
                    // This will be the height of the area. We mustn't rely upon the board height because if we pan the view
                    // such that the line is not visible anymore, the borders of the area will get visible in some cases.
                    h,
                    bb = board.getBoundingBox(),
                    factor = attr.inverse ? -1 : 1,
                    expansion = 1.5,
                    w = expansion * Math.max(bb[2] - bb[0], bb[1] - bb[3]),
                    // Fake a point (for Math.Geometry.perpendicular)
                    // contains centroid of the board
                    dp = {
                        coords: {
                            usrCoords: [1, (bb[0] + bb[2]) / 2, attr.inverse ? bb[1] : bb[3]]
                        }
                    },

                    slope1 = parents[0].stdform.slice(1),
                    slope2 = slope1;

                // This is wrong. Example:
                // var line = board.create('line', [0, -1, -1]);
                // var ineq = board.create('inequality', [line]);
                //
                // if (slope1[1] > 0) {
                //     slope1 = Statistics.multiply(slope1, -1);
                //     slope2 = slope1;
                // }

                // Calculate the area height as
                //  expansion times the distance of the line to the
                // point in the middle of the top/bottom border.
                h = expansion * Math.max(Geometry.perpendicular(parents[0], dp, board)[0].distance(Const.COORDS_BY_USER, dp.coords), w);
                h *= factor;

                // reuse dp
                dp = {
                    coords: {
                        usrCoords: [1, (bb[0] + bb[2]) / 2, (bb[1] + bb[3]) / 2]
                    }
                };

                // If dp is on the line, Geometry.perpendicular will return a point not on the line.
                // Since this somewhat odd behavior of Geometry.perpendicular is needed in GEONExT,
                // it is circumvented here.
                if (Math.abs(Mat.innerProduct(dp.coords.usrCoords, parents[0].stdform, 3)) >= Mat.eps) {
                    dp = Geometry.perpendicular(parents[0], dp, board)[0].usrCoords;
                } else {
                    dp = dp.coords.usrCoords;
                }
                i1 = [1, dp[1] + slope1[1] * w, dp[2] - slope1[0] * w];
                i2 = [1, dp[1] - slope2[1] * w, dp[2] + slope2[0] * w];

                // One of the vectors based in i1 and orthogonal to the parent line has the direction d1 = (slope1, -1)
                // We will go from i1 to to i1 + h*d1, from there to i2 + h*d2 (with d2 calculated equivalent to d1) and
                // end up in i2.
                this.dataX = [i1[1], i1[1] + slope1[0] * h, i2[1] + slope2[0] * h, i2[1], i1[1]];
                this.dataY = [i1[2], i1[2] + slope1[1] * h, i2[2] + slope2[1] * h, i2[2], i1[2]];
            };
        } else if (parents[0].elementClass === Const.OBJECT_CLASS_CURVE &&
            parents[0].visProp.curvetype === 'functiongraph') {

            a = board.create('curve', [[], []], attr);
            a.updateDataArray = function() {
                var bbox = this.board.getBoundingBox(),
                    points = [],
                    infty, first, last,
                    len, i,
                    mi = parents[0].minX(),
                    ma = parents[0].maxX(),
                    curve_mi, curve_ma,
                    firstx,
                    lastx,
                    enlarge = (bbox[1] - bbox[3]) * 0.3, // enlarge the bbox vertically by this amount
                    inverse = Type.evaluate(this.visProp.inverse);

                // inverse == true <=> Fill area with y >= f(x)
                infty = (inverse) ? 1 : 3; // we will use either bbox[1] or bbox[3] below

                this.dataX = [];
                this.dataY = [];
                len = parents[0].points.length;
                if (len === 0) {
                    return;
                }

                bbox[1] += enlarge;
                bbox[3] -= enlarge;

                last = -1;
                while (last < len - 1) {

                    // Find the first point with real coordinates on this curve segment
                    for (i = last + 1, first = len; i < len; i++) {
                        if (parents[0].points[i].isReal()) {
                            first = i;
                            break;
                        }
                    }
                    // No real points found -> exit
                    if (first >= len) {
                        break;
                    }

                    // Find the last point with real coordinates on this curve segment
                    for (i = first, last = len - 1; i < len - 1; i++) {
                        if (!parents[0].points[i + 1].isReal()) {
                            last = i;
                            break;
                        }
                    }

                    firstx = parents[0].points[first].usrCoords[1];
                    lastx = parents[0].points[last].usrCoords[1];

                    // Restrict the plot interval if the function ends inside of the board
                    curve_mi = (bbox[0] < mi) ? mi : bbox[0];
                    curve_ma = (bbox[2] > ma) ? ma : bbox[2];

                    // Found NaNs
                    curve_mi = (first === 0)      ? curve_mi : Math.max(curve_mi, firstx);
                    curve_ma = (last === len - 1) ? curve_ma : Math.min(curve_ma, lastx);

                    // First and last relevant x-coordinate of the curve
                    curve_mi = (first === 0)     ? mi: firstx;
                    curve_ma = (last === len - 1)? ma: lastx;


                    // Copy the curve points
                    points = [];

                    points.push([1, curve_mi, bbox[infty]]);
                    points.push([1, curve_mi, parents[0].points[first].usrCoords[2]]);
                    for (i = first; i <= last; i++) {
                        points.push(parents[0].points[i].usrCoords);
                    }
                    points.push([1, curve_ma, parents[0].points[last].usrCoords[2]]);
                    points.push([1, curve_ma, bbox[infty]]);
                    points.push(points[0]);

                    for (i = 0; i < points.length; i++) {
                        this.dataX.push(points[i][1]);
                        this.dataY.push(points[i][2]);
                    }


                    if (last < len - 1) {
                        this.dataX.push(NaN);
                        this.dataY.push(NaN);
                    }
              }

            };

            // Previous code:
            a.hasPoint = function () {
                return false;
            };
        } else {
            // Not yet practical?
            f = Type.createFunction(parents[0]);
            if (!Type.exists(f)) {
                throw new Error("JSXGraph: Can't create area with the given parents." +
                    "\nPossible parent types: [line], [function]");
            }
        }

        a.addParents(parents[0]);
        return a;
    };


    JXG.registerElement('arrowparallel', JXG.createArrowParallel);
    JXG.registerElement('bisector', JXG.createBisector);
    JXG.registerElement('bisectorlines', JXG.createAngularBisectorsOfTwoLines);
    JXG.registerElement('msector', JXG.createMsector);
    JXG.registerElement('circumcircle', JXG.createCircumcircle);
    JXG.registerElement('circumcirclemidpoint', JXG.createCircumcenter);
    JXG.registerElement('circumcenter', JXG.createCircumcenter);
    JXG.registerElement('incenter', JXG.createIncenter);
    JXG.registerElement('incircle', JXG.createIncircle);
    JXG.registerElement('integral', JXG.createIntegral);
    JXG.registerElement('midpoint', JXG.createMidpoint);
    JXG.registerElement('mirrorelement', JXG.createMirrorElement);
    JXG.registerElement('mirrorpoint', JXG.createMirrorPoint);
    JXG.registerElement('normal', JXG.createNormal);
    JXG.registerElement('orthogonalprojection', JXG.createOrthogonalProjection);
    JXG.registerElement('parallel', JXG.createParallel);
    JXG.registerElement('parallelpoint', JXG.createParallelPoint);
    JXG.registerElement('perpendicular', JXG.createPerpendicular);
    JXG.registerElement('perpendicularpoint', JXG.createPerpendicularPoint);
    JXG.registerElement('perpendicularsegment', JXG.createPerpendicularSegment);
    JXG.registerElement('reflection', JXG.createReflection);
    JXG.registerElement('grid', JXG.createGrid);
    JXG.registerElement('inequality', JXG.createInequality);

    return {
        createArrowParallel: JXG.createArrowParallel,
        createBisector: JXG.createBisector,
        createAngularBisectorOfTwoLines: JXG.createAngularBisectorsOfTwoLines,
        createCircumcircle: JXG.createCircumcircle,
        createCircumcenter: JXG.createCircumcenter,
        createIncenter: JXG.createIncenter,
        createIncircle: JXG.createIncircle,
        createIntegral: JXG.createIntegral,
        createMidpoint: JXG.createMidpoint,
        createMirrorElement: JXG.createMirrorElement,
        createMirrorPoint: JXG.createMirrorPoint,
        createNormal: JXG.createNormal,
        createOrthogonalProjection: JXG.createOrthogonalProjection,
        createParallel: JXG.createParallel,
        createParallelPoint: JXG.createParallelPoint,
        createPerpendicular: JXG.createPerpendicular,
        createPerpendicularPoint: JXG.createPerpendicularPoint,
        createPerpendicularSegmen: JXG.createPerpendicularSegment,
        createReflection: JXG.createReflection,
        createGrid: JXG.createGrid,
        createInequality: JXG.createInequality
    };
});

/*
 Copyright 2008-2022
 Matthias Ehmann,
 Michael Gerhaeuser,
 Carsten Miller,
 Bianca Valentin,
 Alfred Wassermann,
 Peter Wilfahrt

 This file is part of JSXGraph.

 JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

 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 and
 the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
 and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/math
 math/geometry
 math/numerics
 math/statistics
 math/symbolic
 base/composition
 base/coords
 base/constants
 utils/type
 elements:
 line
 circle
 transform
 point
 glider
 text
 curve
 */

define('element/locus',[
    'jxg', 'math/symbolic', 'utils/type'
], function (JXG, Symbolic, Type) {

    "use strict";

    /**
     * @class This element is used to visualize the locus of a given dependent point.
     * @pseudo
     * @description The locus element is used to visualize the curve a given point describes.
     * @constructor
     * @name Locus
     * @type JXG.Curve
     * @augments JXG.Curve
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Point} p The constructed curve is the geometric locus of the given point.
     * @example
     *  // This examples needs JXG.Server up and running, otherwise it won't work.
     *  p1 = board.create('point', [0, 0]);
     *  p2 = board.create('point', [6, -1]);
     *  c1 = board.create('circle', [p1, 2]);
     *  c2 = board.create('circle', [p2, 1.5]);
     *  g1 = board.create('glider', [6, 3, c1]);
     *  c3 = board.create('circle', [g1, 4]);
     *  g2 = board.create('intersection', [c2,c3,0]);
     *  m1 = board.create('midpoint', [g1,g2]);
     *  loc = board.create('locus', [m1], {strokeColor: 'red'});
     * </pre><div class="jxgbox" id="JXGd45d7188-6624-4d6e-bebb-1efa2a305c8a" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *  lcex_board = JXG.JSXGraph.initBoard('JXGd45d7188-6624-4d6e-bebb-1efa2a305c8a', {boundingbox:[-4, 6, 10, -6], axis: true, grid: false, keepaspectratio: true});
     *  lcex_p1 = lcex_board.create('point', [0, 0]);
     *  lcex_p2 = lcex_board.create('point', [6, -1]);
     *  lcex_c1 = lcex_board.create('circle', [lcex_p1, 2]);
     *  lcex_c2 = lcex_board.create('circle', [lcex_p2, 1.5]);
     *  lcex_g1 = lcex_board.create('glider', [6, 3, lcex_c1]);
     *  lcex_c3 = lcex_board.create('circle', [lcex_g1, 4]);
     *  lcex_g2 = lcex_board.create('intersection', [lcex_c2,lcex_c3,0]);
     *  lcex_m1 = lcex_board.create('midpoint', [lcex_g1,lcex_g2]);
     *  lcex_loc = board.create('locus', [lcex_m1], {strokeColor: 'red'});
     * </script><pre>
     */
    JXG.createLocus = function (board, parents, attributes) {
        var c, p;

        if (Type.isArray(parents) && parents.length === 1 && Type.isPoint(parents[0])) {
            p = parents[0];
        } else {
            throw new Error("JSXGraph: Can't create locus with parent of type other than point." +
                "\nPossible parent types: [point]");
        }

        c = board.create('curve', [[null], [null]], attributes);
        c.dontCallServer = false;

        c.elType = 'locus';
        c.setParents([p.id]);

        /**
         * Should be documented in JXG.Curve
         * @ignore
         */
        c.updateDataArray = function () {
            var spe, cb, data;

            if (c.board.mode > 0) {
                return;
            }

            spe = Symbolic.generatePolynomials(board, p, true).join('|');
            if (spe === c.spe) {
                return;
            }

            c.spe = spe;

            cb = function (x, y, eq, t) {
                c.dataX = x;
                c.dataY = y;

                /**
                 * The implicit definition of the locus.
                 * @memberOf Locus.prototype
                 * @name eq
                 * @type String
                 */
                c.eq = eq;

                /**
                 * The time it took to calculate the locus
                 * @memberOf Locus.prototype
                 * @name ctime
                 * @type Number
                 */
                c.ctime = t;

                // convert equation and use it to build a generatePolynomial-method
                c.generatePolynomial = (function (equations) {
                    return function (point) {
                        var i,
                            x = '(' + point.symbolic.x + ')',
                            y = '(' + point.symbolic.y + ')',
                            res = [];

                        for (i = 0; i < equations.length; i++) {
                            res[i] = equations[i].replace(/\*\*/g, '^').replace(/x/g, x).replace(/y/g, y);
                        }

                        return res;
                    };
                }(eq));
            };
            data = Symbolic.geometricLocusByGroebnerBase(board, p, cb);

            cb(data.datax, data.datay, data.polynomial, data.exectime);
        };
        return c;
    };

    JXG.registerElement('locus', JXG.createLocus);

    return {
        createLocus: JXG.createLocus
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 base/coords
 base/element
 math/math
 utils/type
 */

/**
 * @fileoverview In this file the geometry element Image is defined.
 */

define('base/image',[
    'jxg', 'base/constants', 'base/coords', 'base/element', 'math/math', 'utils/type', 'base/coordselement'
], function (JXG, Const, Coords, GeometryElement, Mat, Type, CoordsElement) {

    "use strict";

    /**
     * Construct and handle images
     *
     * The image can be supplied as an URL or an base64 encoded inline image
     * like "data:image/png;base64, /9j/4AAQSkZJRgA..." or a function returning
     * an URL: function(){ return 'xxx.png; }.
     *
     * @class Creates a new image object. Do not use this constructor to create a image. Use {@link JXG.Board#create} with
     * type {@link Image} instead.
     * @augments JXG.GeometryElement
     * @augments JXG.CoordsElement
     * @param {string|JXG.Board} board The board the new image is drawn on.
     * @param {Array} coordinates An array with the user coordinates of the image.
     * @param {Object} attributes An object containing visual and - optionally - a name and an id.
     * @param {string|function} url An URL string or a function returning an URL string.
     * @param  {Array} size Array containing width and height of the image in user coordinates.
     *
     */
    JXG.Image = function (board, coords, attributes, url, size) {
        this.constructor(board, attributes, Const.OBJECT_TYPE_IMAGE, Const.OBJECT_CLASS_OTHER);
        this.element = this.board.select(attributes.anchor);
        this.coordsConstructor(coords);

        this.W = Type.createFunction(size[0], this.board, '');
        this.H = Type.createFunction(size[1], this.board, '');

        this.usrSize = [this.W(), this.H()];

        /**
         * Array of length two containing [width, height] of the image in pixel.
         * @type array
         */
        this.size = [Math.abs(this.usrSize[0] * board.unitX), Math.abs(this.usrSize[1] * board.unitY)];

        /**
         * 'href' of the image. This might be an URL, but also a data-uri is allowed.
         * @type string
         */
        this.url = url;

        this.elType = 'image';

        // span contains the anchor point and the two vectors
        // spanning the image rectangle.
        this.span = [
            this.coords.usrCoords.slice(0),
            [this.coords.usrCoords[0], this.W(), 0],
            [this.coords.usrCoords[0], 0, this.H()]
        ];

        //this.parent = board.select(attributes.anchor);
        this.id = this.board.setId(this, 'Im');

        this.board.renderer.drawImage(this);
        this.board.finalizeAdding(this);

        this.methodMap = JXG.deepCopy(this.methodMap, {
            addTransformation: 'addTransform',
            trans: 'addTransform'
        });
    };

    JXG.Image.prototype = new GeometryElement();
    Type.copyPrototypeMethods(JXG.Image, CoordsElement, 'coordsConstructor');

    JXG.extend(JXG.Image.prototype, /** @lends JXG.Image.prototype */ {

        /**
         * Checks whether (x,y) is over or near the image;
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @returns {Boolean} True if (x,y) is over the image, False otherwise.
         */
        hasPoint: function (x, y) {
            var dx, dy, r, type, prec,
                c, v, p, dot,
                len = this.transformations.length;

                if (Type.isObject(Type.evaluate(this.visProp.precision))) {
                    type = this.board._inputDevice;
                    prec = Type.evaluate(this.visProp.precision[type]);
                } else {
                    // 'inherit'
                    prec = this.board.options.precision.hasPoint;
                }

            // Easy case: no transformation
            if (len === 0) {
                dx = x - this.coords.scrCoords[1];
                dy = this.coords.scrCoords[2] - y;
                r = prec;

                return dx >= -r && dx - this.size[0] <= r &&
                    dy >= -r && dy - this.size[1] <= r;
            }

            // Image is transformed
            c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);
            // v is the vector from anchor point to the drag point
            c = c.usrCoords;
            v = [c[0] - this.span[0][0],
                c[1] - this.span[0][1],
                c[2] - this.span[0][2]];
            dot = Mat.innerProduct;   // shortcut

            // Project the drag point to the sides.
            p = dot(v, this.span[1]);
            if (0 <= p && p <= dot(this.span[1], this.span[1])) {
                p = dot(v, this.span[2]);

                if (0 <= p && p <= dot(this.span[2], this.span[2])) {
                    return true;
                }
            }
            return false;
        },

        /**
         * Recalculate the coordinates of lower left corner and the width and height.
         *
         * @returns {JXG.GeometryElement} A reference to the element
         * @private
         */
        update: function (fromParent) {
            if (!this.needsUpdate) {
                return this;
            }

            this.updateCoords(fromParent);
            this.updateSize();
            this.updateSpan();

            return this;
        },

        /**
         * Send an update request to the renderer.
         * @private
         */
        updateRenderer: function () {
            return this.updateRendererGeneric('updateImage');
        },

        /**
         * Updates the internal arrays containing size of the image.
         * @returns {JXG.GeometryElement} A reference to the element
         * @private
         */
        updateSize: function () {
            this.usrSize = [this.W(), this.H()];
            this.size = [Math.abs(this.usrSize[0] * this.board.unitX), Math.abs(this.usrSize[1] * this.board.unitY)];

            return this;
        },

        /**
         * Update the anchor point of the image, i.e. the lower left corner
         * and the two vectors which span the rectangle.
         * @returns {JXG.GeometryElement} A reference to the element
         * @private
         *
         */
        updateSpan: function () {
            var i, j, len = this.transformations.length, v = [];

            if (len === 0) {
                this.span = [[this.Z(), this.X(), this.Y()],
                    [this.Z(), this.W(), 0],
                    [this.Z(), 0, this.H()]];
            } else {
                // v contains the three defining corners of the rectangle/image
                v[0] = [this.Z(), this.X(), this.Y()];
                v[1] = [this.Z(), this.X() + this.W(), this.Y()];
                v[2] = [this.Z(), this.X(), this.Y() + this.H()];

                // Transform the three corners
                for (i = 0; i < len; i++) {
                    for (j = 0; j < 3; j++) {
                        v[j] = Mat.matVecMult(this.transformations[i].matrix, v[j]);
                    }
                }
                // Normalize the vectors
                for (j = 0; j < 3; j++) {
                    v[j][1] /= v[j][0];
                    v[j][2] /= v[j][0];
                    v[j][0] /= v[j][0];
                }
                // Compute the two vectors spanning the rectangle
                // by subtracting the anchor point.
                for (j = 1; j < 3; j++) {
                    v[j][0] -= v[0][0];
                    v[j][1] -= v[0][1];
                    v[j][2] -= v[0][2];
                }
                this.span = v;
            }

            return this;
        },

        addTransform: function (transform) {
            var i;

            if (Type.isArray(transform)) {
                for (i = 0; i < transform.length; i++) {
                    this.transformations.push(transform[i]);
                }
            } else {
                this.transformations.push(transform);
            }

            return this;
        },

        // Documented in element.js
        getParents: function () {
            var p = [this.url, [this.Z(), this.X(), this.Y()], this.usrSize];

            if (this.parents.length !== 0) {
                p = this.parents;
            }

            return p;
        },

        /**
         * Set the width and height of the image. After setting a new size,
         * board.update() or image.fullUpdate()
         * has to be called to make the change visible.
         * @param  {number, function, string} width  Number, function or string
         *                            that determines the new width of the image
         * @param  {number, function, string} height Number, function or string
         *                            that determines the new height of the image
         * @returns {JXG.GeometryElement} A reference to the element
         *
         * @example
         * var im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg',
         *                                [-3,-2], [3,3]]);
         * im.setSize(4, 4);
         * board.update();
         *
         * </pre><div id="JXG8411e60c-f009-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG8411e60c-f009-11e5-b1bf-901b0e1b8723',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', [-3,-2],    [3,3]]);
         *     //im.setSize(4, 4);
         *     //board.update();
         *
         *     })();
         *
         * </script><pre>
         *
         * @example
         * var p0 = board.create('point', [-3, -2]),
         *     im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg',
         *                     [function(){ return p0.X(); }, function(){ return p0.Y(); }],
         *                     [3,3]]),
         *     p1 = board.create('point', [1, 2]);
         *
         * im.setSize(function(){ return p1.X() - p0.X(); }, function(){ return p1.Y() - p0.Y(); });
         * board.update();
         *
         * </pre><div id="JXG4ce706c0-f00a-11e5-b1bf-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG4ce706c0-f00a-11e5-b1bf-901b0e1b8723',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *     var p0 = board.create('point', [-3, -2]),
         *         im = board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg',
         *                         [function(){ return p0.X(); }, function(){ return p0.Y(); }],
         *                         [3,3]]),
         *         p1 = board.create('point', [1, 2]);
         *
         *     im.setSize(function(){ return p1.X() - p0.X(); }, function(){ return p1.Y() - p0.Y(); });
         *     board.update();
         *
         *     })();
         *
         * </script><pre>
         *
         */
        setSize: function(width, height) {
            this.W = Type.createFunction(width, this.board, '');
            this.H = Type.createFunction(height, this.board, '');

            // this.fullUpdate();

            return this;
        },

        /**
         * Returns the width of the image in user coordinates.
         * @returns {number} width of the image in user coordinates
         */
        W: function() {},  // Needed for docs, defined in constructor

        /**
         * Returns the height of the image in user coordinates.
         * @returns {number} height of the image in user coordinates
         */
        H: function() {}  // Needed for docs, defined in constructor

    });

    /**
     * @class Displays an image.
     * @pseudo
     * @description
     * @name Image
     * @type JXG.Image
     * @augments JXG.Image
     * @constructor
     * @constructor
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {string,function_Array_Array} url,coords,size url defines the location of the image data. The array coords contains the user coordinates
     * of the lower left corner of the image.
     *   It can consist of two or three elements of type number, a string containing a GEONE<sub>x</sub>T
     *   constraint, or a function which takes no parameter and returns a number. Every element determines one coordinate. If a coordinate is
     *   given by a number, the number determines the initial position of a free image. If given by a string or a function that coordinate will be constrained
     *   that means the user won't be able to change the image's position directly by mouse because it will be calculated automatically depending on the string
     *   or the function's return value. If two parent elements are given the coordinates will be interpreted as 2D affine Euclidean coordinates, if three such
     *   parent elements are given they will be interpreted as homogeneous coordinates.
     * <p>
     * The array size defines the image's width and height in user coordinates.
     * @example
     * var im = board.create('image', ['https://jsxgraph.org/jsxgraph/distrib/images/uccellino.jpg', [-3,-2], [3,3]]);
     *
     * </pre><div class="jxgbox" id="JXG9850cda0-7ea0-4750-981c-68bacf9cca57" style="width: 400px; height: 400px;"></div>
     * <script type="text/javascript">
     *   var image_board = JXG.JSXGraph.initBoard('JXG9850cda0-7ea0-4750-981c-68bacf9cca57', {boundingbox: [-4, 4, 4, -4], axis: true, showcopyright: false, shownavigation: false});
     *   var image_im = image_board.create('image', ['https://jsxgraph.org/distrib/images/uccellino.jpg', [-3,-2],[3,3]]);
     * </script><pre>
     */
    JXG.createImage = function (board, parents, attributes) {
        var attr, im,
            url = parents[0],
            coords = parents[1],
            size = parents[2];

        attr = Type.copyAttributes(attributes, board.options, 'image');
        im = CoordsElement.create(JXG.Image, board, coords, attr, url, size);
        if (!im) {
            throw new Error("JSXGraph: Can't create image with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                    "\nPossible parent types: [x,y], [z,x,y], [element,transformation]");
        }

        if (attr.rotate !== 0) {  // This is the default value, i.e. no rotation
            im.addRotation(attr.rotate);
        }

        return im;
    };

    JXG.registerElement('image', JXG.createImage);

    return {
        Image: JXG.Image,
        createImage: JXG.createImage
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/math
 base/constants
 base/point
 utils/type
  elements:
   point
   group
   segment
   ticks
   glider
   text
 */

/**
 * @fileoverview The geometry object slider is defined in this file. Slider stores all
 * style and functional properties that are required to draw and use a slider on
 * a board.
 */

define('element/slider',[
    'jxg', 'math/math', 'base/constants', 'base/coords', 'utils/type', 'base/point'
], function (JXG, Mat, Const, Coords, Type, Point) {

    "use strict";

    /**
     * @class A slider can be used to choose values from a given range of numbers.
     * @pseudo
     * @description
     * @name Slider
     * @augments Glider
     * @constructor
     * @type JXG.Point
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {Array_Array_Array} start,end,data The first two arrays give the start and the end where the slider is drawn
     * on the board. The third array gives the start and the end of the range the slider operates as the first resp. the
     * third component of the array. The second component of the third array gives its start value.
     * @example
     * // Create a slider with values between 1 and 10, initial position is 5.
     * var s = board.create('slider', [[1, 2], [3, 2], [1, 5, 10]]);
     * </pre><div class="jxgbox" id="JXGcfb51cde-2603-4f18-9cc4-1afb452b374d" style="width: 200px; height: 200px;"></div>
     * <script type="text/javascript">
     *   (function () {
     *     var board = JXG.JSXGraph.initBoard('JXGcfb51cde-2603-4f18-9cc4-1afb452b374d', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
     *     var s = board.create('slider', [[1, 2], [3, 2], [1, 5, 10]]);
     *   })();
     * </script><pre>
     * @example
     * // Create a slider taking integer values between 1 and 50. Initial value is 50.
     * var s = board.create('slider', [[1, 3], [3, 1], [0, 10, 50]], {snapWidth: 1, ticks: { drawLabels: true }});
     * </pre><div class="jxgbox" id="JXGe17128e6-a25d-462a-9074-49460b0d66f4" style="width: 200px; height: 200px;"></div>
     * <script type="text/javascript">
     *   (function () {
     *     var board = JXG.JSXGraph.initBoard('JXGe17128e6-a25d-462a-9074-49460b0d66f4', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
     *     var s = board.create('slider', [[1, 3], [3, 1], [1, 10, 50]], {snapWidth: 1, ticks: { drawLabels: true }});
     *   })();
     * </script><pre>
     * @example
     *     // Draggable slider
     *     var s1 = board.create('slider', [[-3,1], [2,1],[-10,1,10]], {
     *         visible: true,
     *         snapWidth: 2,
     *         point1: {fixed: false},
     *         point2: {fixed: false},
     *         baseline: {fixed: false, needsRegularUpdate: true}
     *     });
     *
     * </pre><div id="JXGbfc67817-2827-44a1-bc22-40bf312e76f8" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGbfc67817-2827-44a1-bc22-40bf312e76f8',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *         var s1 = board.create('slider', [[-3,1], [2,1],[-10,1,10]], {
     *             visible: true,
     *             snapWidth: 2,
     *             point1: {fixed: false},
     *             point2: {fixed: false},
     *             baseline: {fixed: false, needsRegularUpdate: true}
     *         });
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     *     // Set the slider by clicking on the base line: attribute 'moveOnUp'
     *     var s1 = board.create('slider', [[-3,1], [2,1],[-10,1,10]], {
     *         snapWidth: 2,
     *         moveOnUp: true // default value
     *     });
     *
     * </pre><div id="JXGc0477c8a-b1a7-4111-992e-4ceb366fbccc" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGc0477c8a-b1a7-4111-992e-4ceb366fbccc',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *         var s1 = board.create('slider', [[-3,1], [2,1],[-10,1,10]], {
     *             snapWidth: 2,
     *             moveOnUp: true // default value
     *         });
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * // Set colors
     * var sl = board.create('slider', [[-3, 1], [1, 1], [-10, 1, 10]], {
     *
     *   baseline: { strokeColor: 'blue'},
     *   highline: { strokeColor: 'red'},
     *   fillColor: 'yellow',
     *   label: {fontSize: 24, strokeColor: 'orange'},
     *   name: 'xyz', // Not shown, if suffixLabel is set
     *   suffixLabel: 'x = ',
     *   postLabel: ' u'
     *
     * });
     *
     * </pre><div id="JXGd96c9e2c-2c25-4131-b6cf-9dbb80819401" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGd96c9e2c-2c25-4131-b6cf-9dbb80819401',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var sl = board.create('slider', [[-3, 1], [1, 1], [-10, 1, 10]], {
     *
     *       baseline: { strokeColor: 'blue'},
     *       highline: { strokeColor: 'red'},
     *       fillColor: 'yellow',
     *       label: {fontSize: 24, strokeColor: 'orange'},
     *       name: 'xyz', // Not shown, if suffixLabel is set
     *       suffixLabel: 'x = ',
     *       postLabel: ' u'
     *
     *     });
     *
     *     })();
     *
     * </script><pre>
     *
 */
    JXG.createSlider = function (board, parents, attributes) {
        var pos0, pos1, smin, start, smax, sdiff,
            p1, p2, l1, ticks, ti, startx, starty, p3, l2, t,
            withText, withTicks, snapWidth, sw, s, attr, digits;

        attr = Type.copyAttributes(attributes, board.options, 'slider');
        withTicks = attr.withticks;
        withText = attr.withlabel;
        snapWidth = attr.snapwidth;

        // start point
        attr = Type.copyAttributes(attributes, board.options, 'slider', 'point1');
        p1 = board.create('point', parents[0],  attr);

        // end point
        attr = Type.copyAttributes(attributes, board.options, 'slider', 'point2');
        p2 = board.create('point', parents[1],  attr);
        //g = board.create('group', [p1, p2]);

        // Base line
        attr = Type.copyAttributes(attributes, board.options, 'slider', 'baseline');
        l1 = board.create('segment', [p1, p2], attr);

        // This is required for a correct projection of the glider onto the segment below
        l1.updateStdform();

        pos0 = p1.coords.usrCoords.slice(1);
        pos1 = p2.coords.usrCoords.slice(1);
        smin = parents[2][0];
        start = parents[2][1];
        smax = parents[2][2];
        sdiff = smax - smin;

        sw = Type.evaluate(snapWidth);
        s = (sw === -1) ? start : Math.round(start / sw) * sw;
        startx = pos0[0] + (pos1[0] - pos0[0]) * (s - smin) / (smax - smin);
        starty = pos0[1] + (pos1[1] - pos0[1]) * (s - smin) / (smax - smin);

        // glider point
        attr = Type.copyAttributes(attributes, board.options, 'slider');
        // overwrite this in any case; the sliders label is a special text element, not the gliders label.
        // this will be set back to true after the text was created (and only if withlabel was true initially).
        attr.withLabel = false;
        // gliders set snapwidth=-1 by default (i.e. deactivate them)
        p3 = board.create('glider', [startx, starty, l1], attr);
        p3.setAttribute({snapwidth: snapWidth});

        // Segment from start point to glider point: highline
        attr = Type.copyAttributes(attributes, board.options, 'slider', 'highline');
        l2 = board.create('segment', [p1, p3],  attr);

        /**
         * Returns the current slider value.
         * @memberOf Slider.prototype
         * @name Value
         * @function
         * @returns {Number}
         */
        p3.Value = function () {
            var sdiff = this._smax - this._smin,
                ev_sw = Type.evaluate(this.visProp.snapwidth);

            return ev_sw === -1 ?
                        this.position * sdiff + this._smin :
                        Math.round((this.position * sdiff + this._smin) / ev_sw) * ev_sw;
        };

        p3.methodMap = Type.deepCopy(p3.methodMap, {
            Value: 'Value',
            setValue: 'setValue',
            smax: '_smax',
            smin: '_smin',
            setMax: 'setMax',
            setMin: 'setMin'
        });

        /**
         * End value of the slider range.
         * @memberOf Slider.prototype
         * @name _smax
         * @type Number
         */
        p3._smax = smax;

        /**
         * Start value of the slider range.
         * @memberOf Slider.prototype
         * @name _smin
         * @type Number
         */
        p3._smin = smin;

        /**
         * Sets the maximum value of the slider.
         * @memberOf Slider.prototype
         * @name setMax
         * @param {Number} val New maximum value
         * @returns {Object} this object
         */
        p3.setMax = function(val) {
            this._smax = val;
            return this;
        };

        /**
         * Sets the value of the slider. This call must be followed
         * by a board update call.
         * @memberOf Slider.prototype
         * @name setValue
         * @param {Number} val New value
         * @returns {Object} this object
         */
        p3.setValue = function(val) {
            var sdiff = this._smax - this._smin;

            if (Math.abs(sdiff) > Mat.eps) {
                this.position = (val - this._smin) / sdiff;
            } else {
                this.position = 0.0; //this._smin;
            }
            this.position = Math.max(0.0, Math.min(1.0, this.position));
            return this;
        };

        /**
         * Sets the minimum value of the slider.
         * @memberOf Slider.prototype
         * @name setMin
         * @param {Number} val New minimum value
         * @returns {Object} this object
         */
        p3.setMin = function(val) {
            this._smin = val;
            return this;
        };

        if (withText) {
            attr = Type.copyAttributes(attributes, board.options, 'slider', 'label');
            t = board.create('text', [
                function () {
                    return (p2.X() - p1.X()) * 0.05 + p2.X();
                },
                function () {
                    return (p2.Y() - p1.Y()) * 0.05 + p2.Y();
                },
                function () {
                    var n,
                        d = Type.evaluate(p3.visProp.digits),
                        sl = Type.evaluate(p3.visProp.suffixlabel),
                        ul = Type.evaluate(p3.visProp.unitlabel),
                        pl = Type.evaluate(p3.visProp.postlabel);

                    if (d === 2 && Type.evaluate(p3.visProp.precision) !== 2) {
                        // Backwards compatibility
                        d = Type.evaluate(p3.visProp.precision);
                    }

                    if (sl !== null) {
                        n = sl;
                    } else if (p3.name && p3.name !== '') {
                        n = p3.name + ' = ';
                    } else {
                        n = '';
                    }

                    n += Type.toFixed(p3.Value(), d);

                    if (ul !== null) {
                        n += ul;
                    }
                    if (pl !== null) {
                        n += pl;
                    }

                    return n;
                }
            ], attr);

            /**
             * The text element to the right of the slider, indicating its current value.
             * @memberOf Slider.prototype
             * @name label
             * @type JXG.Text
             */
            p3.label = t;

            // reset the withlabel attribute
            p3.visProp.withlabel = true;
            p3.hasLabel = true;
        }

        /**
         * Start point of the base line.
         * @memberOf Slider.prototype
         * @name point1
         * @type JXG.Point
         */
        p3.point1 = p1;

        /**
         * End point of the base line.
         * @memberOf Slider.prototype
         * @name point2
         * @type JXG.Point
         */
        p3.point2 = p2;

        /**
         * The baseline the glider is bound to.
         * @memberOf Slider.prototype
         * @name baseline
         * @type JXG.Line
         */
        p3.baseline = l1;

        /**
         * A line on top of the baseline, indicating the slider's progress.
         * @memberOf Slider.prototype
         * @name highline
         * @type JXG.Line
         */
        p3.highline = l2;

        if (withTicks) {
            // Function to generate correct label texts

            attr = Type.copyAttributes(attributes, board.options, 'slider', 'ticks');
            if (!Type.exists(attr.generatelabeltext)) {
                attr.generateLabelText = function(tick, zero, value) {
                    var labelText,
                        dFull = p3.point1.Dist(p3.point2),
                        smin = p3._smin, smax = p3._smax,
                        val = this.getDistanceFromZero(zero, tick) * (smax - smin) / dFull  + smin;

                        if (dFull < Mat.eps || Math.abs(val) < Mat.eps) { // Point is zero
                            labelText = '0';
                        } else {
                            labelText = this.formatLabelText(val);
                        }
                        return labelText;
                };
            }
            ticks  = 2;
            ti = board.create('ticks', [
                p3.baseline,
                p3.point1.Dist(p1) / ticks,

                function (tick) {
                    var dFull = p3.point1.Dist(p3.point2),
                        d = p3.point1.coords.distance(Const.COORDS_BY_USER, tick);

                    if (dFull < Mat.eps) {
                        return 0;
                    }

                    return d / dFull * sdiff + smin;
                }
            ], attr);

            /**
             * Ticks give a rough indication about the slider's current value.
             * @memberOf Slider.prototype
             * @name ticks
             * @type JXG.Ticks
             */
            p3.ticks = ti;
        }

        // override the point's remove method to ensure the removal of all elements
        p3.remove = function () {
            if (withText) {
                board.removeObject(t);
            }

            board.removeObject(l2);
            board.removeObject(l1);
            board.removeObject(p2);
            board.removeObject(p1);


            Point.Point.prototype.remove.call(p3);
        };

        p1.dump = false;
        p2.dump = false;
        l1.dump = false;
        l2.dump = false;

        p3.elType = 'slider';
        p3.parents = parents;
        p3.subs = {
            point1: p1,
            point2: p2,
            baseLine: l1,
            highLine: l2
        };
        p3.inherits.push(p1, p2, l1, l2);

        if (withTicks) {
            ti.dump = false;
            p3.subs.ticks = ti;
            p3.inherits.push(ti);
        }

        p3.baseline.on('up', function(evt) {
            var pos, c;

            if (Type.evaluate(p3.visProp.moveonup) && !Type.evaluate(p3.visProp.fixed) ) {
                pos = l1.board.getMousePosition(evt, 0);
                c = new Coords(Const.COORDS_BY_SCREEN, pos, this.board);
                p3.moveTo([c.usrCoords[1], c.usrCoords[2]]);
            }
        });

        // Save the visibility attribute of the sub-elements
        // for (el in p3.subs) {
        //     p3.subs[el].status = {
        //         visible: p3.subs[el].visProp.visible
        //     };
        // }

        // p3.hideElement = function () {
        //     var el;
        //     GeometryElement.prototype.hideElement.call(this);
        //
        //     for (el in this.subs) {
        //         // this.subs[el].status.visible = this.subs[el].visProp.visible;
        //         this.subs[el].hideElement();
        //     }
        // };

//         p3.showElement = function () {
//             var el;
//             GeometryElement.prototype.showElement.call(this);
//
//             for (el in this.subs) {
// //                if (this.subs[el].status.visible) {
//                 this.subs[el].showElement();
// //                }
//             }
//         };



        // This is necessary to show baseline, highline and ticks
        // when opening the board in case the visible attributes are set
        // to 'inherit'.
        p3.prepareUpdate().update();
        if (!board.isSuspendedUpdate) {
            p3.updateVisibility().updateRenderer();
            p3.baseline.updateVisibility().updateRenderer();
            p3.highline.updateVisibility().updateRenderer();
            if (withTicks) {
                p3.ticks.updateVisibility().updateRenderer();
            }
        }

        return p3;
    };

    JXG.registerElement('slider', JXG.createSlider);

    return {
        createSlider: JXG.createSlider
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/type
 base/element
  elements:
   point
   segment
   ticks
 */

/**
 * @fileoverview Geometry objects for measurements are defined in this file. This file stores all
 * style and functional properties that are required to use a tape measure on
 * a board.
 */

define('element/measure',[
    'jxg', 'utils/type', 'base/element'
], function (JXG, Type, GeometryElement) {

    "use strict";

    /**
     * @class A tape measure can be used to measure distances between points.
     * @pseudo
     * @description
     * @name Tapemeasure
     * @augments Segment
     * @constructor
     * @type JXG.Segment
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {Array_Array} start,end, The two arrays give the initial position where the tape measure
     * is drawn on the board.
     * @example
     * // Create a tape measure
     * var p1 = board.create('point', [0,0]);
     * var p2 = board.create('point', [1,1]);
     * var p3 = board.create('point', [3,1]);
     * var tape = board.create('tapemeasure', [[1, 2], [4, 2]], {name:'dist'});
     * </pre><div class="jxgbox" id="JXG6d9a2cda-22fe-4cd1-9d94-34283b1bdc01" style="width: 200px; height: 200px;"></div>
     * <script type="text/javascript">
     *   (function () {
     *     var board = JXG.JSXGraph.initBoard('JXG6d9a2cda-22fe-4cd1-9d94-34283b1bdc01', {boundingbox: [-1, 5, 5, -1], axis: true, showcopyright: false, shownavigation: false});
     *     var p1 = board.create('point', [0,0]);
     *     var p2 = board.create('point', [1,1]);
     *     var p3 = board.create('point', [3,1]);
     *     var tape = board.create('tapemeasure', [[1, 2], [4, 2]], {name:'dist'} );
     *   })();
     * </script><pre>
     */
    JXG.createTapemeasure = function (board, parents, attributes) {
        var pos0, pos1,
            attr, withTicks, withText, digits,
            li, p1, p2, n, ti;

        pos0 = parents[0];
        pos1 = parents[1];

        // start point
        attr = Type.copyAttributes(attributes, board.options, 'tapemeasure', 'point1');
        p1 = board.create('point', pos0,  attr);

        // end point
        attr = Type.copyAttributes(attributes, board.options, 'tapemeasure', 'point2');
        p2 = board.create('point', pos1,  attr);

        p1.setAttribute({ignoredSnapToPoints: [p2]});
        p2.setAttribute({ignoredSnapToPoints: [p1]});

        // tape measure line
        attr = Type.copyAttributes(attributes, board.options, 'tapemeasure');
        withTicks = attr.withticks;
        withText = attr.withlabel;
        digits = attr.digits;

        if (digits === 2 && attr.precision !== 2) {
            // Backward compatibility
            digits = attr.precision;
        }

        // Below, we will replace the label by the measurement function.
        if (withText) {
            attr.withlabel = true;
        }
        li = board.create('segment', [p1, p2], attr);
        // p1, p2 are already added to li.inherits

        if (withText) {
            if (attributes.name && attributes.name !== '') {
                n = attributes.name + ' = ';
            } else {
                n = '';
            }
            li.label.setText(function () {
                return n + Type.toFixed(p1.Dist(p2), digits);
            });
        }

        if (withTicks) {
            attr = Type.copyAttributes(attributes, board.options, 'tapemeasure', 'ticks');
            //ticks  = 2;
            ti = board.create('ticks', [li, 0.1], attr);
            li.inherits.push(ti);
        }

        // override the segments's remove method to ensure the removal of all elements
        /** @ignore */
        li.remove = function () {
            if (withTicks) {
                li.removeTicks(ti);
            }

            board.removeObject(p2);
            board.removeObject(p1);

            GeometryElement.prototype.remove.call(this);
        };

        /**
         * Returns the length of the tape measure.
         * @name Value
         * @memberOf Tapemeasure.prototype
         * @function
         * @returns {Number} length of tape measure.
         */
        li.Value = function () {
            return p1.Dist(p2);
        };

        p1.dump = false;
        p2.dump = false;

        li.elType = 'tapemeasure';
        li.getParents = function() {
            return [[p1.X(), p1.Y()], [p2.X(), p2.Y()]];
        };

        li.subs = {
            point1: p1,
            point2: p2
        };

        if (withTicks) {
            ti.dump = false;
        }

        li.methodMap = JXG.deepCopy(li.methodMap, {
            Value: 'Value'
        });

        li.prepareUpdate().update();
        if (!board.isSuspendedUpdate) {
            li.updateVisibility().updateRenderer();
            // The point updates are necessary in case of snapToGrid==true
            li.point1.updateVisibility().updateRenderer();
            li.point2.updateVisibility().updateRenderer();
        }

        return li;
    };

    JXG.registerElement('tapemeasure', JXG.createTapemeasure);

    return {
        createTapemeasure: JXG.createTapemeasure
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, document: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/type
 */

/**
 * @fileoverview The JXG.DataSource is a helper class for data organization. Currently supported data sources are
 * javascript arrays and HTML tables.
 */

define('parser/datasource',['jxg', 'utils/type'], function (JXG, Type) {

    "use strict";

    JXG.DataSource = function () {
        this.data = [];
        this.columnHeaders = [];
        this.rowHeaders = [];

        return this;
    };

    JXG.extend(JXG.DataSource.prototype, /** @lends JXG.DataSource.prototype */ {
        loadFromArray: function (table, columnHeader, rowHeader) {
            var i, j, cell;

            if (Type.isArray(columnHeader)) {
                this.columnHeaders = columnHeader;
                columnHeader = false;
            }

            if (Type.isArray(rowHeader)) {
                this.rowHeaders = rowHeader;
                rowHeader = false;
            }

            this.data = [];

            if (columnHeader) {
                this.columnHeaders = [];
            }

            if (rowHeader) {
                this.rowHeaders = [];
            }

            if (Type.exists(table)) {
                // extract the data
                this.data = [];

                for (i = 0; i < table.length; i++) {
                    this.data[i] = [];

                    for (j = 0; j < table[i].length; j++) {
                        cell = table[i][j];
                        if (parseFloat(cell).toString() === cell) {
                            this.data[i][j] = parseFloat(cell);
                        } else if (cell !== '-') {
                            this.data[i][j] = cell;
                        } else {
                            this.data[i][j] = NaN;
                        }
                    }
                }

                if (columnHeader) {
                    this.columnHeaders = this.data[0].slice(1);
                    this.data = this.data.slice(1);
                }

                if (rowHeader) {
                    this.rowHeaders = [];
                    for (i = 0; i < this.data.length; i++) {
                        this.rowHeaders.push(this.data[i][0]);
                        this.data[i] = this.data[i].slice(1);
                    }
                }
            }

            return this;
        },

        loadFromTable: function (table, columnHeader, rowHeader) {
            var row, i, j, col, cell, name;

            if (Type.isArray(columnHeader)) {
                this.columnHeaders = columnHeader;
                columnHeader = false;
            }

            if (Type.isArray(rowHeader)) {
                this.rowHeaders = rowHeader;
                rowHeader = false;
            }

            this.data = [];

            if (columnHeader) {
                this.columnHeaders = [];
            }

            if (rowHeader) {
                this.rowHeaders = [];
            }

            // to adjust: examples in examples folder & wiki
            table = document.getElementById(table);

            if (Type.exists(table)) {
                // extract the data
                row = table.getElementsByTagName('tr');
                this.data = [];

                for (i = 0; i < row.length; i++) {
                    col = row[i].getElementsByTagName('td');
                    this.data[i] = [];

                    for (j = 0; j < col.length; j++) {
                        cell = col[j].innerHTML;

                        if (parseFloat(cell).toString() === cell) {
                            this.data[i][j] = parseFloat(cell);
                        } else if (cell !== '-') {
                            this.data[i][j] = cell;
                        } else {
                            this.data[i][j] = NaN;
                        }
                    }
                }

                if (columnHeader) {
                    this.columnHeaders = this.data[0].slice(1);
                    this.data = this.data.slice(1);
                }

                if (rowHeader) {
                    this.rowHeaders = [];
                    for (i = 0; i < this.data.length; i++) {
                        this.rowHeaders.push(this.data[i][0]);
                        this.data[i] = this.data[i].slice(1);
                    }
                }
            }

            return this;
        },

        addColumn: function (name, pos, data) {
            throw new Error('not implemented');
        },

        addRow: function (name, pos, data) {
            throw new Error('not implemented');
        },

        getColumn: function (col) {
            var i,
                result = [];

            // get column index if column is given as column header title
            if (Type.isString(col)) {
                for (i = 0; i < this.columnHeaders.length; i++) {
                    if (col === this.columnHeaders[i]) {
                        col = i;
                        break;
                    }
                }
            }

            // build column array
            for (i = 0; i < this.data.length; i++) {
                if (this.data[i].length > col) {
                    result[i] = parseFloat(this.data[i][col]);
                }
            }

            return result;
        },

        getRow: function (row) {
            var result, i;

            // get column index if column is given as column header title
            if (Type.isString(row)) {
                for (i = 0; i < this.rowHeaders.length; i++) {
                    if (row === this.rowHeaders[i]) {
                        row = i;
                        break;
                    }
                }
            }

            // allocate memory for result array
            result = [];

            // build column array. result = this.data[row] is a flat copy and will
            // destroy our local data copy, that's why we're copying it element wise.
            for (i = 0; i < this.data[row].length; i++) {
                result[i] = this.data[row][i];
            }

            return result;
        }
    });

    return JXG.DataSource;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, document: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/numerics
 math/statistics
 base/constants
 base/coords
 base/element
 parser/datasource
 utils/color
 utils/type
 utils/env
  elements:
   curve
   spline
   functiongraph
   point
   text
   polygon
   sector
   transform
   line
   legend
   circle
 */

define('base/chart',[
    'jxg', 'math/numerics', 'math/statistics', 'base/constants', 'base/coords', 'base/element', 'parser/datasource',
    'utils/color', 'utils/type', 'utils/env', 'base/curve', 'base/point', 'base/text', 'base/polygon', 'element/sector',
    'base/transformation', 'base/line', 'base/circle'
], function (JXG, Numerics, Statistics, Const, Coords, GeometryElement, DataSource, Color, Type, Env, Curve, Point, Text,
        Polygon, Sector, Transform, Line, Circle) {

    "use strict";

     /**
      *
      * The Chart class is a basic class for the chart object.
      * @class Creates a new basic chart object. Do not use this constructor to create a chart.
      * Use {@link JXG.Board#create} with type {@link Chart} instead.
      * @constructor
      * @augments JXG.GeometryElement
      * @param {String,JXG.Board} board The board the new chart is drawn on.
      * @param {Array} parent data arrays for the chart
      * @param {Object} attributes Javascript object containing attributes like name, id and colors.
      *
      */
    JXG.Chart = function (board, parents, attributes) {
        this.constructor(board, attributes);

        var x, y, i, c, style, len;

        if (!Type.isArray(parents) || parents.length === 0) {
            throw new Error('JSXGraph: Can\'t create a chart without data');
        }

        /**
         * Contains pointers to the various subelements of the chart.
         */
        this.elements = [];

        if (Type.isNumber(parents[0])) {
            // parents looks like [a,b,c,..]
            // x has to be filled

            y = parents;
            x = [];
            for (i = 0; i < y.length; i++) {
                x[i] = i + 1;
            }
        } else if (parents.length === 1 && Type.isArray(parents[0])) {
            // parents looks like [[a,b,c,..]]
            // x has to be filled

            y = parents[0];
            x = [];

            len = Type.evaluate(y).length;
            for (i = 0; i < len; i++) {
                x[i] = i + 1;
            }
        } else if (parents.length === 2) {
            // parents looks like [[x0,x1,x2,...],[y1,y2,y3,...]]
            len = Math.min(parents[0].length, parents[1].length);
            x = parents[0].slice(0, len);
            y = parents[1].slice(0, len);
        }

        if (Type.isArray(y) && y.length === 0) {
            throw new Error('JSXGraph: Can\'t create charts without data.');
        }

        // does this really need to be done here? this should be done in createChart and then
        // there should be an extra chart for each chartstyle
        style = attributes.chartstyle.replace(/ /g, '').split(',');
        for (i = 0; i < style.length; i++) {
            switch (style[i]) {
            case 'bar':
                c = this.drawBar(board, x, y, attributes);
                break;
            case 'line':
                c = this.drawLine(board, x, y, attributes);
                break;
            case 'fit':
                c = this.drawFit(board, x, y, attributes);
                break;
            case 'spline':
                c = this.drawSpline(board, x, y, attributes);
                break;
            case 'pie':
                c = this.drawPie(board, y, attributes);
                break;
            case 'point':
                c = this.drawPoints(board, x, y, attributes);
                break;
            case 'radar':
                c = this.drawRadar(board, parents, attributes);
                break;
            }
            this.elements.push(c);
        }
        this.id = this.board.setId(this, 'Chart');

        return this.elements;
    };

    JXG.Chart.prototype = new GeometryElement();

    JXG.extend(JXG.Chart.prototype, /** @lends JXG.Chart.prototype */ {
        /**
         * Create line chart defined by two data arrays.
         *
         * @param  {String,JXG.Board} board      The board the chart is drawn on
         * @param  {Array} x          Array of x-coordinates
         * @param  {Array} y          Array of y-coordinates
         * @param  {Object} attributes  Javascript object containing attributes like colors
         * @returns {JXG.Curve}       JSXGraph curve
         */
        drawLine: function (board, x, y, attributes) {
            // we don't want the line chart to be filled
            attributes.fillcolor = 'none';
            attributes.highlightfillcolor = 'none';

            return board.create('curve', [x, y], attributes);
        },

        /**
         * Create line chart that consists of a natural spline curve
         * defined by two data arrays.
         *
         * @param  {String,JXG.Board} board      The board the chart is drawn on
         * @param  {Array} x          Array of x-coordinates
         * @param  {Array} y          Array of y-coordinates
         * @param  {Object} attributes Javascript object containing attributes like colors
         * @returns {JXG.Curve}       JSXGraph (natural) spline curve
         */
        drawSpline: function (board, x, y, attributes) {
            // we don't want the spline chart to be filled
            attributes.fillColor = 'none';
            attributes.highlightfillcolor = 'none';

            return board.create('spline', [x, y], attributes);
        },

        /**
         * Create line chart where the curve is given by a regression polynomial
         * defined by two data arrays. The degree of the polynomial is supplied
         * through the attribute "degree" in attributes.
         *
         * @param  {String,JXG.Board} board      The board the chart is drawn on
         * @param  {Array} x          Array of x-coordinates
         * @param  {Array} y          Array of y-coordinates
         * @param  {Object} attributes Javascript object containing attributes like colors
         * @returns {JXG.Curve}    JSXGraph function graph object
         */
        drawFit: function (board, x, y, attributes) {
            var deg = attributes.degree;

            deg = Math.max(parseInt(deg, 10), 1) || 1;

            // never fill
            attributes.fillcolor = 'none';
            attributes.highlightfillcolor = 'none';

            return board.create('functiongraph', [Numerics.regressionPolynomial(deg, x, y)], attributes);
        },

        /**
         * Create bar chart defined by two data arrays.
         * Attributes to change the layout of the bar chart are:
         * <ul>
         * <li> width (optional)
         * <li> dir: 'horizontal' or 'vertical'
         * <li> colors: array of colors
         * <li> labels: array of labels
         * </ul>
         *
         * @param  {String,JXG.Board} board      The board the chart is drawn on
         * @param  {Array} x          Array of x-coordinates
         * @param  {Array} y          Array of y-coordinates
         * @param  {Object} attributes Javascript object containing attributes like colors
         * @returns {Array}    Array of JXG polygons defining the bars
         */
        drawBar: function (board, x, y, attributes) {
            var i, strwidth, text, w, xp0, xp1, xp2, yp, colors,
                pols = [],
                p = [],
                attr, attrSub,
                makeXpFun = function (i, f) {
                    return function () {
                        return x[i]() - f * w;
                    };
                },
                hiddenPoint = {
                    fixed: true,
                    withLabel: false,
                    visible: false,
                    name: ''
                };

            attr = Type.copyAttributes(attributes, board.options, 'chart');

            // Determine the width of the bars
            if (attr && attr.width) {  // width given
                w = attr.width;
            } else {
                if (x.length <= 1) {
                    w = 1;
                } else {
                    // Find minimum distance between to bars.
                    w = x[1] - x[0];
                    for (i = 1; i < x.length - 1; i++) {
                        w = (x[i + 1] - x[i] < w) ? x[i + 1] - x[i] : w;
                    }
                }
                w *= 0.8;
            }

            attrSub = Type.copyAttributes(attributes, board.options, 'chart', 'label');

            for (i = 0; i < x.length; i++) {
                if (Type.isFunction(x[i])) {
                    xp0 = makeXpFun(i, -0.5);
                    xp1 = makeXpFun(i, 0);
                    xp2 = makeXpFun(i, 0.5);
                } else {
                    xp0 = x[i] - w * 0.5;
                    xp1 = x[i];
                    xp2 = x[i] + w * 0.5;
                }
                if (Type.isFunction(y[i])) {
                    yp = y[i]();
                } else {
                    yp = y[i];
                }
                yp = y[i];

                if (attr.dir === 'horizontal') {  // horizontal bars
                    p[0] = board.create('point', [0, xp0], hiddenPoint);
                    p[1] = board.create('point', [yp, xp0], hiddenPoint);
                    p[2] = board.create('point', [yp, xp2], hiddenPoint);
                    p[3] = board.create('point', [0, xp2], hiddenPoint);

                    if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
                        attrSub.anchorY = 'middle';
                        text = board.create('text', [
                            yp,
                            xp1,
                            attr.labels[i]], attrSub);
                        text.visProp.anchorx = (function(txt) { return function() {
                            return (txt.X() >= 0) ? 'left' : 'right';
                        }; })(text);

                    }
                } else { // vertical bars
                    p[0] = board.create('point', [xp0, 0], hiddenPoint);
                    p[1] = board.create('point', [xp0, yp], hiddenPoint);
                    p[2] = board.create('point', [xp2, yp], hiddenPoint);
                    p[3] = board.create('point', [xp2, 0], hiddenPoint);

                    if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
                        attrSub.anchorX = 'middle';

                        text = board.create('text', [
                            xp1,
                            yp,
                            attr.labels[i]], attrSub);

                        text.visProp.anchory = (function(txt) {
                            return function() {
                                    return (txt.Y() >= 0) ? 'bottom' : 'top';
                                };
                            })(text);

                    }
                }

                if (Type.isArray(attr.colors)) {
                    colors = attr.colors;
                    attr.fillcolor = colors[i % colors.length];
                }

                pols[i] = board.create('polygon', p, attr);
                if (Type.exists(attr.labels) && Type.exists(attr.labels[i])) {
                    pols[i].text = text;
                }
            }

            return pols;
        },

        /**
         * Create chart consisting of JSXGraph points.
         * Attributes to change the layout of the point chart are:
         * <ul>
         * <li> fixed (Boolean)
         * <li> infoboxArray (Array): Texts for the infobox
         * </ul>
         *
         * @param  {String,JXG.Board} board      The board the chart is drawn on
         * @param  {Array} x          Array of x-coordinates
         * @param  {Array} y          Array of y-coordinates
         * @param  {Object} attributes Javascript object containing attributes like colors
         * @returns {Array} Array of JSXGraph points
         */
        drawPoints: function (board, x, y, attributes) {
            var i,
                points = [],
                infoboxArray = attributes.infoboxarray;

            attributes.fixed = true;
            attributes.name = '';

            for (i = 0; i < x.length; i++) {
                attributes.infoboxtext = infoboxArray ? infoboxArray[i % infoboxArray.length] : false;
                points[i] = board.create('point', [x[i], y[i]], attributes);
            }

            return points;
        },

        /**
         * Create pie chart.
         * Attributes to change the layout of the pie chart are:
         * <ul>
         * <li> labels: array of labels
         * <li> colors: (Array)
         * <li> highlightColors (Array)
         * <li> radius
         * <li> center (coordinate array)
         * <li> highlightOnSector (Boolean)
         * </ul>
         *
         * @param  {String,JXG.Board} board      The board the chart is drawn on
         * @param  {Array} y          Array of x-coordinates
         * @param  {Object} attributes Javascript object containing attributes like colors
         * @returns {Object}  with keys: "{sectors, points, midpoint}"
         */
        drawPie: function (board, y, attributes) {
            var i, center,
                p = [],
                sector = [],
                s = Statistics.sum(y),
                colorArray = attributes.colors,
                highlightColorArray = attributes.highlightcolors,
                labelArray = attributes.labels,
                r = attributes.radius || 4,
                radius = r,
                cent = attributes.center || [0, 0],
                xc = cent[0],
                yc = cent[1],

                makeRadPointFun = function (j, fun, xc) {
                    return function () {
                        var s, i, rad,
                            t = 0;

                        for (i = 0; i <= j; i++) {
                            t += parseFloat(Type.evaluate(y[i]));
                        }

                        s = t;
                        for (i = j + 1; i < y.length; i++) {
                            s += parseFloat(Type.evaluate(y[i]));
                        }
                        rad = (s !== 0) ? (2 * Math.PI * t / s) : 0;

                        return radius() * Math[fun](rad) + xc;
                    };
                },

                highlightHandleLabel = function (f, s) {
                    var dx = -this.point1.coords.usrCoords[1] + this.point2.coords.usrCoords[1],
                        dy = -this.point1.coords.usrCoords[2] + this.point2.coords.usrCoords[2];

                    if (Type.exists(this.label)) {
                        this.label.rendNode.style.fontSize = (s * Type.evaluate(this.label.visProp.fontsize)) + 'px';
                        this.label.fullUpdate();
                    }

                    this.point2.coords = new Coords(Const.COORDS_BY_USER, [
                        this.point1.coords.usrCoords[1] + dx * f,
                        this.point1.coords.usrCoords[2] + dy * f
                    ], this.board);
                    this.fullUpdate();
                },

                highlightFun = function () {
                    if (!this.highlighted) {
                        this.highlighted = true;
                        this.board.highlightedObjects[this.id] = this;
                        this.board.renderer.highlight(this);

                        highlightHandleLabel.call(this, 1.1, 2);
                    }
                },

                noHighlightFun = function () {
                    if (this.highlighted) {
                        this.highlighted = false;
                        this.board.renderer.noHighlight(this);

                        highlightHandleLabel.call(this, 0.90909090, 1);
                    }
                },

                hiddenPoint = {
                    fixed: true,
                    withLabel: false,
                    visible: false,
                    name: ''
                };

            if (!Type.isArray(labelArray)) {
                labelArray = [];
                for (i = 0; i < y.length; i++) {
                    labelArray[i] = '';
                }
            }

            if (!Type.isFunction(r)) {
                radius = function () {
                    return r;
                };
            }

            attributes.highlightonsector = attributes.highlightonsector || false;
            attributes.straightfirst = false;
            attributes.straightlast = false;

            center = board.create('point', [xc, yc], hiddenPoint);
            p[0] = board.create('point', [
                function () {
                    return radius() + xc;
                },
                function () {
                    return yc;
                }
            ], hiddenPoint);

            for (i = 0; i < y.length; i++) {
                p[i + 1] = board.create('point', [makeRadPointFun(i, 'cos', xc), makeRadPointFun(i, 'sin', yc)], hiddenPoint);

                attributes.name = labelArray[i];
                attributes.withlabel = attributes.name !== '';
                attributes.fillcolor = colorArray && colorArray[i % colorArray.length];
                attributes.labelcolor = colorArray && colorArray[i % colorArray.length];
                attributes.highlightfillcolor = highlightColorArray && highlightColorArray[i % highlightColorArray.length];

                sector[i] = board.create('sector', [center, p[i], p[i + 1]], attributes);

                if (attributes.highlightonsector) {
                    // overwrite hasPoint so that the whole sector is used for highlighting
                    sector[i].hasPoint = sector[i].hasPointSector;
                }
                if (attributes.highlightbysize) {
                    sector[i].highlight = highlightFun;

                    sector[i].noHighlight = noHighlightFun;
                }

            }

            // Not enough! We need points, but this gives an error in setAttribute.
            return {sectors: sector, points: p, midpoint: center};
        },

        /**
         * Create radar chart.
         * Attributes to change the layout of the pie chart are:
         * <ul>
         * <li> paramArray: labels for axes, [ paramx, paramy, paramz ]
         * <li> startShiftRatio: 0 <= offset from chart center <=1
         * <li> endShiftRatio:  0 <= offset from chart radius <=1
         * <li> startShiftArray: Adjust offsets per each axis
         * <li> endShiftArray: Adjust offsets per each axis
         * <li> startArray: Values for inner circle. Default values: minimums
         * <li> start: one value to overwrite all startArray values
         * <li> endArray: Values for outer circle, maximums by default
         * <li> end: one value to overwrite all endArray values
         * <li> labelArray
         * <li> polyStrokeWidth
         * <li> colors
         * <li> highlightcolors
         * <li> labelArray: [ row1, row2, row3 ]
         * <li> radius
         * <li> legendPosition
         * <li> showCircles
         * <li> circleLabelArray
         * <li> circleStrokeWidth
         * </ul>
         *
         * @param  {String,JXG.Board} board      The board the chart is drawn on
         * @param  {Array} parents    Array of coordinates, e.g. [[x1, y1, z1], [x2, y2, z2], [x3, y3, z3]]
         * @param  {Object} attributes Javascript object containing attributes like colors
         * @returns {Object} with keys "{circles, lines, points, midpoint, polygons}"
         */
        drawRadar: function (board, parents, attributes) {
            var i, j, paramArray, numofparams, maxes, mins,
                la, pdata, ssa, esa, ssratio, esratio,
                sshifts, eshifts, starts, ends,
                labelArray, colorArray, highlightColorArray, radius, myAtts,
                cent, xc, yc, center, start_angle, rad, p, line, t,
                xcoord, ycoord, polygons, legend_position, circles, lxoff, lyoff,
                cla, clabelArray, ncircles, pcircles, angle, dr, sw, data,
                len = parents.length,

                get_anchor = function () {
                    var x1, x2, y1, y2,
                        relCoords = Type.evaluate(this.visProp.label.offset).slice(0);

                    x1 = this.point1.X();
                    x2 = this.point2.X();
                    y1 = this.point1.Y();
                    y2 = this.point2.Y();
                    if (x2 < x1) {
                        relCoords[0] = -relCoords[0];
                    }

                    if (y2 < y1) {
                        relCoords[1] = -relCoords[1];
                    }

                    this.setLabelRelativeCoords(relCoords);

                    return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board);
                },

                get_transform = function (angle, i) {
                    var t, tscale, trot;

                    t = board.create('transform', [-(starts[i] - sshifts[i]), 0], {type: 'translate'});
                    tscale = board.create('transform', [radius / ((ends[i] + eshifts[i]) - (starts[i] - sshifts[i])), 1], {type: 'scale'});
                    t.melt(tscale);
                    trot = board.create('transform', [angle], {type: 'rotate'});
                    t.melt(trot);

                    return t;
                };

            if (len <= 0) {
                throw new Error('JSXGraph radar chart: no data');
            }
            // labels for axes
            paramArray = attributes.paramarray;
            if (!Type.exists(paramArray)) {
                throw new Error('JSXGraph radar chart: need paramArray attribute');
            }
            numofparams = paramArray.length;
            if (numofparams <= 1) {
                throw new Error('JSXGraph radar chart: need more than one param in paramArray');
            }

            for (i = 0; i < len; i++) {
                if (numofparams !== parents[i].length) {
                    throw new Error('JSXGraph radar chart: use data length equal to number of params (' + parents[i].length + ' != ' + numofparams + ')');
                }
            }

            maxes = [];
            mins = [];

            for (j = 0; j < numofparams; j++) {
                maxes[j] = parents[0][j];
                mins[j] = maxes[j];
            }

            for (i = 1; i < len; i++) {
                for (j = 0; j < numofparams; j++) {
                    if (parents[i][j] > maxes[j]) {
                        maxes[j] = parents[i][j];
                    }

                    if (parents[i][j] < mins[j]) {
                        mins[j] = parents[i][j];
                    }
                }
            }

            la = [];
            pdata = [];

            for (i = 0; i < len; i++) {
                la[i] = '';
                pdata[i] = [];
            }

            ssa = [];
            esa = [];

            // 0 <= Offset from chart center <=1
            ssratio = attributes.startshiftratio || 0;
            // 0 <= Offset from chart radius <=1
            esratio = attributes.endshiftratio || 0;

            for (i = 0; i < numofparams; i++) {
                ssa[i] = (maxes[i] - mins[i]) * ssratio;
                esa[i] = (maxes[i] - mins[i]) * esratio;
            }

            // Adjust offsets per each axis
            sshifts = attributes.startshiftarray || ssa;
            eshifts = attributes.endshiftarray || esa;
            // Values for inner circle, minimums by default
            starts = attributes.startarray || mins;

            if (Type.exists(attributes.start)) {
                for (i = 0; i < numofparams; i++) {
                    starts[i] = attributes.start;
                }
            }

            // Values for outer circle, maximums by default
            ends = attributes.endarray || maxes;
            if (Type.exists(attributes.end)) {
                for (i = 0; i < numofparams; i++) {
                    ends[i] = attributes.end;
                }
            }

            if (sshifts.length !== numofparams) {
                throw new Error('JSXGraph radar chart: start shifts length is not equal to number of parameters');
            }

            if (eshifts.length !== numofparams) {
                throw new Error('JSXGraph radar chart: end shifts length is not equal to number of parameters');
            }

            if (starts.length !== numofparams) {
                throw new Error('JSXGraph radar chart: starts length is not equal to number of parameters');
            }

            if (ends.length !== numofparams) {
                throw new Error('JSXGraph radar chart: snds length is not equal to number of parameters');
            }

            // labels for legend
            labelArray = attributes.labelarray || la;
            colorArray = attributes.colors;
            highlightColorArray = attributes.highlightcolors;
            radius = attributes.radius || 10;
            sw = attributes.strokewidth || 1;

            if (!Type.exists(attributes.highlightonsector)) {
                attributes.highlightonsector = false;
            }

            myAtts = {
                name: attributes.name,
                id: attributes.id,
                strokewidth: sw,
                polystrokewidth: attributes.polystrokewidth || sw,
                strokecolor: attributes.strokecolor || 'black',
                straightfirst: false,
                straightlast: false,
                fillcolor: attributes.fillColor || '#FFFF88',
                fillopacity: attributes.fillOpacity || 0.4,
                highlightfillcolor: attributes.highlightFillColor || '#FF7400',
                highlightstrokecolor: attributes.highlightStrokeColor || 'black',
                gradient: attributes.gradient || 'none'
            };

            cent = attributes.center || [0, 0];
            xc = cent[0];
            yc = cent[1];
            center = board.create('point', [xc, yc], {name: '', fixed: true, withlabel: false, visible: false});
            start_angle = Math.PI / 2 - Math.PI / numofparams;
            start_angle = attributes.startangle || 0;
            rad = start_angle;
            p = [];
            line = [];

            for (i = 0; i < numofparams; i++) {
                rad += 2 * Math.PI / numofparams;
                xcoord = radius * Math.cos(rad) + xc;
                ycoord = radius * Math.sin(rad) + yc;

                p[i] = board.create('point', [xcoord, ycoord], {name: '', fixed: true, withlabel: false, visible: false});
                line[i] = board.create('line', [center, p[i]], {
                    name: paramArray[i],
                    strokeColor: myAtts.strokecolor,
                    strokeWidth: myAtts.strokewidth,
                    strokeOpacity: 1.0,
                    straightFirst: false,
                    straightLast: false,
                    withLabel: true,
                    highlightStrokeColor: myAtts.highlightstrokecolor
                });
                line[i].getLabelAnchor = get_anchor;
                t = get_transform(rad, i);

                for (j = 0; j < parents.length; j++) {
                    data = parents[j][i];
                    pdata[j][i] = board.create('point', [data, 0], {name: '', fixed: true, withlabel: false, visible: false});
                    pdata[j][i].addTransform(pdata[j][i], t);
                }
            }

            polygons = [];
            for (i = 0; i < len; i++) {
                myAtts.labelcolor = colorArray && colorArray[i % colorArray.length];
                myAtts.strokecolor = colorArray && colorArray[i % colorArray.length];
                myAtts.fillcolor = colorArray && colorArray[i % colorArray.length];
                polygons[i] = board.create('polygon', pdata[i], {
                    withLines: true,
                    withLabel: false,
                    fillColor: myAtts.fillcolor,
                    fillOpacity: myAtts.fillopacity,
                    highlightFillColor: myAtts.highlightfillcolor
                });

                for (j = 0; j < numofparams; j++) {
                    polygons[i].borders[j].setAttribute('strokecolor:' + colorArray[i % colorArray.length]);
                    polygons[i].borders[j].setAttribute('strokewidth:' + myAtts.polystrokewidth);
                }
            }

            legend_position = attributes.legendposition || 'none';
            switch (legend_position) {
            case 'right':
                lxoff = attributes.legendleftoffset || 2;
                lyoff = attributes.legendtopoffset || 1;

                this.legend = board.create('legend', [xc + radius + lxoff, yc + radius - lyoff], {
                    labels: labelArray,
                    colors: colorArray
                });
                break;
            case 'none':
                break;
            default:
                JXG.debug('Unknown legend position');
            }

            circles = [];
            if (attributes.showcircles) {
                cla = [];
                for (i = 0; i < 6; i++) {
                    cla[i] = 20 * i;
                }
                cla[0] = "0";
                clabelArray = attributes.circlelabelarray || cla;
                ncircles = clabelArray.length;

                if (ncircles < 2) {
                    throw new Error('JSXGraph radar chart: too less circles in circleLabelArray');
                }

                pcircles = [];
                angle = start_angle + Math.PI / numofparams;
                t = get_transform(angle, 0);

                myAtts.fillcolor = 'none';
                myAtts.highlightfillcolor = 'none';
                myAtts.strokecolor = attributes.strokecolor || 'black';
                myAtts.strokewidth = attributes.circlestrokewidth || 0.5;
                myAtts.layer = 0;

                // we have ncircles-1 intervals between ncircles circles
                dr = (ends[0] - starts[0]) / (ncircles - 1);

                for (i = 0; i < ncircles; i++) {
                    pcircles[i] = board.create('point', [starts[0] + i * dr, 0], {
                        name: clabelArray[i],
                        size: 0,
                        fixed: true,
                        withLabel: true,
                        visible: true
                    });
                    pcircles[i].addTransform(pcircles[i], t);
                    circles[i] = board.create('circle', [center, pcircles[i]], myAtts);
                }

            }
            this.rendNode = polygons[0].rendNode;
            return {
                circles: circles,
                lines: line,
                points: pdata,
                midpoint: center,
                polygons: polygons
            };
        },

         /**
          * Uses the boards renderer to update the chart.
          * @private
          */
        updateRenderer: function () {
            return this;
        },

         // documented in base/element
        update: function () {
            if (this.needsUpdate) {
                this.updateDataArray();
            }

            return this;
        },

        /**
         * Template for dynamic charts update.
         * This method is used to compute new entries
         * for the arrays this.dataX and
         * this.dataY. It is used in update.
         * Default is an empty method, can be overwritten
         * by the user.
         *
         * @returns {JXG.Chart} Reference to this chart object.
         */
        updateDataArray: function () { return this; }
    });

    /**
     * @class Constructor for a chart.
     * @pseudo
     * @description
     * @name Chart
     * @augments JXG.Chart
     * @constructor
     * @type JXG.Chart
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {Array} x Array of x-coordinates (default case, see below for alternatives)
     * @param {Array} y Array of y-coordinates (default case, see below for alternatives)
     * <p>
     * The parent array may be of one of the following forms:
     * <ol>
     * <li> Parents array looks like [number, number, number, ...]. It is interpreted as array of y-coordinates.
     * The x coordinates are automatically set to [1, 2, ...]
     * <li> Parents array looks like [[number, number, number, ...]]. The content is interpreted as array of y-coordinates.
     * The x coordinates are automatically set to [1, 2, ...]x coordinates are automatically set to [1, 2, ...]
     * Default case: [[x0,x1,x2,...],[y1,y2,y3,...]]
     * </ol>
     *
     * The attribute value for the key 'chartStyle' determines the type(s) of the chart. 'chartStyle' is a comma
     * separated list of strings of the possible chart types
     * 'bar', 'fit', 'line',  'pie', 'point', 'radar', 'spline'.
     *
     * @see JXG.Chart#drawBar
     * @see JXG.Chart#drawFit
     * @see JXG.Chart#drawLine
     * @see JXG.Chart#drawPie
     * @see JXG.Chart#drawPoints
     * @see JXG.Chart#drawRadar
     * @see JXG.Chart#drawSpline
     *
     * @example
     *   board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox:[-0.5,8,9,-2],axis:true});
     *
     *   var f = [4, 2, -1, 3, 6, 7, 2];
     *   var chart = board.create('chart', f,
     *                 {chartStyle:'bar',
     *                  width:0.8,
     *                  labels:f,
     *                  colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
     *                              '#F1B112','#FCF302','#C1E212'],
     *                  label: {fontSize:30, display:'internal', anchorX:'left', rotate:90}
     *             });
     *
     * </pre><div id="JXG1528c395-9fa4-4210-ada6-7fc5652ed920" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG1528c395-9fa4-4210-ada6-7fc5652ed920',
     *             {boundingbox: [-0.5,8,9,-2], axis: true, showcopyright: false, shownavigation: false});
     *                 var f = [4,2,-1,3,6,7,2];
     *                 var chart = board.create('chart', f,
     *                     {chartStyle:'bar',
     *                      width:0.8,
     *                      labels:f,
     *                      colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
     *                                  '#F1B112','#FCF302','#C1E212'],
     *                      label: {fontSize:30, display:'internal', anchorX:'left', rotate:90}
     *                 });
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     *   board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-1, 9, 13, -3], axis:true});
     *
     *   var s = board.create('slider', [[4,7],[8,7],[1,1,1.5]], {name:'S', strokeColor:'black', fillColor:'white'});
     *   var f = [function(){return (s.Value()*4.5).toFixed(2);},
     *                      function(){return (s.Value()*(-1)).toFixed(2);},
     *                      function(){return (s.Value()*3).toFixed(2);},
     *                      function(){return (s.Value()*2).toFixed(2);},
     *                      function(){return (s.Value()*(-0.5)).toFixed(2);},
     *                      function(){return (s.Value()*5.5).toFixed(2);},
     *                      function(){return (s.Value()*2.5).toFixed(2);},
     *                      function(){return (s.Value()*(-0.75)).toFixed(2);},
     *                      function(){return (s.Value()*3.5).toFixed(2);},
     *                      function(){return (s.Value()*2).toFixed(2);},
     *                      function(){return (s.Value()*(-1.25)).toFixed(2);}
     *                      ];
     *   var chart = board.create('chart', [f],
     *                                             {chartStyle:'bar',width:0.8,labels:f,
     *                                              colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
     *                                                          '#F1B112','#FCF302','#C1E212']});
     *
     *   var dataArr = [4,1,3,2,5,6.5,1.5,2,0.5,1.5,-1];
     *   var chart2 = board.create('chart', dataArr, {chartStyle:'line,point'});
     *   chart2[0].setAttribute('strokeColor:black','strokeWidth:2pt');
     *   for(var i=0; i<11;i++) {
     *            chart2[1][i].setAttribute({strokeColor:'black',fillColor:'white',face:'[]', size:4, strokeWidth:'2pt'});
     *   }
     *   board.unsuspendUpdate();
     *
     * </pre><div id="JXG22deb158-48c6-41c3-8157-b88b4b968a55" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG22deb158-48c6-41c3-8157-b88b4b968a55',
     *             {boundingbox: [-1, 9, 13, -3], axis: true, showcopyright: false, shownavigation: false});
     *                 var s = board.create('slider', [[4,7],[8,7],[1,1,1.5]], {name:'S', strokeColor:'black', fillColor:'white'});
     *                 var f = [function(){return (s.Value()*4.5).toFixed(2);},
     *                          function(){return (s.Value()*(-1)).toFixed(2);},
     *                          function(){return (s.Value()*3).toFixed(2);},
     *                          function(){return (s.Value()*2).toFixed(2);},
     *                          function(){return (s.Value()*(-0.5)).toFixed(2);},
     *                          function(){return (s.Value()*5.5).toFixed(2);},
     *                          function(){return (s.Value()*2.5).toFixed(2);},
     *                          function(){return (s.Value()*(-0.75)).toFixed(2);},
     *                          function(){return (s.Value()*3.5).toFixed(2);},
     *                          function(){return (s.Value()*2).toFixed(2);},
     *                          function(){return (s.Value()*(-1.25)).toFixed(2);}
     *                          ];
     *                 var chart = board.create('chart', [f],
     *                                                 {chartStyle:'bar',width:0.8,labels:f,
     *                                                  colorArray:['#8E1B77','#BE1679','#DC1765','#DA2130','#DB311B','#DF4917','#E36317','#E87F1A',
     *                                                              '#F1B112','#FCF302','#C1E212']});
     *
     *                 var dataArr = [4,1,3,2,5,6.5,1.5,2,0.5,1.5,-1];
     *                 var chart2 = board.create('chart', dataArr, {chartStyle:'line,point'});
     *                 chart2[0].setAttribute('strokeColor:black','strokeWidth:2pt');
     *                 for(var i=0; i<11;i++) {
     *                     chart2[1][i].setAttribute({strokeColor:'black',fillColor:'white',face:'[]', size:4, strokeWidth:'2pt'});
     *                 }
     *                 board.unsuspendUpdate();
     *
     *     })();
     *
     * </script><pre>
     *
      * @example
     *         var dataArr = [4, 1.2, 3, 7, 5, 4, 1.54, function () { return 2; }];
     *         var a = board.create('chart', dataArr, {
     *                 chartStyle:'pie', colors:['#B02B2C','#3F4C6B','#C79810','#D15600'],
     *                 fillOpacity:0.9,
     *                 center:[5,2],
     *                 strokeColor:'#ffffff',
     *                 strokeWidth:6,
     *                 highlightBySize:true,
     *                 highlightOnSector:true
     *             });
     *
     * </pre><div id="JXG1180b7dd-b048-436a-a5ad-87ffa82d5aff" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG1180b7dd-b048-436a-a5ad-87ffa82d5aff',
     *             {boundingbox: [0, 8, 12, -4], axis: true, showcopyright: false, shownavigation: false});
     *             var dataArr = [4, 1.2, 3, 7, 5, 4, 1.54, function () { return 2; }];
     *             var a = board.create('chart', dataArr, {
     *                     chartStyle:'pie', colors:['#B02B2C','#3F4C6B','#C79810','#D15600'],
     *                     fillOpacity:0.9,
     *                     center:[5,2],
     *                     strokeColor:'#ffffff',
     *                     strokeWidth:6,
     *                     highlightBySize:true,
     *                     highlightOnSector:true
     *                 });
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     *             board = JXG.JSXGraph.initBoard('jxgbox', {boundingbox: [-12, 12, 20, -12], axis: false});
     *             board.suspendUpdate();
     *             // See labelArray and paramArray
     *             var dataArr = [[23, 14, 15.0], [60, 8, 25.0], [0, 11.0, 25.0], [10, 15, 20.0]];
     *
     *             var a = board.create('chart', dataArr, {
     *                 chartStyle:'radar',
     *                 colorArray:['#0F408D','#6F1B75','#CA147A','#DA2228','#E8801B','#FCF302','#8DC922','#15993C','#87CCEE','#0092CE'],
     *                 //fillOpacity:0.5,
     *                 //strokeColor:'black',
     *                 //strokeWidth:1,
     *                 //polyStrokeWidth:1,
     *                 paramArray:['Speed','Flexibility', 'Costs'],
     *                 labelArray:['Ruby','JavaScript', 'PHP', 'Python'],
     *                 //startAngle:Math.PI/4,
     *                 legendPosition:'right',
     *                 //"startShiftRatio": 0.1,
     *                 //endShiftRatio:0.1,
     *                 //startShiftArray:[0,0,0],
     *                 //endShiftArray:[0.5,0.5,0.5],
     *                 start:0
     *                 //end:70,
     *                 //startArray:[0,0,0],
     *                 //endArray:[7,7,7],
     *                 //radius:3,
     *                 //showCircles:true,
     *                 //circleLabelArray:[1,2,3,4,5],
     *                 //highlightColorArray:['#E46F6A','#F9DF82','#F7FA7B','#B0D990','#69BF8E','#BDDDE4','#92C2DF','#637CB0','#AB91BC','#EB8EBF'],
     *             });
     *             board.unsuspendUpdate();
     *
     * </pre><div id="JXG985fbbe6-0488-4073-b73b-cb3ebaea488a" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG985fbbe6-0488-4073-b73b-cb3ebaea488a',
     *             {boundingbox: [-12, 12, 20, -12], axis: false, showcopyright: false, shownavigation: false});
     *                 board.suspendUpdate();
     *                 // See labelArray and paramArray
     *                 var dataArr = [[23, 14, 15.0], [60, 8, 25.0], [0, 11.0, 25.0], [10, 15, 20.0]];
     *
     *                 var a = board.create('chart', dataArr, {
     *                     chartStyle:'radar',
     *                     colorArray:['#0F408D','#6F1B75','#CA147A','#DA2228','#E8801B','#FCF302','#8DC922','#15993C','#87CCEE','#0092CE'],
     *                     //fillOpacity:0.5,
     *                     //strokeColor:'black',
     *                     //strokeWidth:1,
     *                     //polyStrokeWidth:1,
     *                     paramArray:['Speed','Flexibility', 'Costs'],
     *                     labelArray:['Ruby','JavaScript', 'PHP', 'Python'],
     *                     //startAngle:Math.PI/4,
     *                     legendPosition:'right',
     *                     //"startShiftRatio": 0.1,
     *                     //endShiftRatio:0.1,
     *                     //startShiftArray:[0,0,0],
     *                     //endShiftArray:[0.5,0.5,0.5],
     *                     start:0
     *                     //end:70,
     *                     //startArray:[0,0,0],
     *                     //endArray:[7,7,7],
     *                     //radius:3,
     *                     //showCircles:true,
     *                     //circleLabelArray:[1,2,3,4,5],
     *                     //highlightColorArray:['#E46F6A','#F9DF82','#F7FA7B','#B0D990','#69BF8E','#BDDDE4','#92C2DF','#637CB0','#AB91BC','#EB8EBF'],
     *                 });
     *                 board.unsuspendUpdate();
     *
     *     })();
     *
     * </script><pre>
     *
     * For more examples see
     * <ul>
     * <li><a href="https://jsxgraph.org/wiki/index.php/Charts_from_HTML_tables_-_tutorial">JSXgraph wiki: Charts from HTML tables - tutorial</a>
     * <li><a href="https://jsxgraph.org/wiki/index.php/Pie_chart">JSXgraph wiki: Pie chart</a>
     * <li><a href="https://jsxgraph.org/wiki/index.php/Different_chart_styles">JSXgraph wiki: Various chart styles</a>
     * <li><a href="https://jsxgraph.org/wiki/index.php/Dynamic_bar_chart">JSXgraph wiki: Dynamic bar chart</a>
     * </ul>
     */
     JXG.createChart = function (board, parents, attributes) {
         var data, row, i, j, col,
             charts = [],
             w, x, showRows, attr,
             originalWidth, name, strokeColor, fillColor,
             hStrokeColor, hFillColor, len,
             table = Env.isBrowser ? board.document.getElementById(parents[0]) : null;

        if ((parents.length === 1) && (Type.isString(parents[0]))) {
            if (Type.exists(table)) {
                // extract the data
                attr = Type.copyAttributes(attributes, board.options, 'chart');

                table = (new DataSource()).loadFromTable(parents[0], attr.withheaders, attr.withheaders);
                data = table.data;
                col = table.columnHeaders;
                row = table.rowHeaders;

                originalWidth = attr.width;
                name = attr.name;
                strokeColor = attr.strokecolor;
                fillColor = attr.fillcolor;
                hStrokeColor = attr.highlightstrokecolor;
                hFillColor = attr.highlightfillcolor;

                board.suspendUpdate();

                len = data.length;
                showRows = [];
                if (attr.rows && Type.isArray(attr.rows)) {
                    for (i = 0; i < len; i++) {
                        for (j = 0; j < attr.rows.length; j++) {
                            if ((attr.rows[j] === i) || (attr.withheaders && attr.rows[j] === row[i])) {
                                showRows.push(data[i]);
                                break;
                            }
                        }
                    }
                } else {
                    showRows = data;
                }

                len = showRows.length;

                for (i = 0; i < len; i++) {

                    x = [];
                    if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) {
                        if (originalWidth) {
                            w = originalWidth;
                        } else {
                            w = 0.8;
                        }

                        x.push(1 - w / 2 + (i + 0.5) * w / len);

                        for (j = 1; j < showRows[i].length; j++) {
                            x.push(x[j - 1] + 1);
                        }

                        attr.width = w / len;
                    }

                    if (name && name.length === len) {
                        attr.name = name[i];
                    } else if (attr.withheaders) {
                        attr.name = col[i];
                    }

                    if (strokeColor && strokeColor.length === len) {
                        attr.strokecolor = strokeColor[i];
                    } else {
                        attr.strokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
                    }

                    if (fillColor && fillColor.length === len) {
                        attr.fillcolor = fillColor[i];
                    } else {
                        attr.fillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
                    }

                    if (hStrokeColor && hStrokeColor.length === len) {
                        attr.highlightstrokecolor = hStrokeColor[i];
                    } else {
                        attr.highlightstrokecolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 1.0);
                    }

                    if (hFillColor && hFillColor.length === len) {
                        attr.highlightfillcolor = hFillColor[i];
                    } else {
                        attr.highlightfillcolor = Color.hsv2rgb(((i + 1) / len) * 360, 0.9, 0.6);
                    }

                    if (attr.chartstyle && attr.chartstyle.indexOf('bar') !== -1) {
                        charts.push(new JXG.Chart(board, [x, showRows[i]], attr));
                    } else {
                        charts.push(new JXG.Chart(board, [showRows[i]], attr));
                    }
                }

                board.unsuspendUpdate();

            }
            return charts;
        }

        attr = Type.copyAttributes(attributes, board.options, 'chart');
        return new JXG.Chart(board, parents, attr);
    };

    JXG.registerElement('chart', JXG.createChart);

    /**
     * Legend for chart
     * TODO
     *
     * The Legend class is a basic class for legends.
     * @class Creates a new Lgend object. Do not use this constructor to create a legend.
     * Use {@link JXG.Board#create} with type {@link Legend} instead.
     * <p>
     * The legend object consists of segements with labels. These lines can be
     * access with the property "lines" of the element.
     * @constructor
     * @augments JXG.GeometryElement
     * @param {String,JXG.Board} board The board the new legend is drawn on.
     * @param {Array} coords Coordinates of the left top point of the legend.
     * @param  {Object} attributes Attributes of the legend
     */
    JXG.Legend = function (board, coords, attributes) {
        var attr;

        /* Call the constructor of GeometryElement */
        this.constructor();

        attr = Type.copyAttributes(attributes, board.options, 'legend');

        this.board = board;
        this.coords = new Coords(Const.COORDS_BY_USER, coords, this.board);
        this.myAtts = {};
        this.label_array = attr.labelarray || attr.labels;
        this.color_array = attr.colorarray || attr.colors;
        this.lines = [];
        this.myAtts.strokewidth = attr.strokewidth || 5;
        this.myAtts.straightfirst = false;
        this.myAtts.straightlast = false;
        this.myAtts.withlabel = true;
        this.myAtts.fixed = true;
        this.style = attr.legendstyle || attr.style;

        if (this.style === 'vertical') {
            this.drawVerticalLegend(board, attr);
        } else {
            throw new Error('JSXGraph: Unknown legend style: ' + this.style);
        }
    };

    JXG.Legend.prototype = new GeometryElement();

    /**
     * Draw a vertical legend.
     *
     * @private
     * @param  {String,JXG.Board} board      The board the legend is drawn on
     * @param  {Object} attributes Attributes of the legend
     */
    JXG.Legend.prototype.drawVerticalLegend = function (board, attributes) {
        var i,
            line_length = attributes.linelength || 1,
            offy = (attributes.rowheight || 20) / this.board.unitY,

            getLabelAnchor = function () {
                this.setLabelRelativeCoords(this.visProp.label.offset);
                return new Coords(Const.COORDS_BY_USER, [this.point2.X(), this.point2.Y()], this.board);
            };

        for (i = 0; i < this.label_array.length; i++) {
            this.myAtts.name = this.label_array[i];
            this.myAtts.strokecolor = this.color_array[i % this.color_array.length];
            this.myAtts.highlightstrokecolor = this.color_array[i % this.color_array.length];
            this.myAtts.label = {
                offset: [10, 0],
                strokeColor: this.color_array[i % this.color_array.length ],
                strokeWidth: this.myAtts.strokewidth
            };

            this.lines[i] = board.create('line', [
                [this.coords.usrCoords[1], this.coords.usrCoords[2] - i * offy],
                [this.coords.usrCoords[1] + line_length, this.coords.usrCoords[2] - i * offy]],
                this.myAtts);

            this.lines[i].getLabelAnchor = getLabelAnchor;
            this.lines[i].prepareUpdate().update().updateVisibility(Type.evaluate(this.lines[i].visProp.visible)).updateRenderer();
        }
    };

    /**
     * @class This element is used to provide a constructor for a chart legend.
     * Parameter is a pair of coordinates. The label names and  the label colors are
     * supplied in the attributes:
     * <ul>
     * <li> labels (Array): array of strings containing label names
     * <li> labelArray (Array): alternative array for label names (has precedence over 'labels')
     * <li> colors (Array): array of color values
     * <li> colorArray (Array): alternative array for color values (has precedence over 'colors')
     * <li> legendStyle or style: at the time being only 'vertical' is supported.
     * <li> rowHeight.
     * </ul>
     *
     * @pseudo
     * @description
     * @name Legend
     * @augments JXG.Legend
     * @constructor
     * @type JXG.Legend
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {Number} x Horizontal coordinate of the left top point of the legend
     * @param {Number} y Vertical coordinate of the left top point of the legend
     *
     * @example
     * var board = JXG.JSXGraph.initBoard('jxgbox', {axis:true,boundingbox:[-4,48.3,12.0,-2.3]});
     * var x       = [-3,-2,-1,0,1,2,3,4,5,6,7,8];
     * var dataArr = [4,7,7,27,33,37,46,22,11,4,1,0];
     *
     * colors = ['green', 'yellow', 'red', 'blue'];
     * board.create('chart', [x,dataArr], {chartStyle:'bar', width:1.0, labels:dataArr, colors: colors} );
     * board.create('legend', [8, 45], {labels:dataArr, colors: colors, strokeWidth:5} );
     *
     * </pre><div id="JXGeeb588d9-a4fd-41bf-93f4-cd6f7a016682" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGeeb588d9-a4fd-41bf-93f4-cd6f7a016682',
     *             {boundingbox: [-4,48.3,12.0,-2.3], axis: true, showcopyright: false, shownavigation: false});
     *     var x       = [-3,-2,-1,0,1,2,3,4,5,6,7,8];
     *     var dataArr = [4,7,7,27,33,37,46,22,11,4,1,0];
     *
     *     colors = ['green', 'yellow', 'red', 'blue'];
     *     board.create('chart', [x,dataArr], {chartStyle:'bar', width:1.0, labels:dataArr, colors: colors} );
     *     board.create('legend', [8, 45], {labels:dataArr, colors: colors, strokeWidth:5} );
     *
     *     })();
     *
     * </script><pre>
     *
     *
     */
    JXG.createLegend = function (board, parents, attributes) {
        //parents are coords of left top point of the legend
        var start_from = [0, 0];

        if (Type.exists(parents) && parents.length === 2) {
                start_from = parents;
        } else {
            throw new Error('JSXGraph: Legend element needs two numbers as parameters');
        }

        return new JXG.Legend(board, start_from, attributes);
    };

    JXG.registerElement('legend', JXG.createLegend);

    return {
        Chart: JXG.Chart,
        Legend: JXG.Legend,
        createChart: JXG.createChart,
        createLegend: JXG.createLegend
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 base/element
 utils/type
  elements:
   curve
   point
   line
   transform
 */

/**
 * @fileoverview The JSXGraph object Turtle is defined. It acts like
 * "turtle graphics".
 * @author A.W.
 */

define('base/turtle',[
    'jxg', 'base/constants', 'base/element', 'utils/type'
], function (JXG, Const, GeometryElement, Type) {

    "use strict";

    /**
     * Constructs a new Turtle object.
     * @class This is the Turtle class.
     * It is derived from {@link JXG.GeometryElement}.
     * It stores all properties required
     * to move a turtle.
     * @constructor
     * @param {JXG.Board} board The board the new turtle is drawn on.
     * @param {Array} parents Start position and start direction of the turtle. Possible values are
     * [x, y, angle]
     * [[x, y], angle]
     * [x, y]
     * [[x, y]]
     * @param {Object} attributes Attributes to change the visual properties of the turtle object
     * All angles are in degrees.
     *
     * @example
     *
     * //creates a figure 8 animation
     * var board = JXG.JSXGraph.initBoard('jxgbox',{boundingbox: [-250, 250, 250, -250]});
     * var t = board.create('turtle',[0, 0], {strokeOpacity:0.5});
     * t.setPenSize(3);
     * t.right(90);
     * var alpha = 0;
     *
     * var run = function() {
     *  t.forward(2);
     *  if (Math.floor(alpha / 360) % 2 === 0) {
     *   t.left(1);        // turn left by 1 degree
     *  } else {
     *   t.right(1);       // turn right by 1 degree
     *  }
     *  alpha += 1;
     *
     *  if (alpha < 1440) {  // stop after two rounds
     *   setTimeout(run, 20);
     *  }
     * }
     *
     *run();
     *
     * </pre><div class="jxgbox" id="JXG14167b1c-2ad3-11e5-8dd9-901b0e1b8723" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var brd = JXG.JSXGraph.initBoard('JXG14167b1c-2ad3-11e5-8dd9-901b0e1b8723',
     *             {boundingbox: [-250, 250, 250, -250], axis: true, showcopyright: false, shownavigation: false});
     *               var t = brd.create('turtle',[0, 0], {strokeOpacity:0.5});
     *               t.setPenSize(3);
     *               t.right(90);
     *               var alpha = 0;
     *
     *              var run = function() {
     *              t.forward(2);
     *             if (Math.floor(alpha / 360) % 2 === 0) {
     *                t.left(1);        // turn left by 1 degree
     *              } else {
     *                   t.right(1);       // turn right by 1 degree
     *             }
     *             alpha += 1;
     *
     *             if (alpha < 1440) {  // stop after two rounds
     *                 setTimeout(run, 20);
     *               }
     *             }
     *
     *          run();
     *
     *     })();
     *
     * </script><pre>
     */
    JXG.Turtle = function (board, parents, attributes) {
        var x, y, dir;

        this.constructor(board, attributes, Const.OBJECT_TYPE_TURTLE, Const.OBJECT_CLASS_OTHER);

        this.turtleIsHidden = false;
        this.board = board;
        this.visProp.curveType = 'plot';

        // Save visProp in this._attributes.
        // this._attributes is overwritten by setPenSize, setPenColor...
        // Setting the color or size affects the turtle from the time of
        // calling the method,
        // whereas Turtle.setAttribute affects all turtle curves.
        this._attributes = Type.copyAttributes(this.visProp, board.options, 'turtle');
        delete this._attributes.id;

        x = 0;
        y = 0;
        dir = 90;

        if (parents.length !== 0) {
            // [x,y,dir]
            if (parents.length === 3) {
                // Only numbers are accepted at the moment
                x = parents[0];
                y = parents[1];
                dir = parents[2];
            } else if (parents.length === 2) {
                // [[x,y],dir]
                if (Type.isArray(parents[0])) {
                    x = parents[0][0];
                    y = parents[0][1];
                    dir = parents[1];
                // [x,y]
                } else {
                    x = parents[0];
                    y = parents[1];
                }
            // [[x,y]]
            } else {
                x = parents[0][0];
                y = parents[0][1];
            }
        }

        this.init(x, y, dir);

        this.methodMap = Type.deepCopy(this.methodMap, {
            forward: 'forward',
            fd: 'forward',
            back: 'back',
            bk: 'back',
            right: 'right',
            rt: 'right',
            left: 'left',
            lt: 'left',
            penUp: 'penUp',
            pu: 'penUp',
            penDown: 'penDown',
            pd: 'penDown',
            clearScreen: 'clearScreen',
            cs: 'clearScreen',
            clean: 'clean',
            setPos: 'setPos',
            home: 'home',
            hideTurtle: 'hideTurtle',
            ht: 'hideTurtle',
            showTurtle: 'showTurtle',
            st: 'showTurtle',
            penSize: 'setPenSize',
            penColor: 'setPenColor',
            pushTurtle: 'pushTurtle',
            push: 'pushTurtle',
            popTurtle: 'popTurtle',
            pop: 'popTurtle',
            lookTo: 'lookTo',
            pos: 'pos',
            moveTo: 'moveTo',
            X: 'X',
            Y: 'Y'
        });

        return this;
    };

    JXG.Turtle.prototype = new GeometryElement();

    JXG.extend(JXG.Turtle.prototype, /** @lends JXG.Turtle.prototype */ {
        /**
         * Initialize a new turtle or reinitialize a turtle after {@link JXG.Turtle#clearScreen}.
         * @private
         */
        init: function (x, y, dir) {
            var hiddenPointAttr = {
                    fixed: true,
                    name: '',
                    visible: false,
                    withLabel: false
                };

            this.arrowLen = 20 / Math.sqrt(this.board.unitX * this.board.unitX + this.board.unitY * this.board.unitY);

            this.pos = [x, y];
            this.isPenDown = true;
            this.dir = 90;
            this.stack = [];
            this.objects = [];
            this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
            this.objects.push(this.curve);

            this.turtle = this.board.create('point', this.pos, hiddenPointAttr);
            this.objects.push(this.turtle);

            this.turtle2 = this.board.create('point', [this.pos[0], this.pos[1] + this.arrowLen], hiddenPointAttr);
            this.objects.push(this.turtle2);

            this.visProp.arrow.lastArrow = true;
            this.visProp.arrow.straightFirst = false;
            this.visProp.arrow.straightLast = false;
            this.arrow = this.board.create('line', [this.turtle, this.turtle2], this.visProp.arrow);
            this.objects.push(this.arrow);

            this.subs = {
                arrow: this.arrow
            };
            this.inherits.push(this.arrow);

            this.right(90 - dir);
            this.board.update();
        },

        /**
         * Move the turtle forward.
         * @param {Number} len of forward move in user coordinates
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        forward: function (len) {
            if (len === 0) {
                return this;
            }

            var t,
                dx = len * Math.cos(this.dir * Math.PI / 180),
                dy = len * Math.sin(this.dir * Math.PI / 180);

            if (!this.turtleIsHidden) {
                t = this.board.create('transform', [dx, dy], {type: 'translate'});

                t.applyOnce(this.turtle);
                t.applyOnce(this.turtle2);
            }

            if (this.isPenDown) {
                // IE workaround
                if (this.curve.dataX.length >= 8192) {
                    this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
                    this.objects.push(this.curve);
                }
            }

            this.pos[0] += dx;
            this.pos[1] += dy;

            if (this.isPenDown) {
                this.curve.dataX.push(this.pos[0]);
                this.curve.dataY.push(this.pos[1]);
            }

            this.board.update();
            return this;
        },

        /**
         * Move the turtle backwards.
         * @param {Number} len of backwards move in user coordinates
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        back: function (len) {
            return this.forward(-len);
        },

        /**
         * Rotate the turtle direction to the right
         * @param {Number} angle of the rotation in degrees
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        right: function (angle) {
            this.dir -= angle;
            this.dir %= 360;

            if (!this.turtleIsHidden) {
                var t = this.board.create('transform', [-angle * Math.PI / 180, this.turtle], {type: 'rotate'});
                t.applyOnce(this.turtle2);
            }

            this.board.update();
            return this;
        },

        /**
         * Rotate the turtle direction to the right.
         * @param {Number} angle of the rotation in degrees
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        left: function (angle) {
            return this.right(-angle);
        },

        /**
         * Pen up, stops visible drawing
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        penUp: function () {
            this.isPenDown = false;
            return this;
        },

        /**
         * Pen down, continues visible drawing
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        penDown: function () {
            this.isPenDown = true;
            this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
            this.objects.push(this.curve);

            return this;
        },

        /**
         * Removes the turtle curve from the board. The turtle stays in its position.
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        clean: function () {
            var i, el;

            for (i = 0; i < this.objects.length; i++) {
                el = this.objects[i];
                if (el.type === Const.OBJECT_TYPE_CURVE) {
                    this.board.removeObject(el);
                    this.objects.splice(i, 1);
                }
            }

            this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
            this.objects.push(this.curve);
            this.board.update();

            return this;
        },

        /**
         *  Removes the turtle completely and resets it to its initial position and direction.
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        clearScreen: function () {
            var i, el,
                len = this.objects.length;

            for (i = 0; i < len; i++) {
                el = this.objects[i];
                this.board.removeObject(el);
            }

            this.init(0, 0, 90);
            return this;
        },

        /**
         *  Moves the turtle without drawing to a new position
         * @param {Number} x new x- coordinate
         * @param {Number} y new y- coordinate
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        setPos: function (x, y) {
            var t;

            if (Type.isArray(x)) {
                this.pos = x;
            } else {
                this.pos = [x, y];
            }

            if (!this.turtleIsHidden) {
                this.turtle.setPositionDirectly(Const.COORDS_BY_USER, [x, y]);
                this.turtle2.setPositionDirectly(Const.COORDS_BY_USER, [x, y + this.arrowLen]);
                t = this.board.create('transform', [-(this.dir - 90) * Math.PI / 180, this.turtle], {type: 'rotate'});
                t.applyOnce(this.turtle2);
            }

            this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
            this.objects.push(this.curve);
            this.board.update();

            return this;
        },

        /**
         *  Sets the pen size. Equivalent to setAttribute({strokeWidth:size})
         * but affects only the future turtle.
         * @param {Number} size
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        setPenSize: function (size) {
            //this.visProp.strokewidth = size;
            this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this.copyAttr('strokeWidth', size));
            this.objects.push(this.curve);
            return this;
        },

        /**
         *  Sets the pen color. Equivalent to setAttribute({strokeColor:color})
         * but affects only the future turtle.
         * @param {String} color
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        setPenColor: function (color) {
            this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this.copyAttr('strokeColor', color));
            this.objects.push(this.curve);

            return this;
        },

        /**
         *  Sets the highlight pen color. Equivalent to setAttribute({highlightStrokeColor:color})
         * but affects only the future turtle.
         * @param {String} color
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        setHighlightPenColor: function (color) {
            //this.visProp.highlightstrokecolor = colStr;
            this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this.copyAttr('highlightStrokeColor', color));
            this.objects.push(this.curve);
            return this;
        },

        /**
         * Sets properties of the turtle, see also {@link JXG.GeometryElement#setAttribute}.
         * Sets the property for all curves of the turtle in the past and in the future.
         * @param {Object} attributes key:value pairs
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        setAttribute: function (attributes) {
            var i, el, tmp,
                len = this.objects.length;

            for (i = 0; i < len; i++) {
                el = this.objects[i];
                if (el.type === Const.OBJECT_TYPE_CURVE) {
                    el.setAttribute(attributes);
                }
            }

            // Set visProp of turtle
            tmp = this.visProp.id;
            this.visProp = Type.deepCopy(this.curve.visProp);
            this.visProp.id = tmp;
            this._attributes = Type.deepCopy(this.visProp);
            delete this._attributes.id;

            return this;
        },

        /**
         * Set a future attribute of the turtle.
         * @private
         * @param {String} key
         * @param {Number|String} val
         * @returns {Object} pointer to the attributes object
         */
        copyAttr: function (key, val) {
            this._attributes[key.toLowerCase()] = val;
            return this._attributes;
        },

        /**
         * Sets the visibility of the turtle head to true,
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        showTurtle: function () {
            this.turtleIsHidden = false;
            this.arrow.setAttribute({visible: true});
            this.visProp.arrow.visible = false;
            this.setPos(this.pos[0], this.pos[1]);
            this.board.update();

            return this;
        },

        /**
         * Sets the visibility of the turtle head to false,
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        hideTurtle: function () {
            this.turtleIsHidden = true;
            this.arrow.setAttribute({visible: false});
            this.visProp.arrow.visible = false;
            this.board.update();

            return this;
        },

        /**
         * Moves the turtle to position [0,0].
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        home: function () {
            this.pos = [0, 0];
            this.setPos(this.pos[0], this.pos[1]);

            return this;
        },

        /**
         *  Pushes the position of the turtle on the stack.
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        pushTurtle: function () {
            this.stack.push([this.pos[0], this.pos[1], this.dir]);

            return this;
        },

        /**
         *  Gets the last position of the turtle on the stack, sets the turtle to this position and removes this
         * position from the stack.
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        popTurtle: function () {
            var status = this.stack.pop();
            this.pos[0] = status[0];
            this.pos[1] = status[1];
            this.dir = status[2];
            this.setPos(this.pos[0], this.pos[1]);

            return this;
        },

        /**
         * Rotates the turtle into a new direction.
         * There are two possibilities:
         * @param {Number|Array} target If a number is given, it is interpreted as the new direction to look to; If an array
         * consisting of two Numbers is given targeted is used as a pair coordinates.
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        lookTo: function (target) {
            var ax, ay, bx, by, beta;

            if (Type.isArray(target)) {
                ax = this.pos[0];
                ay = this.pos[1];
                bx = target[0];
                by = target[1];

                // Rotate by the slope of the line [this.pos, target]
                beta = Math.atan2(by - ay, bx - ax);
                this.right(this.dir - (beta * 180 / Math.PI));
            } else if (Type.isNumber(target)) {
                this.right(this.dir - target);
            }
            return this;
        },

        /**
         * Moves the turtle to a given coordinate pair.
         * The direction is not changed.
         * @param {Array} target Coordinates of the point where the turtle looks to.
         * @returns {JXG.Turtle} pointer to the turtle object
         */
        moveTo: function (target) {
            var dx, dy, t;

            if (Type.isArray(target)) {
                dx = target[0] - this.pos[0];
                dy = target[1] - this.pos[1];

                if (!this.turtleIsHidden) {
                    t = this.board.create('transform', [dx, dy], {type: 'translate'});
                    t.applyOnce(this.turtle);
                    t.applyOnce(this.turtle2);
                }

                if (this.isPenDown) {
                    // IE workaround
                    if (this.curve.dataX.length >= 8192) {
                        this.curve = this.board.create('curve', [[this.pos[0]], [this.pos[1]]], this._attributes);
                        this.objects.push(this.curve);
                    }
                }

                this.pos[0] = target[0];
                this.pos[1] = target[1];

                if (this.isPenDown) {
                    this.curve.dataX.push(this.pos[0]);
                    this.curve.dataY.push(this.pos[1]);
                }
                this.board.update();
            }

            return this;
        },

        /**
         * Alias for {@link JXG.Turtle#forward}
         */
        fd: function (len) { return this.forward(len); },
        /**
         * Alias for {@link JXG.Turtle#back}
         */
        bk: function (len) { return this.back(len); },
        /**
         * Alias for {@link JXG.Turtle#left}
         */
        lt: function (angle) { return this.left(angle); },
        /**
         * Alias for {@link JXG.Turtle#right}
         */
        rt: function (angle) { return this.right(angle); },
        /**
         * Alias for {@link JXG.Turtle#penUp}
         */
        pu: function () { return this.penUp(); },
        /**
         * Alias for {@link JXG.Turtle#penDown}
         */
        pd: function () { return this.penDown(); },
        /**
         * Alias for {@link JXG.Turtle#hideTurtle}
         */
        ht: function () { return this.hideTurtle(); },
        /**
         * Alias for {@link JXG.Turtle#showTurtle}
         */
        st: function () { return this.showTurtle(); },
        /**
         * Alias for {@link JXG.Turtle#clearScreen}
         */
        cs: function () { return this.clearScreen(); },
        /**
         * Alias for {@link JXG.Turtle#pushTurtle}
         */
        push: function () { return this.pushTurtle(); },
        /**
         * Alias for {@link JXG.Turtle#popTurtle}
         */
        pop: function () { return this.popTurtle(); },

        /**
         * The "co"-coordinate of the turtle curve at position t is returned.
         *
         * @param {Number} t parameter
         * @param {String} co. Either 'X' or 'Y'.
         * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
         */
        evalAt: function (t, co) {
            var i, j, el, tc,
                len = this.objects.length;

            for (i = 0, j = 0; i < len; i++) {
                el = this.objects[i];

                if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
                    if (j <= t && t < j + el.numberPoints) {
                        tc = (t - j);
                        return el[co](tc);
                    }
                    j += el.numberPoints;
                }
            }

            return this[co]();
        },

        /**
         * if t is not supplied the x-coordinate of the turtle is returned. Otherwise
         * the x-coordinate of the turtle curve at position t is returned.
         * @param {Number} t parameter
         * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
         */
        X: function (t) {
            if (!Type.exists(t)) {
                return this.pos[0];
            }

            return this.evalAt(t, 'X');
        },

        /**
         * if t is not supplied the y-coordinate of the turtle is returned. Otherwise
         * the y-coordinate of the turtle curve at position t is returned.
         * @param {Number} t parameter
         * @returns {Number} x-coordinate of the turtle position or x-coordinate of turtle at position t
         */
        Y: function (t) {
            if (!Type.exists(t)) {
                return this.pos[1];
            }
            return this.evalAt(t, 'Y');
        },

        /**
         * @returns {Number} z-coordinate of the turtle position
         */
        Z: function (t) {
            return 1.0;
        },

        /**
         * Gives the lower bound of the parameter if the the turtle is treated as parametric curve.
         */
        minX: function () {
            return 0;
        },

        /**
         * Gives the upper bound of the parameter if the the turtle is treated as parametric curve.
         * May be overwritten in @see generateTerm.
         */
        maxX: function () {
            var i, el,
                len = this.objects.length,
                np = 0;

            for (i = 0; i < len; i++) {
                el = this.objects[i];
                if (el.elementClass === Const.OBJECT_CLASS_CURVE) {
                    np += this.objects[i].numberPoints;
                }
            }
            return np;
        },

        /**
         * Checks whether (x,y) is near the curve.
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @returns {Boolean} True if (x,y) is near the curve, False otherwise.
         */
        hasPoint: function (x, y) {
            var i, el;

            // run through all curves of this turtle
            for (i = 0; i < this.objects.length; i++) {
                el = this.objects[i];

                if (el.type === Const.OBJECT_TYPE_CURVE) {
                    if (el.hasPoint(x, y)) {
                        // So what??? All other curves have to be notified now (for highlighting)
                        return true;
                        // This has to be done, yet.
                    }
                }
            }
            return false;
        }
    });

    /**
     * @class This element is used to provide a constructor for a turtle.
     * @pseudo
     * @description  Creates a new turtle
     * @name Turtle
     * @augments JXG.Turtle
     * @constructor
     * @type JXG.Turtle
     *
     * @param {JXG.Board} board The board the turtle is put on.
     * @param {Array} parents
     * @param {Object} attributes Object containing properties for the element such as stroke-color and visibility. See {@link JXG.GeometryElement#setAttribute}
     * @returns {JXG.Turtle} Reference to the created turtle object.
     */
    JXG.createTurtle = function (board, parents, attributes) {
        var attr;
        parents = parents || [];

        attr = Type.copyAttributes(attributes, board.options, 'turtle');
        return new JXG.Turtle(board, parents, attr);
    };

    JXG.registerElement('turtle', JXG.createTurtle);

    return {
        Turtle: JXG.Turtle,
        createTurtle: JXG.createTurtle
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 math/math
 math/geometry
 base/constants
 base/element
 base/coords
 utils/type
  elements:
   text
 */

/**
 * @fileoverview In this file the geometry object Ticks is defined. Ticks provides
 * methods for creation and management of ticks on an axis.
 * @author graphjs
 * @version 0.1
 */

define('base/ticks',[
    'jxg', 'math/math', 'math/geometry', 'math/numerics', 'base/constants', 'base/element', 'base/coords', 'utils/type', 'base/text'
], function (JXG, Mat, Geometry, Numerics, Const, GeometryElement, Coords, Type, Text) {

    "use strict";

    /**
     * Creates ticks for an axis.
     * @class Ticks provides methods for creation and management
     * of ticks on an axis.
     * @param {JXG.Line} line Reference to the axis the ticks are drawn on.
     * @param {Number|Array} ticks Number defining the distance between two major ticks or an array defining static ticks.
     * @param {Object} attributes Properties
     * @see JXG.Line#addTicks
     * @constructor
     * @extends JXG.GeometryElement
     */
    JXG.Ticks = function (line, ticks, attributes) {
        this.constructor(line.board, attributes, Const.OBJECT_TYPE_TICKS, Const.OBJECT_CLASS_OTHER);

        /**
         * The line the ticks belong to.
         * @type JXG.Line
         */
        this.line = line;

        /**
         * The board the ticks line is drawn on.
         * @type JXG.Board
         */
        this.board = this.line.board;

        /**
         * A function calculating ticks delta depending on the ticks number.
         * @type Function
         */
        this.ticksFunction = null;

        /**
         * Array of fixed ticks.
         * @type Array
         */
        this.fixedTicks = null;

        /**
         * Equidistant ticks. Distance is defined by ticksFunction
         * @type Boolean
         */
        this.equidistant = false;

        this.labelsData = [];

        if (Type.isFunction(ticks)) {
            this.ticksFunction = ticks;
            throw new Error("Function arguments are no longer supported.");
        }

        if (Type.isArray(ticks)) {
            this.fixedTicks = ticks;
        } else {
            if (Math.abs(ticks) < Mat.eps || ticks < 0) {
                ticks = attributes.defaultdistance;
            }

            /*
             * Ticks function:
             * determines the distance (in user units) of two major ticks
             */
            this.ticksFunction = this.makeTicksFunction(ticks);

            this.equidistant = true;
        }

        /**
         * Least distance between two ticks, measured in pixels.
         * @type int
         */
        this.minTicksDistance = attributes.minticksdistance;

        /**
         * Stores the ticks coordinates
         * @type Array
         */
        this.ticks = [];

        /**
         * Distance between two major ticks in user coordinates
         * @type Number
         */
        this.ticksDelta = 1;

        /**
         * Array where the labels are saved. There is an array element for every tick,
         * even for minor ticks which don't have labels. In this case the array element
         * contains just <tt>null</tt>.
         * @type Array
         */
        this.labels = [];

        /**
         * A list of labels which have to be displayed in updateRenderer.
         * @type Array
         */
        this.labelData = [];

        /**
         * To ensure the uniqueness of label ids this counter is used.
         * @type number
         */
        this.labelCounter = 0;

        this.id = this.line.addTicks(this);
        this.elType = 'ticks';
        this.inherits.push(this.labels);
        this.board.setId(this, 'Ti');
    };

    JXG.Ticks.prototype = new GeometryElement();

    JXG.extend(JXG.Ticks.prototype, /** @lends JXG.Ticks.prototype */ {

        /**
         * Ticks function:
         * determines the distance (in user units) of two major ticks.
         * See above in constructor and in @see JXG.GeometryElement#setAttribute
         *
         * @private
         * @param {Number} ticks Distance between two major ticks
         * @returns {Function} returns method ticksFunction
         */
        makeTicksFunction: function (ticks) {
            return function () {
                var delta, b, dist;

                if (Type.evaluate(this.visProp.insertticks)) {
                    b = this.getLowerAndUpperBounds(this.getZeroCoordinates(), 'ticksdistance');
                    dist = b.upper - b.lower;

                    delta = Math.pow(10, Math.floor(Math.log(0.6 * dist) / Math.LN10));
                    if (dist <= 6 * delta) {
                        delta *= 0.5;
                    }
                    return delta;
                }

                // upto 0.99.1:
                return ticks;
            };
        },

        /**
         * Checks whether (x,y) is near the line.
         * Only available for line elements,  not for ticks on curves.
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @returns {Boolean} True if (x,y) is near the line, False otherwise.
         */
        hasPoint: function (x, y) {
            var i, t,
                len = (this.ticks && this.ticks.length) || 0,
                r, type;

            if (Type.isObject(Type.evaluate(this.visProp.precision))) {
                type = this.board._inputDevice;
                r = Type.evaluate(this.visProp.precision[type]);
            } else {
                // 'inherit'
                r = this.board.options.precision.hasPoint;
            }
            r += Type.evaluate(this.visProp.strokewidth) * 0.5;
            if (!Type.evaluate(this.line.visProp.scalable) ||
                this.line.elementClass === Const.OBJECT_CLASS_CURVE) {
                return false;
            }

            // Ignore non-axes and axes that are not horizontal or vertical
            if (this.line.stdform[1] !== 0 && this.line.stdform[2] !== 0 && this.line.type !== Const.OBJECT_TYPE_AXIS) {
                return false;
            }

            for (i = 0; i < len; i++) {
                t = this.ticks[i];

                // Skip minor ticks
                if (t[2]) {
                    // Ignore ticks at zero
                    if (!((this.line.stdform[1] === 0 && Math.abs(t[0][0] - this.line.point1.coords.scrCoords[1]) < Mat.eps) ||
                            (this.line.stdform[2] === 0 && Math.abs(t[1][0] - this.line.point1.coords.scrCoords[2]) < Mat.eps))) {
                        // tick length is not zero, ie. at least one pixel
                        if (Math.abs(t[0][0] - t[0][1]) >= 1 || Math.abs(t[1][0] - t[1][1]) >= 1) {
                            if (this.line.stdform[1] === 0) {
                                // Allow dragging near axes only.
                                if (Math.abs(y - (t[1][0] + t[1][1]) * 0.5) < 2 * r && t[0][0] - r < x && x < t[0][1] + r) {
                                    return true;
                                }
                            } else if (this.line.stdform[2] === 0) {
                                if (Math.abs(x - (t[0][0] + t[0][1]) * 0.5) < 2 * r && t[1][0] - r < y && y < t[1][1] + r) {
                                    return true;
                                }
                            }
                        }
                    }
                }
            }

            return false;
        },

        /**
         * Sets x and y coordinate of the tick.
         * @param {number} method The type of coordinates used here. Possible values are {@link JXG.COORDS_BY_USER} and {@link JXG.COORDS_BY_SCREEN}.
         * @param {Array} coords coordinates in screen/user units
         * @param {Array} oldcoords previous coordinates in screen/user units
         * @returns {JXG.Ticks} this element
         */
        setPositionDirectly: function (method, coords, oldcoords) {
            var dx, dy,
                c = new Coords(method, coords, this.board),
                oldc = new Coords(method, oldcoords, this.board),
                bb = this.board.getBoundingBox();

            if (this.line.type !== Const.OBJECT_TYPE_AXIS ||
                !Type.evaluate(this.line.visProp.scalable)) {

                return this;
            }

            if (Math.abs(this.line.stdform[1]) < Mat.eps &&
                Math.abs(c.usrCoords[1] * oldc.usrCoords[1]) > Mat.eps) {

                // Horizontal line
                dx = oldc.usrCoords[1] / c.usrCoords[1];
                bb[0] *= dx;
                bb[2] *= dx;
                this.board.setBoundingBox(bb, this.board.keepaspectratio, 'update');

            } else if (Math.abs(this.line.stdform[2]) < Mat.eps &&
                       Math.abs(c.usrCoords[2] * oldc.usrCoords[2]) > Mat.eps) {

                // Vertical line
                dy = oldc.usrCoords[2] / c.usrCoords[2];
                bb[3] *= dy;
                bb[1] *= dy;
                this.board.setBoundingBox(bb, this.board.keepaspectratio, 'update');
            }

            return this;
        },

        /**
         * (Re-)calculates the ticks coordinates.
         * @private
         */
        calculateTicksCoordinates: function () {
            var coordsZero, bounds,
                r_max, bb;

            if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
                // Calculate Ticks width and height in Screen and User Coordinates
                this.setTicksSizeVariables();

                // If the parent line is not finite, we can stop here.
                if (Math.abs(this.dx) < Mat.eps &&
                    Math.abs(this.dy) < Mat.eps) {
                    return;
                }
            }

            // Get Zero (coords element for lines , number for curves)
            coordsZero = this.getZeroCoordinates();

            // Calculate lower bound and upper bound limits based on distance
            // between p1 and center and p2 and center
            if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
                bounds = this.getLowerAndUpperBounds(coordsZero);
            } else {
                bounds = {
                    lower: this.line.minX(),
                    upper: this.line.maxX()
                };
            }

            if (Type.evaluate(this.visProp.type) === 'polar') {
                bb = this.board.getBoundingBox();
                r_max = Math.max(Math.sqrt(bb[0] * bb[0] + bb[1] * bb[1]),
                    Math.sqrt(bb[2] * bb[2] + bb[3] * bb[3]));
                bounds.upper = r_max;
            }

            // Clean up
            this.ticks = [];
            this.labelsData = [];
            // Create Ticks Coordinates and Labels
            if (this.equidistant) {
                this.generateEquidistantTicks(coordsZero, bounds);
            } else {
                this.generateFixedTicks(coordsZero, bounds);
            }

            return this;
        },

        /**
         * Sets the variables used to set the height and slope of each tick.
         *
         * @private
         */
        setTicksSizeVariables: function (pos) {
            var d, mi, ma, len,
                distMaj = Type.evaluate(this.visProp.majorheight) * 0.5,
                distMin = Type.evaluate(this.visProp.minorheight) * 0.5;

            // For curves:
            if (Type.exists(pos)) {
                mi = this.line.minX();
                ma = this.line.maxX();
                len = this.line.points.length;
                if (len < 2) {
                    this.dxMaj = 0;
                    this.dyMaj = 0;
                } else if (Mat.relDif(pos, mi) < Mat.eps) {
                    this.dxMaj = this.line.points[0].usrCoords[2] - this.line.points[1].usrCoords[2];
                    this.dyMaj = this.line.points[1].usrCoords[1] - this.line.points[0].usrCoords[1];
                } else if (Mat.relDif(pos, ma) < Mat.eps) {
                    this.dxMaj = this.line.points[len - 2].usrCoords[2] - this.line.points[len - 1].usrCoords[2];
                    this.dyMaj = this.line.points[len - 1].usrCoords[1] - this.line.points[len - 2].usrCoords[1];
                } else {
                    this.dxMaj = -Numerics.D(this.line.Y)(pos);
                    this.dyMaj = Numerics.D(this.line.X)(pos);
                }
            } else {
                // ticks width and height in screen units
                this.dxMaj = this.line.stdform[1];
                this.dyMaj = this.line.stdform[2];
            }
            this.dxMin = this.dxMaj;
            this.dyMin = this.dyMaj;

            // ticks width and height in user units
            this.dx = this.dxMaj;
            this.dy = this.dyMaj;

            // After this, the length of the vector (dxMaj, dyMaj) in screen coordinates is equal to distMaj pixel.
            d = Math.sqrt(
                this.dxMaj * this.dxMaj * this.board.unitX * this.board.unitX +
                    this.dyMaj * this.dyMaj * this.board.unitY * this.board.unitY
            );
            this.dxMaj *= distMaj / d * this.board.unitX;
            this.dyMaj *= distMaj / d * this.board.unitY;
            this.dxMin *= distMin / d * this.board.unitX;
            this.dyMin *= distMin / d * this.board.unitY;

            // Grid-like ticks?
            this.minStyle= (Type.evaluate(this.visProp.minorheight) < 0) ? 'infinite' : 'finite';
            this.majStyle= (Type.evaluate(this.visProp.majorheight) < 0) ? 'infinite' : 'finite';
        },

        /**
         * Returns the coordinates of the point zero of the line.
         *
         * If the line is an {@link Axis}, the coordinates of the projection of the board's zero point is returned
         *
         * Otherwise, the coordinates of the point that acts as zero are
         * established depending on the value of {@link JXG.Ticks#anchor}
         *
         * @returns {JXG.Coords} Coords object for the zero point on the line
         * @private
         */
        getZeroCoordinates: function () {
            var c1x, c1y, c1z, c2x, c2y, c2z, t, mi, ma,
                ev_a = Type.evaluate(this.visProp.anchor);

            if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
                if (this.line.type === Const.OBJECT_TYPE_AXIS) {
                    return Geometry.projectPointToLine({
                        coords: {
                            usrCoords: [1, 0, 0]
                        }
                    }, this.line, this.board);
                }
                c1z = this.line.point1.coords.usrCoords[0];
                c1x = this.line.point1.coords.usrCoords[1];
                c1y = this.line.point1.coords.usrCoords[2];
                c2z = this.line.point2.coords.usrCoords[0];
                c2x = this.line.point2.coords.usrCoords[1];
                c2y = this.line.point2.coords.usrCoords[2];

                if (ev_a === 'right') {
                    return this.line.point2.coords;
                }
                if (ev_a === 'middle') {
                    return new Coords(Const.COORDS_BY_USER, [
                        (c1z + c2z) * 0.5,
                        (c1x + c2x) * 0.5,
                        (c1y + c2y) * 0.5
                    ], this.board);
                }
                if (Type.isNumber(ev_a)) {
                    return new Coords(Const.COORDS_BY_USER, [
                        c1z + (c2z - c1z) * ev_a,
                        c1x + (c2x - c1x) * ev_a,
                        c1y + (c2y - c1y) * ev_a
                    ], this.board);
                }
                return this.line.point1.coords;
            }
            mi = this.line.minX();
            ma = this.line.maxX();
            if (ev_a === 'right') {
                t = ma;
            } else if (ev_a === 'middle') {
                t = (mi + ma) * 0.5;
            } else if (Type.isNumber(ev_a)) {
                t = mi * (1 - ev_a) + ma * ev_a;
                // t = ev_a;
            } else {
                t = mi;
            }
            return t;
        },

        /**
         * Calculate the lower and upper bounds for tick rendering
         * If {@link JXG.Ticks#includeBoundaries} is false, the boundaries will exclude point1 and point2
         *
         * @param  {JXG.Coords} coordsZero
         * @returns {String} type  (Optional) If type=='ticksdistance' the bounds are
         *                         the intersection of the line with the bounding box of the board.
         *                         Otherwise, it is the projection of the corners of the bounding box
         *                         to the line. The first case is needed to automatically
         *                         generate ticks. The second case is for drawing of the ticks.
         * @returns {Object}     contains the lower and upper bounds
         *
         * @private
         */
        getLowerAndUpperBounds: function (coordsZero, type) {
            var lowerBound, upperBound,
                fA, lA,
                point1, point2, isPoint1inBoard, isPoint2inBoard,
                // We use the distance from zero to P1 and P2 to establish lower and higher points
                dZeroPoint1, dZeroPoint2,
                ev_sf = Type.evaluate(this.line.visProp.straightfirst),
                ev_sl = Type.evaluate(this.line.visProp.straightlast),
                ev_i = Type.evaluate(this.visProp.includeboundaries);

            // The line's defining points that will be adjusted to be within the board limits
            if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) {
                return {
                    lower: this.line.minX(),
                    upper: this.line.maxX()
                };
            }

            point1 = new Coords(Const.COORDS_BY_USER, this.line.point1.coords.usrCoords, this.board);
            point2 = new Coords(Const.COORDS_BY_USER, this.line.point2.coords.usrCoords, this.board);
            // Are the original defining points within the board?
            isPoint1inBoard = (Math.abs(point1.usrCoords[0]) >= Mat.eps &&
                point1.scrCoords[1] >= 0.0 && point1.scrCoords[1] <= this.board.canvasWidth &&
                point1.scrCoords[2] >= 0.0 && point1.scrCoords[2] <= this.board.canvasHeight);
            isPoint2inBoard = (Math.abs(point2.usrCoords[0]) >= Mat.eps &&
                point2.scrCoords[1] >= 0.0 && point2.scrCoords[1] <= this.board.canvasWidth &&
                point2.scrCoords[2] >= 0.0 && point2.scrCoords[2] <= this.board.canvasHeight);

            // Adjust line limit points to be within the board
            if (Type.exists(type) || type === 'tickdistance') {
                // The good old calcStraight is needed for determining the distance between major ticks.
                // Here, only the visual area is of importance
                Geometry.calcStraight(this.line, point1, point2, Type.evaluate(this.line.visProp.margin));
            } else {
                // This function projects the corners of the board to the line.
                // This is important for diagonal lines with infinite tick lines.
                Geometry.calcLineDelimitingPoints(this.line, point1, point2);
            }

            // Shorten ticks bounds such that ticks are not through arrow heads
            fA = Type.evaluate(this.line.visProp.firstarrow);
            lA = Type.evaluate(this.line.visProp.lastarrow);
            if (fA || lA) {
                this.board.renderer.getPositionArrowHead(this.line, point1, point2,
                        Type.evaluate(this.line.visProp.strokewidth));

                if (fA) {
                    point1.setCoordinates(Const.COORDS_BY_SCREEN, [
                        point1.scrCoords[1],
                        point1.scrCoords[2]
                    ]);
                }
                if (lA) {
                    point2.setCoordinates(Const.COORDS_BY_SCREEN, [
                        point2.scrCoords[1],
                        point2.scrCoords[2]
                    ]);
                }
                // if (fA) {
                //     point1.setCoordinates(Const.COORDS_BY_SCREEN, [
                //         point1.scrCoords[1] - obj.d1x,
                //         point1.scrCoords[2] - obj.d1y
                //     ]);
                // }
                // if (lA) {
                //     point2.setCoordinates(Const.COORDS_BY_SCREEN, [
                //         point2.scrCoords[1] - obj.d2x,
                //         point2.scrCoords[2] - obj.d2y
                //     ]);
                // }
            }


            // Calculate (signed) distance from Zero to P1 and to P2
            dZeroPoint1 = this.getDistanceFromZero(coordsZero, point1);
            dZeroPoint2 = this.getDistanceFromZero(coordsZero, point2);

            // We have to establish if the direction is P1->P2 or P2->P1 to set the lower and upper
            // boundaries appropriately. As the distances contain also a sign to indicate direction,
            // we can compare dZeroPoint1 and dZeroPoint2 to establish the line direction
            if (dZeroPoint1 < dZeroPoint2) { // Line goes P1->P2
                lowerBound = dZeroPoint1;
                if (!ev_sf && isPoint1inBoard && !ev_i) {
                    lowerBound += Mat.eps;
                }
                upperBound = dZeroPoint2;
                if (!ev_sl && isPoint2inBoard && !ev_i) {
                    upperBound -= Mat.eps;
                }
            } else if (dZeroPoint2 < dZeroPoint1) { // Line goes P2->P1
                lowerBound = dZeroPoint2;
                if (!ev_sl && isPoint2inBoard && !ev_i) {
                    lowerBound += Mat.eps;
                }
                upperBound = dZeroPoint1;
                if (!ev_sf && isPoint1inBoard && !ev_i) {
                    upperBound -= Mat.eps;
                }
            } else { // P1 = P2 = Zero, we can't do a thing
                lowerBound = 0;
                upperBound = 0;
            }

            return {
                lower: lowerBound,
                upper: upperBound
            };
        },

        /**
         * Calculates the distance in user coordinates from zero to a given point including its sign.
         * Sign is positive, if the direction from zero to point is the same as the direction
         * zero to point2 of the line.
         *
         * @param  {JXG.Coords} zero  coordinates of the point considered zero
         * @param  {JXG.Coords} point coordinates of the point to find out the distance
         * @returns {Number}           distance between zero and point, including its sign
         * @private
         */
        getDistanceFromZero: function (zero, point) {
            var p1, p2,
                dirLine, dirPoint,
                distance;

            p1 = this.line.point1.coords;
            p2 = this.line.point2.coords;
            distance = zero.distance(Const.COORDS_BY_USER, point);

            // Establish sign
            dirLine = [p2.usrCoords[0] - p1.usrCoords[0],
                p2.usrCoords[1] - p1.usrCoords[1],
                p2.usrCoords[2] - p1.usrCoords[2]];
            dirPoint = [point.usrCoords[0] - zero.usrCoords[0],
                point.usrCoords[1] - zero.usrCoords[1],
                point.usrCoords[2] - zero.usrCoords[2]];
            if (Mat.innerProduct(dirLine, dirPoint, 3) < 0) {
                distance *= -1;
            }

            return distance;
        },

        /**
         * Creates ticks coordinates and labels automatically.
         * The frequency of ticks is affected by the values of {@link JXG.Ticks#insertTicks} and {@link JXG.Ticks#ticksDistance}
         *
         * @param  {JXG.Coords} coordsZero coordinates of the point considered zero
         * @param  {Object}     bounds     contains the lower and upper boundaries for ticks placement
         * @private
         */
        generateEquidistantTicks: function (coordsZero, bounds) {
            var tickPosition,
                eps2 = Mat.eps,
                deltas,
                // Distance between two major ticks in user coordinates
                ticksDelta = (this.equidistant ? this.ticksFunction(1) : this.ticksDelta),
                ev_it = Type.evaluate(this.visProp.insertticks),
                ev_mt = Type.evaluate(this.visProp.minorticks);

            if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
                // Calculate X and Y distance between two major ticks
                deltas = this.getXandYdeltas();
            }

            // adjust ticks distance
            ticksDelta *= Type.evaluate(this.visProp.scale);
            if (ev_it && this.minTicksDistance > Mat.eps) {
                ticksDelta = this.adjustTickDistance(ticksDelta, coordsZero, deltas);
                ticksDelta /= (ev_mt + 1);
            } else if (!ev_it) {
                ticksDelta /= (ev_mt + 1);
            }
            this.ticksDelta = ticksDelta;

            if (ticksDelta < Mat.eps) {
                return;
            }

            // Position ticks from zero to the positive side while not reaching the upper boundary
            tickPosition = 0;
            if (!Type.evaluate(this.visProp.drawzero)) {
                tickPosition = ticksDelta;
            }
            while (tickPosition <= bounds.upper + eps2) {
                // Only draw ticks when we are within bounds, ignore case where tickPosition < lower < upper
                if (tickPosition >= bounds.lower - eps2) {
                    this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas);
                }
                tickPosition += ticksDelta;

                // Emergency out
                if ((bounds.upper - tickPosition) > ticksDelta * 10000) {
                    break;
                }
            }

            // Position ticks from zero (not inclusive) to the negative side while not reaching the lower boundary
            tickPosition = -ticksDelta;
            while (tickPosition >= bounds.lower - eps2) {
                // Only draw ticks when we are within bounds, ignore case where lower < upper < tickPosition
                if (tickPosition <= bounds.upper + eps2) {
                    this.processTickPosition(coordsZero, tickPosition, ticksDelta, deltas);
                }
                tickPosition -= ticksDelta;

                // Emergency out
                if ((tickPosition - bounds.lower) > ticksDelta * 10000) {
                    break;
                }
            }
        },

        /**
         * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to adjust the
         * distance between two ticks depending on {@link JXG.Ticks#minTicksDistance} value
         *
         * @param  {Number}     ticksDelta  distance between two major ticks in user coordinates
         * @param  {JXG.Coords} coordsZero  coordinates of the point considered zero
         * @param  {Object}     deltas      x and y distance in pixel between two user units
         * @param  {Object}     bounds      upper and lower bound of the tick positions in user units.
         * @private
         */
        adjustTickDistance: function (ticksDelta, coordsZero, deltas) {
            var nx, ny, bounds,
                distScr,
                sgn = 1,
                ev_minti = Type.evaluate(this.visProp.minorticks);

            if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) {
                return ticksDelta;
            }
            bounds = this.getLowerAndUpperBounds(coordsZero, 'ticksdistance');
            nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta;
            ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta;
            distScr = coordsZero.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board));

            if (ticksDelta === 0.0) {
                return 0.0;
            }

            while (distScr / (ev_minti + 1) < this.minTicksDistance) {
                if (sgn === 1) {
                    ticksDelta *= 2;
                } else {
                    ticksDelta *= 5;
                }
                sgn *= -1;

                nx = coordsZero.usrCoords[1] + deltas.x * ticksDelta;
                ny = coordsZero.usrCoords[2] + deltas.y * ticksDelta;
                distScr = coordsZero.distance(Const.COORDS_BY_SCREEN, new Coords(Const.COORDS_BY_USER, [nx, ny], this.board));
            }
            return ticksDelta;
        },

        /**
         * Auxiliary method used by {@link JXG.Ticks#generateEquidistantTicks} to create a tick
         * in the line at the given tickPosition.
         *
         * @param  {JXG.Coords} coordsZero    coordinates of the point considered zero
         * @param  {Number}     tickPosition  current tick position relative to zero
         * @param  {Number}     ticksDelta    distance between two major ticks in user coordinates
         * @param  {Object}     deltas      x and y distance between two major ticks
         * @private
         */
        processTickPosition: function (coordsZero, tickPosition, ticksDelta, deltas) {
            var x, y, tickCoords, ti,
                labelVal = null;

            // Calculates tick coordinates
            if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
                x = coordsZero.usrCoords[1] + tickPosition * deltas.x;
                y = coordsZero.usrCoords[2] + tickPosition * deltas.y;
            } else {
                x = this.line.X(coordsZero + tickPosition);
                y = this.line.Y(coordsZero + tickPosition);
            }
            tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board);
            if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) {
                labelVal = coordsZero + tickPosition;
                this.setTicksSizeVariables(labelVal);

            }

            // Test if tick is a major tick.
            // This is the case if tickPosition/ticksDelta is
            // a multiple of the number of minorticks+1
            tickCoords.major = Math.round(tickPosition / ticksDelta) % (Type.evaluate(this.visProp.minorticks) + 1) === 0;

            // Compute the start position and the end position of a tick.
            // If both positions are out of the canvas, ti is empty.
            ti = this.createTickPath(tickCoords, tickCoords.major);
            if (ti.length === 3) {
                this.ticks.push(ti);
                if (tickCoords.major && Type.evaluate(this.visProp.drawlabels)) {
                    // major tick label
                    this.labelsData.push(
                        this.generateLabelData(
                            this.generateLabelText(tickCoords, coordsZero, labelVal),
                            tickCoords,
                            this.ticks.length
                        )
                    );
                } else {
                    // minor ticks have no labels
                    this.labelsData.push(null);
                }
            }
        },

        /**
         * Creates ticks coordinates and labels based on {@link JXG.Ticks#fixedTicks} and {@link JXG.Ticks#labels}.
         *
         * @param  {JXG.Coords} coordsZero Coordinates of the point considered zero
         * @param  {Object}     bounds     contains the lower and upper boundaries for ticks placement
         * @private
         */
        generateFixedTicks: function (coordsZero, bounds) {
            var tickCoords, labelText, i, ti,
                x, y,
                eps2 = Mat.eps, fixedTick,
                hasLabelOverrides = Type.isArray(this.visProp.labels),
                deltas,
                ev_dl = Type.evaluate(this.visProp.drawlabels);

            // Calculate X and Y distance between two major points in the line
            if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
                deltas = this.getXandYdeltas();
            }
            for (i = 0; i < this.fixedTicks.length; i++) {
                if (this.line.elementClass === Const.OBJECT_CLASS_LINE) {
                    fixedTick = this.fixedTicks[i];
                    x = coordsZero.usrCoords[1] + fixedTick * deltas.x;
                    y = coordsZero.usrCoords[2] + fixedTick * deltas.y;
                } else {
                    fixedTick = coordsZero + this.fixedTicks[i];
                    x = this.line.X(fixedTick);
                    y = this.line.Y(fixedTick);
                }
                tickCoords = new Coords(Const.COORDS_BY_USER, [x, y], this.board);

                if (this.line.elementClass === Const.OBJECT_CLASS_CURVE) {
                    this.setTicksSizeVariables(fixedTick);
                }

                // Compute the start position and the end position of a tick.
                // If tick is out of the canvas, ti is empty.
                ti = this.createTickPath(tickCoords, true);
                if (ti.length === 3 && fixedTick >= bounds.lower - eps2 &&
                    fixedTick <= bounds.upper + eps2) {
                    this.ticks.push(ti);

                    if (ev_dl &&
                            (hasLabelOverrides || Type.exists(this.visProp.labels[i]))) {
                        labelText = hasLabelOverrides ?
                                        Type.evaluate(this.visProp.labels[i]) : fixedTick;
                        this.labelsData.push(
                            this.generateLabelData(
                                this.generateLabelText(tickCoords, coordsZero, labelText),
                                tickCoords,
                                i
                            )
                        );
                    } else {
                        this.labelsData.push(null);
                    }
                }
            }
        },

        /**
         * Calculates the x and y distance in pixel between two units in user space.
         *
         * @returns {Object}
         * @private
         */
        getXandYdeltas: function () {
            var
                // Auxiliary points to store the start and end of the line according to its direction
                point1UsrCoords, point2UsrCoords,
                distP1P2 = this.line.point1.Dist(this.line.point2);

            if (this.line.type === Const.OBJECT_TYPE_AXIS) {
                // When line is an Axis, direction depends on Board Coordinates system

                // assume line.point1 and line.point2 are in correct order
                point1UsrCoords = this.line.point1.coords.usrCoords;
                point2UsrCoords = this.line.point2.coords.usrCoords;

                // Check if direction is incorrect, then swap
                if (point1UsrCoords[1] > point2UsrCoords[1] ||
                        (Math.abs(point1UsrCoords[1] - point2UsrCoords[1]) < Mat.eps &&
                        point1UsrCoords[2] > point2UsrCoords[2])) {
                    point1UsrCoords = this.line.point2.coords.usrCoords;
                    point2UsrCoords = this.line.point1.coords.usrCoords;
                }
            } else /* if (this.line.elementClass === Const.OBJECT_CLASS_LINE)*/ {
                // line direction is always from P1 to P2 for non Axis types
                point1UsrCoords = this.line.point1.coords.usrCoords;
                point2UsrCoords = this.line.point2.coords.usrCoords;
            }
            return {
                x: (point2UsrCoords[1] - point1UsrCoords[1]) / distP1P2,
                y: (point2UsrCoords[2] - point1UsrCoords[2]) / distP1P2
            };
        },

        /**
         * Check if (parts of) the tick is inside the canvas. The tick intersects the boundary
         * at two positions: [x[0], y[0]] and [x[1], y[1]] in screen coordinates.
         * @param  {Array}  x Array of length two
         * @param  {Array}  y Array of length two
         * @return {Boolean}   true if parts of the tick are inside of the canvas or on the boundary.
         */
        _isInsideCanvas: function(x, y, m) {
            var cw = this.board.canvasWidth,
                ch = this.board.canvasHeight;

            if (m === undefined) {
                m = 0;
            }
            return (x[0] >= m && x[0] <= cw - m && y[0] >= m && y[0] <= ch - m) ||
                    (x[1] >= m && x[1] <= cw - m && y[1] >= m && y[1] <= ch - m);
        },

        /**
         * @param {JXG.Coords} coords Coordinates of the tick on the line.
         * @param {Boolean} major True if tick is major tick.
         * @returns {Array} Array of length 3 containing path coordinates in screen coordinates
         *                 of the tick (arrays of length 2). 3rd entry is true if major tick otherwise false.
         *                 If the tick is outside of the canvas, the return array is empty.
         * @private
         */
        createTickPath: function (coords, major) {
            var c, lineStdForm, intersection,
                dxs, dys, dxr, dyr, alpha,
                style,
                x = [-2000000, -2000000],
                y = [-2000000, -2000000],
                i, r, r_max, bb, full, delta;

            c = coords.scrCoords;
            if (major) {
                dxs = this.dxMaj;
                dys = this.dyMaj;
                style = this.majStyle;
            } else {
                dxs = this.dxMin;
                dys = this.dyMin;
                style = this.minStyle;
            }
            lineStdForm = [-dys * c[1] - dxs * c[2], dys, dxs];

            // For all ticks regardless if of finite or infinite
            // tick length the intersection with the canvas border is
            // computed.
            if (major && Type.evaluate(this.visProp.type) === 'polar') {
                // polar style
                bb = this.board.getBoundingBox();
                full = 2.0 * Math.PI;
                delta = full / 180;
                //ratio = this.board.unitY / this.board.X;

                // usrCoords: Test if 'circle' is inside of the canvas
                c = coords.usrCoords;
                r = Math.sqrt(c[1] * c[1] + c[2] * c[2]);
                r_max = Math.max(Math.sqrt(bb[0] * bb[0] + bb[1] * bb[1]),
                                Math.sqrt(bb[2] * bb[2] + bb[3] * bb[3]));

                if (r < r_max) {
                    // Now, switch to screen coords
                    x = [];
                    y = [];
                    for (i = 0; i <= full; i += delta) {
                        x.push(this.board.origin.scrCoords[1] + r * Math.cos(i) * this.board.unitX);
                        y.push(this.board.origin.scrCoords[2] + r * Math.sin(i) * this.board.unitY);
                    }
                    return [x, y, major];
                }

            } else {
                // line style
                if (style === 'infinite') {
                    intersection = Geometry.meetLineBoard(lineStdForm, this.board);
                    x[0] = intersection[0].scrCoords[1];
                    x[1] = intersection[1].scrCoords[1];
                    y[0] = intersection[0].scrCoords[2];
                    y[1] = intersection[1].scrCoords[2];
                } else {
                    if (Type.evaluate(this.visProp.face) === '>') {
                        alpha = Math.PI/4;
                    } else if (Type.evaluate(this.visProp.face) === '<') {
                            alpha = -Math.PI/4;
                    } else {
                        alpha = 0;
                    }
                    dxr = Math.cos(alpha) * dxs - Math.sin(alpha) * dys;
                    dyr = Math.sin(alpha) * dxs + Math.cos(alpha) * dys;

                    x[0] = c[1] + dxr * Type.evaluate(this.visProp.tickendings[0]);
                    y[0] = c[2] - dyr * Type.evaluate(this.visProp.tickendings[0]);
                    x[1] = c[1];
                    y[1] = c[2];

                    alpha = -alpha;
                    dxr = Math.cos(alpha) * dxs - Math.sin(alpha) * dys;
                    dyr = Math.sin(alpha) * dxs + Math.cos(alpha) * dys;

                    x[2] = c[1] - dxr * Type.evaluate(this.visProp.tickendings[1]);
                    y[2] = c[2] + dyr * Type.evaluate(this.visProp.tickendings[1]);
                }

                // Check if (parts of) the tick is inside the canvas.
                if (this._isInsideCanvas(x, y)) {
                    return [x, y, major];
                }
            }

            return [];
        },

        /**
         * Format label texts. Show the desired number of digits
         * and use utf-8 minus sign.
         * @param  {Number} value Number to be displayed
         * @return {String}       The value converted into a string.
         * @private
         */
        formatLabelText: function(value) {
            var labelText,
                digits,
                ev_s = Type.evaluate(this.visProp.scalesymbol);

            // if value is Number
            if (Type.isNumber(value)) {
                labelText = (Math.round(value * 1.e13) / 1.e13).toString();
                if (labelText.length > Type.evaluate(this.visProp.maxlabellength) ||
                        labelText.indexOf('e') !== -1) {

                    digits = Type.evaluate(this.visProp.digits);
                    if (Type.evaluate(this.visProp.precision) !== 3 && digits === 3) {
                        // Use the deprecated attribute "precision"
                        digits = Type.evaluate(this.visProp.precision);
                    }

                    //labelText = value.toPrecision(digits).toString();
                    labelText = value.toExponential(digits).toString();
                }

                if (Type.evaluate(this.visProp.beautifulscientificticklabels)) {
                    labelText = this.beautifyScientificNotationLabel(labelText);
                }

                if (labelText.indexOf('.') > -1 && labelText.indexOf('e') === -1) {
                    // trim trailing zeros
                    labelText = labelText.replace(/0+$/, '');
                    // trim trailing .
                    labelText = labelText.replace(/\.$/, '');
                }
            } else {
                labelText = value.toString();
            }

            if (ev_s.length > 0) {
                if (labelText === '1') {
                    labelText = ev_s;
                } else if (labelText === '-1') {
                    labelText = '-' + ev_s;
                } else if (labelText !== '0') {
                    labelText = labelText + ev_s;
                }
            }

            if (Type.evaluate(this.visProp.useunicodeminus)) {
                labelText = labelText.replace(/-/g, '\u2212');
            }
            return labelText;
        },

        /**
         * Formats label texts to make labels displayed in scientific notation look beautiful.
         * For example, label 5.00e+6 will become 5•10⁶, label -1.00e-7 will become into -1•10⁻⁷
         * @param {String} labelText - The label that we want to convert
         * @returns {String} If labelText was not in scientific notation, return labelText without modifications.
         * Otherwise returns beautified labelText with proper superscript notation.
         */
        beautifyScientificNotationLabel: function(labelText) {
            var returnString;

            if (labelText.indexOf('e') === -1) {
                return labelText;
            }

            // Clean up trailing 0's, so numbers like 5.00e+6.0 for example become into 5e+6
            returnString = parseFloat(labelText.substring(0, labelText.indexOf('e'))) +
                            labelText.substring(labelText.indexOf('e'));

            // Replace symbols like -,0,1,2,3,4,5,6,7,8,9 with their superscript version.
            // Gets rid of + symbol since there is no need for it anymore.
            returnString = returnString.replace(/e(.*)$/g, function(match,$1){
                var temp = '\u2022' + '10';
                // Note: Since board ticks do not support HTTP elements like <sub>, we need to replace
                // all the numbers with superscript Unicode characters.
                temp +=  $1
                    .replace(/-/g, "\u207B")
                    .replace(/\+/g, '')
                    .replace(/0/g,'\u2070')
                    .replace(/1/g,'\u00B9')
                    .replace(/2/g,'\u00B2')
                    .replace(/3/g,'\u00B3')
                    .replace(/4/g,'\u2074')
                    .replace(/5/g,'\u2075')
                    .replace(/6/g,'\u2076')
                    .replace(/7/g,'\u2077')
                    .replace(/8/g,'\u2078')
                    .replace(/9/g,'\u2079');

                return temp;
            });

            return returnString;
        },

        /**
         * Creates the label text for a given tick. A value for the text can be provided as a number or string
         *
         * @param  {JXG.Coords}    tick  The Coords-object of the tick to create a label for
         * @param  {JXG.Coords}    zero  The Coords-object of line's zero
         * @param  {Number|String} value A predefined value for this tick
         * @returns {String}
         * @private
         */
        generateLabelText: function (tick, zero, value) {
            var labelText, distance;

            // No value provided, equidistant, so assign distance as value
            if (!Type.exists(value)) { // could be null or undefined
                distance = this.getDistanceFromZero(zero, tick);
                if (Math.abs(distance) < Mat.eps) { // Point is zero
                    return '0';
                }
                value = distance / Type.evaluate(this.visProp.scale);
            }
            labelText = this.formatLabelText(value);

            return labelText;
        },

        /**
         * Create a tick label data, i.e. text and coordinates
         * @param  {String}     labelText
         * @param  {JXG.Coords} tick
         * @param  {Number}     tickNumber
         * @returns {Object} with properties 'x', 'y', 't' (text), 'i' (tick number) or null in case of o label
         * @private
         */
        generateLabelData: function (labelText, tick, tickNumber) {
             var xa, ya, m, fs;

             // Test if large portions of the label are inside of the canvas
             // This is the last chance to abandon the creation of the label if it is mostly
             // outside of the canvas.
             fs = Type.evaluate(this.visProp.label.fontsize);
             xa = [tick.scrCoords[1], tick.scrCoords[1]];
             ya = [tick.scrCoords[2], tick.scrCoords[2]];
             m = (fs === undefined) ? 12 : fs;
             m *= 0.5;
             if (!this._isInsideCanvas(xa, ya, m)) {
                 return null;
             }

             xa = Type.evaluate(this.visProp.label.offset[0]);
             ya = Type.evaluate(this.visProp.label.offset[1]);

             return {
                 x: tick.usrCoords[1] + xa / (this.board.unitX),
                 y: tick.usrCoords[2] + ya / (this.board.unitY),
                 t: labelText,
                 i: tickNumber
             };
         },

        /**
         * Recalculate the tick positions and the labels.
         * @returns {JXG.Ticks}
         */
        update: function () {
            if (this.needsUpdate) {
                //this.visPropCalc.visible = Type.evaluate(this.visProp.visible);
                // A canvas with no width or height will create an endless loop, so ignore it
                if (this.board.canvasWidth !== 0 && this.board.canvasHeight !== 0) {
                    this.calculateTicksCoordinates();
                }
                // this.updateVisibility(this.line.visPropCalc.visible);
                //
                // for (var i = 0; i < this.labels.length; i++) {
                //     if (this.labels[i] !== null) {
                //         this.labels[i].prepareUpdate()
                //             .updateVisibility(this.line.visPropCalc.visible)
                //             .updateRenderer();
                //     }
                // }
            }

            return this;
        },

        /**
         * Uses the boards renderer to update the arc.
         * @returns {JXG.Ticks} Reference to the object.
         */
        updateRenderer: function () {
            if (!this.needsUpdate) {
                return this;
            }

            if (this.visPropCalc.visible) {
                this.board.renderer.updateTicks(this);
            }
            this.updateRendererLabels();

            this.setDisplayRendNode();
            // if (this.visPropCalc.visible != this.visPropOld.visible) {
            //     this.board.renderer.display(this, this.visPropCalc.visible);
            //     this.visPropOld.visible = this.visPropCalc.visible;
            // }

            this.needsUpdate = false;
            return this;
        },

        /**
         * Updates the label elements of the major ticks.
         *
         * @private
         * @returns {JXG.Ticks} Reference to the object.
         */
        updateRendererLabels: function() {
            var i, j,
                lenData, lenLabels,
                attr,
                label, ld,
                visible;

            // The number of labels needed
            lenData = this.labelsData.length;
            // The number of labels which already exist
            // The existing labels are stored in this.labels[]
            // The new label positions and label values are stored in this.labelsData[]
            lenLabels = this.labels.length;

            for (i = 0, j = 0; i < lenData; i++) {
                if (this.labelsData[i] === null) {
                    // This is a tick without label
                    continue;
                }

                ld = this.labelsData[i];
                if (j < lenLabels) {
                    // Take an already existing text element
                    label = this.labels[j];
                    label.setText(ld.t);
                    label.setCoords(ld.x, ld.y);
                    j++;
                } else {
                    // A new text element is needed
                    this.labelCounter += 1;

                    attr = {
                        isLabel: true,
                        layer: this.board.options.layer.line,
                        highlightStrokeColor: this.board.options.text.strokeColor,
                        highlightStrokeWidth: this.board.options.text.strokeWidth,
                        highlightStrokeOpacity: this.board.options.text.strokeOpacity,
                        priv: this.visProp.priv
                    };
                    attr = Type.deepCopy(attr, this.visProp.label);
                    attr.id = this.id + ld.i + 'Label' + this.labelCounter;

                    label = Text.createText(this.board, [ld.x, ld.y, ld.t], attr);
                    label.isDraggable = false;
                    label.dump = false;
                    this.labels.push(label);
                }

                // Look-ahead if the label inherits visiblity.
                // If yes, update label.
                visible = Type.evaluate(this.visProp.label.visible);
                if (visible === 'inherit') {
                    visible = this.visPropCalc.visible;
                }

                label.prepareUpdate()
                    .updateVisibility(visible)
                    .updateRenderer();

                label.distanceX = Type.evaluate(this.visProp.label.offset[0]);
                label.distanceY = Type.evaluate(this.visProp.label.offset[1]);
            }

            // Hide unused labels
            lenData = j;
            for (j = lenData; j < lenLabels; j++) {
                this.board.renderer.display(this.labels[j], false);
                // Tick labels have the attribute "visible: 'inherit'"
                // This must explicitely set to false, otherwise
                // this labels would be set to visible in the upcoming
                // update of the labels.
                this.labels[j].visProp.visible = this.labels[j].visPropCalc.visible = false;
            }

            return this;
        },

        hideElement: function () {
            var i;

            JXG.deprecated('Element.hideElement()', 'Element.setDisplayRendNode()');

            this.visPropCalc.visible = false;
            this.board.renderer.display(this, false);
            for (i = 0; i < this.labels.length; i++) {
                if (Type.exists(this.labels[i])) {
                    this.labels[i].hideElement();
                }
            }

            return this;
        },

        showElement: function () {
            var i;

            JXG.deprecated('Element.showElement()', 'Element.setDisplayRendNode()');

            this.visPropCalc.visible = true;
            this.board.renderer.display(this, false);

            for (i = 0; i < this.labels.length; i++) {
                if (Type.exists(this.labels[i])) {
                    this.labels[i].showElement();
                }
            }

            return this;
        }
    });

    /**
     * @class Ticks are used as distance markers on a line or curve.
     * They are
     * mainly used for axis elements and slider elements. Ticks may stretch infinitely
     * or finitely, which can be set with {@link Ticks#majorHeight} and {@link Ticks#minorHeight}.
     *
     * @pseudo
     * @description Ticks are markers on straight line elements or curves.
     * @name Ticks
     * @augments JXG.Ticks
     * @constructor
     * @type JXG.Ticks
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line|JXG.Curve} line The parents consist of the line or curve the ticks are going to be attached to.
     * @param {Number|Array} distance Number defining the distance between two major ticks or an
     * array defining static ticks. In case a number is specified, the ticks are <i>equidistant</i>,
     * in case of an array, a fixed number of static ticks is created at user-supplied positions.
     * Alternatively, the distance can be specified with the attribute
     * "ticksDistance". For arbitrary lines (and not axes) a "zero coordinate" is determined
     * which defines where the first tick is positioned. This zero coordinate
     * can be altered with the attribute "anchor". Possible values are "left", "middle", "right" or a number.
     * The default value is "left".
     *
     * @example
     * // Create an axis providing two coordinate pairs.
     *   var p1 = board.create('point', [0, 3]);
     *   var p2 = board.create('point', [1, 3]);
     *   var l1 = board.create('line', [p1, p2]);
     *   var t = board.create('ticks', [l1], {ticksDistance: 2});
     * </pre><div class="jxgbox" id="JXGee7f2d68-75fc-4ec0-9931-c76918427e63" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXGee7f2d68-75fc-4ec0-9931-c76918427e63', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false});
     *   var p1 = board.create('point', [0, 3]);
     *   var p2 = board.create('point', [1, 3]);
     *   var l1 = board.create('line', [p1, p2]);
     *   var t = board.create('ticks', [l1, 2], {ticksDistance: 2});
     * })();
     * </script><pre>
     */
    JXG.createTicks = function (board, parents, attributes) {
        var el, dist,
            attr = Type.copyAttributes(attributes, board.options, 'ticks');

        if (parents.length < 2) {
            dist = attr.ticksdistance;
        } else {
            dist = parents[1];
        }

        if (parents[0].elementClass === Const.OBJECT_CLASS_LINE ||
            parents[0].elementClass === Const.OBJECT_CLASS_CURVE) {
            el = new JXG.Ticks(parents[0], dist, attr);
        } else {
            throw new Error("JSXGraph: Can't create Ticks with parent types '" + (typeof parents[0]) + "'.");
        }

        // deprecated
        if (Type.isFunction(attr.generatelabelvalue)) {
            el.generateLabelText = attr.generatelabelvalue;
        }
        if (Type.isFunction(attr.generatelabeltext)) {
            el.generateLabelText = attr.generatelabeltext;
        }

        el.setParents(parents[0]);
        el.isDraggable = true;
        el.fullUpdate(parents[0].visPropCalc.visible);

        return el;
    };

    /**
     * @class Hatches can be used to mark congruent lines or curves.
     * @pseudo
     * @description
     * @name Hatch
     * @augments JXG.Ticks
     * @constructor
     * @type JXG.Ticks
     * @throws {Exception} If the element cannot be constructed with the given parent objects an exception is thrown.
     * @param {JXG.Line|JXG.curve} line The line or curve the hatch marks are going to be attached to.
     * @param {Number} numberofhashes Number of dashes.
     * @example
     * // Create an axis providing two coords pairs.
     *   var p1 = board.create('point', [0, 3]);
     *   var p2 = board.create('point', [1, 3]);
     *   var l1 = board.create('line', [p1, p2]);
     *   var t = board.create('hatch', [l1, 3]);
     * </pre><div class="jxgbox" id="JXG4a20af06-4395-451c-b7d1-002757cf01be" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG4a20af06-4395-451c-b7d1-002757cf01be', {boundingbox: [-1, 7, 7, -1], showcopyright: false, shownavigation: false});
     *   var p1 = board.create('point', [0, 3]);
     *   var p2 = board.create('point', [1, 3]);
     *   var l1 = board.create('line', [p1, p2]);
     *   var t = board.create('hatch', [l1, 3]);
     * })();
     * </script><pre>
     *
     * @example
     * // Alter the position of the hatch
     *
     * var p = board.create('point', [-5, 0]);
     * var q = board.create('point', [5, 0]);
     * var li = board.create('line', [p, q]);
     * var h = board.create('hatch', [li, 2], {anchor: 0.2});
     *
     * </pre><div id="JXG05d720ee-99c9-11e6-a9c7-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG05d720ee-99c9-11e6-a9c7-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *
     *     var p = board.create('point', [-5, 0]);
     *     var q = board.create('point', [5, 0]);
     *     var li = board.create('line', [p, q]);
     *     var h = board.create('hatch', [li, 2], {anchor: 0.2});
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * // Alternative hatch faces
     *
     * var li = board.create('line', [[-6,0], [6,3]]);
     * var h1 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'|'});
     * var h2 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'>', anchor: 0.3});
     * var h3 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'<', anchor: 0.7});
     *
     * </pre><div id="JXG974f7e89-eac8-4187-9aa3-fb8068e8384b" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG974f7e89-eac8-4187-9aa3-fb8068e8384b',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     // Alternative hatch faces
     *
     *     var li = board.create('line', [[-6,0], [6,3]]);
     *     var h1 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'|'});
     *     var h2 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'>', anchor: 0.3});
     *     var h3 = board.create('hatch', [li, 2], {tickEndings: [1,1], face:'<', anchor: 0.7});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createHatchmark = function (board, parents, attributes) {
        var num, i, base, width, totalwidth, el,
            pos = [],
            attr = Type.copyAttributes(attributes, board.options, 'hatch');

        if ((parents[0].elementClass !== Const.OBJECT_CLASS_LINE &&
            parents[0].elementClass !== Const.OBJECT_CLASS_CURVE) || typeof parents[1] !== 'number') {
            throw new Error("JSXGraph: Can't create Hatch mark with parent types '" + (typeof parents[0]) + "' and '" + (typeof parents[1]) + " and ''" + (typeof parents[2]) + "'.");
        }

        num = parents[1];
        width = attr.ticksdistance;
        totalwidth = (num - 1) * width;
        base = -totalwidth * 0.5;

        for (i = 0; i < num; i++) {
            pos[i] = base + i * width;
        }

        el = board.create('ticks', [parents[0], pos], attr);
        el.elType = 'hatch';

        return el;
    };

    JXG.registerElement('ticks', JXG.createTicks);
    JXG.registerElement('hash', JXG.createHatchmark);
    JXG.registerElement('hatch', JXG.createHatchmark);

    return {
        Ticks: JXG.Ticks,
        createTicks: JXG.createTicks,
        createHashmark: JXG.createHatchmark,
        createHatchmark: JXG.createHatchmark
    };
});

/*
 JessieCode Computer algebra algorithms

    Copyright 2011-2019
        Michael Gerhaeuser,
        Alfred Wassermann

    JessieCode is free software dual licensed under the GNU LGPL or MIT 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

    JessieCode 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 and
    the MIT License along with JessieCode. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */

/*global JXG: true, define: true, window: true, console: true, self: true, document: true, parser: true*/
/*jslint nomen: true, plusplus: true*/
/*eslint eqeqeq: "off"*/

/* depends:
 jxg
 parser/geonext
 base/constants
 base/text
 math/math
 math/geometry
 math/statistics
 utils/type
 utils/uuid
 */

/**
 * @fileoverview Here, the computer algebra algorithms are implemented.
 */

define('parser/ca',[
    'jxg', 'base/constants', 'base/text', 'math/math', 'math/geometry', 'math/statistics', 'utils/type', 'utils/env'
], function (JXG, Const, Text, Mat, Geometry, Statistics, Type, Env) {

    "use strict";

    /**
     * A JessieCode object provides an interface to the parser and stores all variables and objects used within a JessieCode script.
     * The optional argument <tt>code</tt> is interpreted after initializing. To evaluate more code after initializing a JessieCode instance
     * please use {@link JXG.JessieCode#parse}. For code snippets like single expressions use {@link JXG.JessieCode#snippet}.
     * @constructor
     * @param {String} [code] Code to parse.
     * @param {Boolean} [geonext=false] Geonext compatibility mode.
     */
    JXG.CA = function (node, createNode, parser) {
        this.node = node;
        this.createNode = createNode;
        this.parser = parser;
    };

    JXG.extend(JXG.CA.prototype, /** @lends JXG.CA.prototype */ {
        findMapNode: function (mapname, node) {
            var i, len, ret;

            //console.log("FINDMAP", node);
            if (node.value === 'op_assign' && node.children[0].value === mapname) {
                return node.children[1];
            } else if (node.children) {
                len = node.children.length;
                for (i = 0; i < len; ++i) {
                    ret = this.findMapNode(mapname, node.children[i]);
                    if (ret !== null) {
                        return ret;
                    }
                }
            }
            return null;
        },

        /**
         * Declare all subnodes as math nodes,
         * i.e recursively set node.isMath = true;
         */
        setMath: function (node) {
            var i, len;

            if ((node.type == 'node_op' && (
                node.value == 'op_add' || node.value == 'op_sub' ||
                node.value == 'op_mul' || node.value == 'op_div' ||
                node.value == 'op_neg' || node.value == 'op_execfun' ||
                node.value == 'op_exp')) ||
                node.type == 'node_var' || node.type == 'node_const') {

                node.isMath = true;
            }
            if (node.children) {
                len = node.children.length;
                for (i = 0; i < len; ++i) {
                    this.setMath(node.children[i]);
                }
            }
        },

        deriveElementary: function (node, varname) {
            var fun = node.children[0].value,
                arg = node.children[1],
                newNode;


            switch (fun) {
                case 'abs':
                    // x / sqrt(x * x)
                    newNode = this.createNode('node_op', 'op_div',
                        arg[0],
                        this.createNode('node_op', 'op_execfun',
                            this.createNode('node_var', 'sqrt'),
                            [this.createNode('node_op', 'op_mul',
                                Type.deepCopy(arg[0]),
                                Type.deepCopy(arg[0])
                            )]
                        )
                    );
                    break;

                case 'sqrt':
                    newNode = this.createNode('node_op', 'op_div',
                        this.createNode('node_const', 1.0),
                        this.createNode('node_op', 'op_mul',
                            this.createNode('node_const', 2.0),
                            this.createNode(node.type, node.value,
                                Type.deepCopy(node.children[0]),
                                Type.deepCopy(node.children[1])
                            )
                        )
                    );
                    break;

                case 'sin':
                    newNode = this.createNode('node_op', 'op_execfun',
                        this.createNode('node_var', 'cos'),
                        Type.deepCopy(arg)
                    );
                    break;

                case 'cos':
                    newNode = this.createNode('node_op', 'op_neg',
                        this.createNode('node_op', 'op_execfun',
                            this.createNode('node_var', 'sin'),
                            Type.deepCopy(arg)
                        )
                    );
                    break;

                case 'tan':
                    newNode = this.createNode('node_op', 'op_div',
                        this.createNode('node_const', 1.0),
                        this.createNode('node_op', 'op_exp',
                            this.createNode('node_op', 'op_execfun',
                                this.createNode('node_var', 'cos'),
                                Type.deepCopy(arg)
                            ),
                            this.createNode('node_const', 2)
                        )
                    );
                    break;

                case 'cot':
                    newNode = this.createNode('node_op', 'op_neg',
                        this.createNode('node_op', 'op_div',
                            this.createNode('node_const', 1.0),
                            this.createNode('node_op', 'op_exp',
                                this.createNode('node_op', 'op_execfun',
                                    this.createNode('node_var', 'sin'),
                                    Type.deepCopy(arg)
                                ),
                                this.createNode('node_const', 2)
                            )
                        )
                    );
                    break;

                case 'exp':
                    newNode = this.createNode(node.type, node.value,
                        Type.deepCopy(node.children[0]),
                        Type.deepCopy(node.children[1])
                    );
                    break;

                case 'pow':
                    // (f^g)' = f^g*(f'g/f + g' log(f))
                    newNode = this.createNode('node_op', 'op_mul',
                        this.createNode('node_op', 'op_execfun',
                            Type.deepCopy(node.children[0]),
                            Type.deepCopy(node.children[1])
                        ),
                        this.createNode('node_op', 'op_add',
                            this.createNode('node_op', 'op_mul',
                                this.derivative(node.children[1][0], varname),
                                this.createNode('node_op', 'op_div',
                                    Type.deepCopy(node.children[1][1]),
                                    Type.deepCopy(node.children[1][0])
                                )
                            ),
                            this.createNode('node_op', 'op_mul',
                                this.derivative(node.children[1][1], varname),
                                this.createNode('node_op', 'op_execfun',
                                    this.createNode('node_var', 'log'),
                                    [Type.deepCopy(node.children[1][0])]
                                )
                            )
                        )
                    );
                    break;

                case 'log':
                case 'ln':
                    newNode = this.createNode('node_op', 'op_div',
                        this.createNode('node_const', 1.0),
                        // Attention: single variable mode
                        Type.deepCopy(arg[0])
                    );
                    break;

                case 'log2':
                case 'lb':
                case 'ld':
                    newNode = this.createNode('node_op', 'op_mul',
                        this.createNode('node_op', 'op_div',
                            this.createNode('node_const', 1.0),
                            // Attention: single variable mode
                            Type.deepCopy(arg[0])
                        ),
                        this.createNode('node_const', 1.4426950408889634)  // 1/log(2)
                    );
                    break;

                case 'log10':
                case 'lg':
                    newNode = this.createNode('node_op', 'op_mul',
                        this.createNode('node_op', 'op_div',
                            this.createNode('node_const', 1.0),
                            // Attention: single variable mode
                            Type.deepCopy(arg[0])
                        ),
                        this.createNode('node_const', 0.43429448190325176)  // 1/log(10)
                    );
                    break;

                case 'asin':
                    newNode = this.createNode('node_op', 'op_div',
                        this.createNode('node_const', 1.0),
                        this.createNode('node_op', 'op_execfun',
                            this.createNode('node_var', 'sqrt'),
                            [
                                this.createNode('node_op', 'op_sub',
                                    this.createNode('node_const', 1.0),
                                    this.createNode('node_op', 'op_mul',
                                        Type.deepCopy(arg[0]),
                                        Type.deepCopy(arg[0])
                                    )
                                )
                            ]
                        )
                    );
                    break;

                case 'acos':
                    newNode = this.createNode('node_op', 'op_neg',
                        this.createNode('node_op', 'op_div',
                            this.createNode('node_const', 1.0),
                            this.createNode('node_op', 'op_execfun',
                                this.createNode('node_var', 'sqrt'),
                                [
                                    this.createNode('node_op', 'op_sub',
                                        this.createNode('node_const', 1.0),
                                        this.createNode('node_op', 'op_mul',
                                            Type.deepCopy(arg[0]),
                                            Type.deepCopy(arg[0])
                                        )
                                    )
                                ]
                            )
                        )
                    );
                    break;

                //case 'atan2':

                case 'atan':
                    newNode = this.createNode('node_op', 'op_div',
                        this.createNode('node_const', 1.0),
                        this.createNode('node_op', 'op_add',
                            this.createNode('node_const', 1.0),
                            this.createNode('node_op', 'op_mul',
                                Type.deepCopy(arg[0]),
                                Type.deepCopy(arg[0])
                            )
                        )
                    );
                    break;

                case 'acot':
                    newNode = this.createNode('node_op', 'op_neg',
                        this.createNode('node_op', 'op_div',
                            this.createNode('node_const', 1.0),
                            this.createNode('node_op', 'op_add',
                                this.createNode('node_const', 1.0),
                                this.createNode('node_op', 'op_mul',
                                    Type.deepCopy(arg[0]),
                                    Type.deepCopy(arg[0])
                                )
                            )
                        )
                    );
                    break;

                case 'sinh':
                    newNode = this.createNode('node_op', 'op_execfun',
                        this.createNode('node_var', 'cosh'),
                        [Type.deepCopy(arg[0])]
                    );
                    break;

                case 'cosh':
                    newNode = this.createNode('node_op', 'op_execfun',
                        this.createNode('node_var', 'sinh'),
                        [Type.deepCopy(arg[0])]
                    );
                    break;

                case 'tanh':
                    newNode = this.createNode('node_op', 'op_sub',
                        this.createNode('node_const', 1.0),
                        this.createNode('node_op', 'op_exp',
                            this.createNode('node_op', 'op_execfun',
                                this.createNode('node_var', 'tanh'),
                                [Type.deepCopy(arg[0])]
                            ),
                            this.createNode('node_const', 2.0)
                        )
                    );
                    break;

                case 'asinh':
                    newNode = this.createNode('node_op', 'op_div',
                        this.createNode('node_const', 1.0),
                        this.createNode('node_op', 'op_execfun',
                            this.createNode('node_var', 'sqrt'),
                            [
                                this.createNode('node_op', 'op_add',
                                    this.createNode('node_op', 'op_mul',
                                        Type.deepCopy(arg[0]),
                                        Type.deepCopy(arg[0])
                                    ),
                                    this.createNode('node_const', 1.0)
                                )
                            ]
                        )
                    );
                    break;

                case 'acosh':
                    newNode = this.createNode('node_op', 'op_div',
                        this.createNode('node_const', 1.0),
                        this.createNode('node_op', 'op_execfun',
                            this.createNode('node_var', 'sqrt'),
                            [
                                this.createNode('node_op', 'op_sub',
                                    this.createNode('node_op', 'op_mul',
                                        Type.deepCopy(arg[0]),
                                        Type.deepCopy(arg[0])
                                    ),
                                    this.createNode('node_const', 1.0)
                                )
                            ]
                        )
                    );
                    break;

                case 'atanh':
                    newNode = this.createNode('node_op', 'op_div',
                        this.createNode('node_const', 1.0),
                        this.createNode('node_op', 'op_sub',
                            this.createNode('node_const', 1.0),
                            this.createNode('node_op', 'op_mul',
                                Type.deepCopy(arg[0]),
                                Type.deepCopy(arg[0])
                            )
                        )
                    );
                    break;

                default:
                    newNode = this.createNode('node_const', 0.0);
                    console.log('Derivative of "' + fun + '" not yet implemented');
                    throw new Error('Error(' + this.line + '): ');
                //  this._error('Derivative of "' + fun + '" not yet implemented');

            }

            return newNode;
        },

        derivative: function (node, varname) {
            var newNode;

            switch (node.type) {
                case 'node_op':
                    switch (node.value) {
                        /*
                        case 'op_map':
                            if (true) {
                                newNode = this.createNode('node_op', 'op_map',
                                        Type.deepCopy(node.children[0]),
                                        this.derivative(node.children[1], varname)
                                    );
                            } else {
                                newNode = this.derivative(node.children[1], varname);
                            }
                            break;
                        */
                        case 'op_execfun':
                            // f'(g(x))g'(x)
                            if (node.children[0].value == 'pow') {
                                newNode = this.deriveElementary(node, varname);
                            } else {
                                if (node.children[1].length === 0) {
                                    newNode = this.createNode('node_const', 0.0);
                                } else {
                                    newNode = this.createNode('node_op', 'op_mul',
                                        this.deriveElementary(node, varname),
                                        // Warning: single variable mode
                                        this.derivative(node.children[1][0], varname)
                                    );
                                }
                            }
                            break;

                        case 'op_div':
                            // (f'g − g'f )/(g*g)
                            newNode = this.createNode('node_op', 'op_div',
                                this.createNode('node_op', 'op_sub',
                                    this.createNode('node_op', 'op_mul',
                                        this.derivative(node.children[0], varname),
                                        Type.deepCopy(node.children[1])
                                    ),
                                    this.createNode('node_op', 'op_mul',
                                        Type.deepCopy(node.children[0]),
                                        this.derivative(node.children[1], varname)
                                    )
                                ),
                                this.createNode('node_op', 'op_mul',
                                    Type.deepCopy(node.children[1]),
                                    Type.deepCopy(node.children[1])
                                )
                            );
                            break;

                        case 'op_mul':
                            // fg' + f'g
                            newNode = this.createNode('node_op', 'op_add',
                                this.createNode('node_op', 'op_mul',
                                    Type.deepCopy(node.children[0]),
                                    this.derivative(node.children[1], varname)),
                                this.createNode('node_op', 'op_mul',
                                    this.derivative(node.children[0], varname),
                                    Type.deepCopy(node.children[1]))
                            );
                            break;

                        case 'op_neg':
                            newNode = this.createNode('node_op', 'op_neg',
                                this.derivative(node.children[0], varname)
                            );
                            break;

                        case 'op_add':
                        case 'op_sub':
                            newNode = this.createNode('node_op', node.value,
                                this.derivative(node.children[0], varname),
                                this.derivative(node.children[1], varname)
                            );
                            break;

                        case 'op_exp':
                            // (f^g)' = f^g*(f'g/f + g' log(f))
                            newNode = this.createNode('node_op', 'op_mul',
                                Type.deepCopy(node),
                                this.createNode('node_op', 'op_add',
                                    this.createNode('node_op', 'op_mul',
                                        this.derivative(node.children[0], varname),
                                        this.createNode('node_op', 'op_div',
                                            Type.deepCopy(node.children[1]),
                                            Type.deepCopy(node.children[0])
                                        )
                                    ),
                                    this.createNode('node_op', 'op_mul',
                                        this.derivative(node.children[1], varname),
                                        this.createNode('node_op', 'op_execfun',
                                            this.createNode('node_var', 'log'),
                                            [Type.deepCopy(node.children[0])]
                                        )
                                    )
                                )
                            );
                            break;
                    }
                    break;

                case 'node_var':
                    //console.log('node_var', node);
                    if (node.value === varname) {
                        newNode = this.createNode('node_const', 1.0);
                    } else {
                        newNode = this.createNode('node_const', 0.0);
                    }
                    break;

                case 'node_const':
                    newNode = this.createNode('node_const', 0.0);
                    break;

                case 'node_const_bool':
                    break;

                case 'node_str':
                    break;

            }

            return newNode;
        },

        /**
         * f = map (x) -> x*sin(x);
         * Usages:
         * h = D(f, x);
         * h = map (x) -> D(f, x);
         *
         */
        expandDerivatives: function (node, parent, ast) {
            var len, i, j, mapNode, codeNode, ret, node2, newNode,
                mapName, varname, vArray, order;

            ret = 0;
            if (!node) {
                return ret;
            }

            this.line = node.line;
            this.col = node.col;

            // First we have to go down in the tree.
            // This ensures that in cases like D(D(f,x),x) the inner D is expanded first.
            len = node.children.length;
            for (i = 0; i < len; ++i) {
                if (node.children[i] && node.children[i].type) {
                    node.children[i] = this.expandDerivatives(node.children[i], node, ast);
                } else if (Type.isArray(node.children[i])) {
                    for (j = 0; j < node.children[i].length; ++j) {
                        if (node.children[i][j] && node.children[i][j].type) {
                            node.children[i][j] = this.expandDerivatives(node.children[i][j], node, ast);
                        }
                    }
                }
            }

            switch (node.type) {
                case 'node_op':
                    switch (node.value) {
                        case 'op_execfun':
                            if (node.children[0] && node.children[0].value === 'D') {
                                if (node.children[1][0].type == 'node_var') {
                                    /*
                                     * Derive map, that is compute D(f,x)
                                     * where e.g. f = map (x) -> x^2
                                     *
                                     * First step: find node where the map is defined
                                     */
                                    mapName = node.children[1][0].value;
                                    mapNode = this.findMapNode(mapName, ast);
                                    vArray = mapNode.children[0];

                                    // Variable name for differentiation
                                    if (node.children[1].length >= 2) {
                                        varname = node.children[1][1].value;
                                    } else {
                                        varname = mapNode.children[0][0]; // Usually it's 'x'
                                    }
                                    codeNode = mapNode.children[1];
                                } else {
                                    /*
                                     * Derive expression, e.g.
                                     *     D(2*x, x)
                                     */
                                    codeNode = node.children[1][0];
                                    vArray = ['x'];

                                    // Variable name for differentiation and order
                                    if (node.children[1].length >= 2) {
                                        varname = node.children[1][1].value;
                                    } else {
                                        varname = 'x';
                                    }
                                }

                                // Differentiation order
                                if (node.children[1].length >= 3) {
                                    order = node.children[1][2].value;
                                } else {
                                    order = 1;
                                }

                                // Create node which contains the derivative
                                newNode = codeNode;
                                //newNode = this.removeTrivialNodes(newNode);
                                if (order >= 1) {
                                    while (order >= 1) {
                                        newNode = this.derivative(newNode, varname);
                                        newNode = this.removeTrivialNodes(newNode);
                                        order--;
                                    }
                                }

                                // Replace the node containing e.g. D(f,x) by the derivative.
                                if (parent.type == 'node_op' && parent.value == 'op_assign') {
                                    // If D is an assignment it has to be replaced by a map
                                    // h = D(f, x)
                                    node2 = this.createNode('node_op', 'op_map',
                                        vArray,
                                        newNode
                                    );
                                } else {
                                    node2 = newNode;
                                }

                                this.setMath(node2);
                                node.type = node2.type;
                                node.value = node2.value;
                                node.children[0] = node2.children[0];
                                node.children[1] = node2.children[1];
                            }
                    }
                    break;

                case 'node_var':
                case 'node_const':
                case 'node_const_bool':
                case 'node_str':
                    break;
            }

            return node;
        },

        removeTrivialNodes: function (node) {
            var i, len, n0, n1, swap;

            // In case of 'op_execfun' the children[1] node is an array.
            if (Type.isArray(node)) {
                len = node.length;
                for (i = 0; i < len; ++i) {
                    node[i] = this.removeTrivialNodes(node[i]);
                }
            }
            if (node.type != 'node_op' || !node.children) {
                return node;
            }

            len = node.children.length;
            for (i = 0; i < len; ++i) {
                this.mayNotBeSimplified = false;
                do {
                    node.children[i] = this.removeTrivialNodes(node.children[i]);
                } while (this.mayNotBeSimplified);

            }

            switch (node.value) {
                // Allow maps of the form
                //  map (x) -> x;
                case 'op_map':
                    n0 = node.children[0];
                    n1 = node.children[1];
                    if (n1.type == 'node_var') {
                        for (i = 0; i < n0.length; ++i) {
                            // Allow maps of the form map(x) -> x
                            if (n0[i] == n1.value) {
                                n1.isMath = true;
                                break;
                            }
                        }
                    }
                    break;

                // a + 0 -> a
                // 0 + a -> a
                case 'op_add':
                    n0 = node.children[0];
                    n1 = node.children[1];
                    if (n0.type == 'node_const' && n0.value === 0.0) {
                        return n1;
                    }
                    if (n1.type == 'node_const' && n1.value === 0.0) {
                        return n0;
                    }

                    // const + const -> const
                    if (n0.type == 'node_const' && n1.type == 'node_const') {
                        n0.value += n1.value;
                        return n0;
                    }
                    break;

                // 1 * a = a
                // a * 1 = a
                // a * 0 = 0
                // 0 * a = 0
                // - * - = +
                // Order children
                case 'op_mul':
                    n0 = node.children[0];
                    n1 = node.children[1];
                    if (n0.type == 'node_const' && n0.value == 1.0) {
                        return n1;
                    }
                    if (n1.type == 'node_const' && n1.value == 1.0) {
                        return n0;
                    }
                    if (n0.type == 'node_const' && n0.value === 0.0) {
                        return n0;
                    }
                    if (n1.type == 'node_const' && n1.value === 0.0) {
                        return n1;
                    }
                    if (n1.type == 'node_const' && n1.value === 0.0) {
                        return n1;
                    }

                    // (-a) * (-b) -> a*b
                    if (n0.type == 'node_op' && n0.value == 'op_neg' &&
                        n1.type == 'node_op' && n1.value == 'op_neg') {
                        node.children = [n0.children[0], n1.children[0]];
                        this.mayNotBeSimplified = true;
                        return node;
                    }
                    // (-a) * b -> -(a*b)
                    if (n0.value == 'op_neg' && n1.value != 'op_neg') {
                        node.type = 'node_op';
                        node.value = 'op_neg';
                        node.children = [this.createNode('node_op', 'op_mul', n0.children[0], n1)];
                        this.mayNotBeSimplified = true;
                        return node;
                    }
                    // a * (-b) -> -(a*b)
                    if (n0.value != 'op_neg' && n1.value == 'op_neg') {
                        node.type = 'node_op';
                        node.value = 'op_neg';
                        node.children = [this.createNode('node_op', 'op_mul', n0, n1.children[0])];
                        this.mayNotBeSimplified = true;
                        return node;
                    }
                    // (1 / a) * b -> a / b
                    if (n0.value == 'op_div' &&
                        n0.children[0].type == 'node_const' && n0.children[0].value == 1.0) {
                        node.type = 'node_op';
                        node.value = 'op_div';
                        node.children = [n1, n0.children[1]];
                        this.mayNotBeSimplified = true;
                        return node;
                    }
                    // a * (1 / b) -> a / b
                    if (n1.value == 'op_div' &&
                        n1.children[0].type == 'node_const' && n1.children[0].value == 1.0) {
                        node.type = 'node_op';
                        node.value = 'op_div';
                        node.children = [n0, n1.children[1]];
                        this.mayNotBeSimplified = true;
                        return node;
                    }

                    // Order children
                    // a * const -> const * a
                    if (n0.type != 'node_const' && n1.type == 'node_const') {
                        node.children = [n1, n0];
                        this.mayNotBeSimplified = true;
                        return node;
                    }
                    // a + (-const) -> -const * a
                    if (n0.type != 'node_const' && n1.type == 'node_op' &&
                        n1.value == 'op_neg' && n1.children[0].type == 'node_const') {
                        node.children = [n1, n0];
                        this.mayNotBeSimplified = true;
                        return node;
                    }

                    // a * var -> var * a
                    // a * fun -> fun * a
                    if (n0.type == 'node_op' && n0.value != 'op_execfun' &&
                        (n1.type == 'node_var' || (n1.type == 'node_op' && n1.value == 'op_execfun'))) {
                        node.children = [n1, n0];
                        this.mayNotBeSimplified = true;
                        return node;
                    }

                    // a + (-var) -> -var * a
                    if (n0.type != 'node_op' && n1.type == 'node_op' &&
                        n1.value == 'op_neg' && n1.children[0].type == 'node_var') {
                        node.children = [n1, n0];
                        this.mayNotBeSimplified = true;
                        return node;
                    }
                    // a * (const * b) -> const * (a*b)
                    // a * (const / b) -> const * (a/b)
                    if (n0.type != 'node_const' && n1.type == 'node_op' &&
                        (n1.value == 'op_mul' || n1.value == 'op_div') &&
                        n1.children[0].type == 'node_const') {
                        swap = n1.children[0];
                        n1.children[0] = n0;
                        node.children = [swap, n1];
                        this.mayNotBeSimplified = true;
                        return node;
                    }

                    // (const * a) * b -> const * (a * b)
                    if (n1.type != 'node_const' && n0.type == 'node_op' &&
                        n0.value == 'op_mul' &&
                        n0.children[0].type == 'node_const') {
                        node.children = [
                            n0.children[0],
                            this.createNode('node_op', 'op_mul', n0.children[1], n1)
                        ];
                        this.mayNotBeSimplified = true;
                        return node;
                    }

                    // const * const -> const
                    if (n0.type == 'node_const' && n1.type == 'node_const') {
                        n0.value *= n1.value;
                        return n0;
                    }

                    // const * (const * a) -> const * a
                    // const * (const / a) -> const / a
                    if (n0.type == 'node_const' && n1.type == 'node_op' &&
                        (n1.value == 'op_mul' || n1.value == 'op_div') &&
                        n1.children[0].type == 'node_const') {
                        n1.children[0].value *= n0.value;
                        return n1;
                    }

                    // a * a-> a^2
                    n0.hash = this.parser.compile(n0);
                    n1.hash = this.parser.compile(n1);
                    if (n0.hash === n1.hash) {
                        node.value = 'op_exp';
                        node.children[1] = this.createNode('node_const', 2.0);
                        return node;
                    }

                    if (n0.type == 'node_const' && n1.type == 'node_op' &&
                        (n1.value == 'op_mul' || n1.value == 'op_div') &&
                        n1.children[0].type == 'node_const') {
                        n1.children[0].value *= n0.value;
                        return n1;
                    }

                    // a * a^b -> a^(b+1)
                    if (n1.type == 'node_op' && n1.value == 'op_exp') {
                        if (!n0.hash) {
                            n0.hash = this.parser.compile(n0);
                        }
                        if (!n1.children[0].hash) {
                            n1.children[0].hash = this.parser.compile(n1.children[0]);
                        }
                        if (n0.hash === n1.children[0].hash) {
                            n1.children[1] = this.createNode('node_op', 'op_add',
                                n1.children[1],
                                this.createNode('node_const', 1.0)
                            );
                            this.mayNotBeSimplified = true;
                            return n1;
                        }
                    }

                    // a^b * a^c -> a^(b+c)
                    if (n0.type == 'node_op' && n0.value == 'op_exp' &&
                        n1.type == 'node_op' && n1.value == 'op_exp') {
                        n0.children[0].hash = this.parser.compile(n0.children[0]);
                        n1.children[0].hash = this.parser.compile(n1.children[0]);
                        if (n0.children[0].hash === n1.children[0].hash) {
                            n0.children[1] = this.createNode('node_op', 'op_add',
                                n0.children[1],
                                n1.children[1]
                            );
                            this.mayNotBeSimplified = true;
                            return n0;
                        }
                    }

                    break;

                // 0 - a -> -a
                // a - 0 -> a
                // a - a -> 0
                case 'op_sub':
                    n0 = node.children[0];
                    n1 = node.children[1];
                    if (n0.type == 'node_const' && n0.value === 0.0) {
                        node.value = 'op_neg';
                        node.children[0] = n1;
                        return node;
                    }
                    if (n1.type == 'node_const' && n1.value === 0.0) {
                        return n0;
                    }
                    if (n0.type == 'node_const' && n1.type == 'node_const' &&
                        n0.value == n1.value) {
                        return this.createNode('node_const', 0.0);
                    }
                    if (n0.type == 'node_var' && n1.type == 'node_var' &&
                        n0.value == n1.value) {
                        return this.createNode('node_const', 0.0);
                    }

                    // const - const -> const
                    if (n0.type == 'node_const' && n1.type == 'node_const') {
                        n0.value -= n1.value;
                        return n0;
                    }

                    // const * a - const * a -> const * a
                    if (n0.type == 'node_op' && n0.value == 'op_mul' &&
                        n1.type == 'node_op' && n1.value == 'op_mul') {

                        n0.children[1].hash = this.parser.compile(n0.children[1]);
                        n1.children[1].hash = this.parser.compile(n1.children[1]);
                        if (n0.children[1].hash === n1.children[1].hash) {

                            node.value = 'op_mul';
                            node.children = [
                                this.createNode('node_op', 'op_sub',
                                    n0.children[0],
                                    n1.children[0]),
                                n0.children[1]
                            ];
                            this.mayNotBeSimplified = true;
                            return node;
                        }
                    }
                    // const * a - a -> (const - 1) * a
                    if (n0.type == 'node_op' && n0.value == 'op_mul') {

                        n0.children[1].hash = this.parser.compile(n0.children[1]);
                        n1.hash = this.parser.compile(n1);
                        if (n0.children[1].hash === n1.hash) {

                            node.value = 'op_mul';
                            node.children = [
                                this.createNode('node_op', 'op_sub',
                                    n0.children[0],
                                    this.createNode('node_const', 1.0)),
                                n1
                            ];
                            this.mayNotBeSimplified = true;
                            return node;
                        }
                    }
                    // a - const*a -> (const - 1) * a
                    if (n1.type == 'node_op' && n1.value == 'op_mul') {

                        n1.children[1].hash = this.parser.compile(n1.children[1]);
                        n0.hash = this.parser.compile(n0);
                        if (n1.children[1].hash === n0.hash) {

                            node.value = 'op_mul';
                            node.children = [
                                this.createNode('node_op', 'op_sub',
                                    this.createNode('node_const', 1.0),
                                    n1.children[0]),
                                n0
                            ];
                            this.mayNotBeSimplified = true;
                            return node;
                        }
                    }

                    break;

                // -0 -> 0
                // -(-b) = b
                case 'op_neg':
                    n0 = node.children[0];
                    if (n0.type == 'node_const' && n0.value === 0.0) {
                        return n0;
                    }
                    if (n0.type == 'node_op' && n0.value == 'op_neg') {
                        return n0.children[0];
                    }
                    break;

                // a / a -> 1, a != 0
                // 0 / a -> 0, a != 0
                // a / 0 -> Infinity, a != 0
                // 0 / 0 -> NaN, a == 0
                case 'op_div':
                    n0 = node.children[0];
                    n1 = node.children[1];
                    if (n0.type == 'node_const' && n1.type == 'node_const' &&
                        n0.value == n1.value && n0.value !== 0) {
                        n0.value = 1.0;
                        return n0;
                    }
                    if (n0.type == 'node_const' && n0.value === 0 &&
                        n1.type == 'node_const' && n1.value !== 0) {
                        n0.value = 0.0;
                        return n0;
                    }

                    // Risky: 0 / (something != 0) -> 0.0
                    if (n0.type == 'node_const' && n0.value === 0 &&
                        (n1.type == 'node_op' || n1.type == 'node_var')) {
                        node.type = 'node_const';
                        node.value = 0.0;
                        return node;
                    }

                    if (n0.type == 'node_var' && n1.type == 'node_var' &&
                        n0.value == n1.value) {
                        return this.createNode('node_const', 1.0);
                    }
                    if (n0.type == 'node_const' && n0.value !== 0 &&
                        n1.type == 'node_const' && n1.value === 0) {
                        if (n0.value > 0.0) {
                            n0.value = Infinity;
                        } else {
                            n0.value = -Infinity; // Do we ever need this?
                        }
                        return n0;
                    }

                    // (-a) / (-b) -> a/b
                    if (n0.type == 'node_op' && n0.value == 'op_neg' &&
                        n1.type == 'node_op' && n1.value == 'op_neg') {
                        node.children = [n0.children[0], n1.children[0]];
                        this.mayNotBeSimplified = true;
                        return node;
                    }
                    // (-a) / b -> -(a/b)
                    if (n0.value == 'op_neg' && n1.value != 'op_neg') {
                        node.type = 'node_op';
                        node.value = 'op_neg';
                        node.children = [this.createNode('node_op', 'op_div', n0.children[0], n1)];
                        this.mayNotBeSimplified = true;
                        return node;
                    }
                    // a / (-b) -> -(a/b)
                    if (n0.value != 'op_neg' && n1.value == 'op_neg') {
                        node.type = 'node_op';
                        node.value = 'op_neg';
                        node.children = [this.createNode('node_op', 'op_div', n0, n1.children[0])];
                        this.mayNotBeSimplified = true;
                        return node;
                    }

                    // a^b / a -> a^(b-1)
                    if (n0.type == 'node_op' && n0.value == 'op_exp') {
                        if (!n1.hash) {
                            n1.hash = this.parser.compile(n1);
                        }
                        if (!n0.children[0].hash) {
                            n0.children[0].hash = this.parser.compile(n0.children[0]);
                        }
                        if (n1.hash === n0.children[0].hash) {
                            n0.children[1] = this.createNode('node_op', 'op_sub',
                                n0.children[1],
                                this.createNode('node_const', 1.0)
                            );
                            this.mayNotBeSimplified = true;
                            return n0;
                        }
                    }

                    // (const * a) / b -> const * (a / b)
                    if (n1.type != 'node_const' && n0.type == 'node_op' &&
                        n0.value == 'op_mul' &&
                        n0.children[0].type == 'node_const') {
                        node.value = 'op_mul';
                        node.children = [
                            n0.children[0],
                            this.createNode('node_op', 'op_div', n0.children[1], n1)
                        ];
                        this.mayNotBeSimplified = true;
                        return node;
                    }

                    // a^b / a^c -> a^(b-c)
                    if (n0.type == 'node_op' && n0.value == 'op_exp' &&
                        n1.type == 'node_op' && n1.value == 'op_exp') {
                        n0.children[0].hash = this.parser.compile(n0.children[0]);
                        n1.children[0].hash = this.parser.compile(n1.children[0]);
                        if (n0.children[0].hash === n1.children[0].hash) {
                            n0.children[1] = this.createNode('node_op', 'op_sub',
                                n0.children[1],
                                n1.children[1]
                            );
                            this.mayNotBeSimplified = true;
                            return n0;
                        }
                    }


                    break;

                // a^0 = 1
                // a^1 -> a
                // 1^a -> 1
                // 0^a -> 0: a const != 0
                case 'op_exp':
                    n0 = node.children[0];
                    n1 = node.children[1];
                    if (n1.type == 'node_const' && n1.value === 0.0) {
                        n1.value = 1.0;
                        return n1;
                    }
                    if (n1.type == 'node_const' && n1.value == 1.0) {
                        return n0;
                    }
                    if (n0.type == 'node_const' && n0.value == 1.0) {
                        return n0;
                    }
                    if (n0.type == 'node_const' && n0.value === 0.0 &&
                        n1.type == 'node_const' && n1.value !== 0.0) {
                        return n0;
                    }

                    // (a^b)^c -> a^(b*c)
                    if (n0.type == 'node_op' && n0.value == 'op_exp') {
                        node.children = [
                            n0.children[0],
                            this.createNode('node_op', 'op_mul',
                                n0.children[1],
                                n1)
                        ];
                        return node;
                    }
                    break;
            }

            switch (node.value) {
                // const_1 + const_2 -> (const_1 + const_2)
                // a + a -> 2*a
                // a + (-b) = a - b
                case 'op_add':
                    n0 = node.children[0];
                    n1 = node.children[1];
                    if (n0.type == 'node_const' && n1.type == 'node_const' &&
                        n0.value == n1.value) {
                        n0.value += n1.value;
                        return n0;
                    }

                    if (n0.type == 'node_var' && n1.type == 'node_var' &&
                        n0.value == n1.value) {
                        node.children[0] = this.createNode('node_const', 2.0);
                        node.value = 'op_mul';
                        return node;
                    }

                    if (n0.type == 'node_op' && n0.value == 'op_neg') {
                        node.value = 'op_sub';
                        node.children[0] = n1;
                        node.children[1] = n0.children[0];
                        this.mayNotBeSimplified = true;
                        return node;
                    }

                    if (n1.type == 'node_op' && n1.value == 'op_neg') {
                        node.value = 'op_sub';
                        node.children[1] = n1.children[0];
                        this.mayNotBeSimplified = true;
                        return node;
                    }

                    // const * a + const * a -> const * a
                    if (n0.type == 'node_op' && n0.value == 'op_mul' &&
                        n1.type == 'node_op' && n1.value == 'op_mul') {

                        n0.children[1].hash = this.parser.compile(n0.children[1]);
                        n1.children[1].hash = this.parser.compile(n1.children[1]);
                        if (n0.children[1].hash === n1.children[1].hash) {

                            node.value = 'op_mul';
                            node.children = [
                                this.createNode('node_op', 'op_add',
                                    n0.children[0],
                                    n1.children[0]),
                                n0.children[1]
                            ];
                            this.mayNotBeSimplified = true;
                            return node;
                        }
                    }
                    // const * a + a -> (const + 1) * a
                    if (n0.type == 'node_op' && n0.value == 'op_mul') {

                        n0.children[1].hash = this.parser.compile(n0.children[1]);
                        n1.hash = this.parser.compile(n1);
                        if (n0.children[1].hash === n1.hash) {

                            node.value = 'op_mul';
                            node.children = [
                                this.createNode('node_op', 'op_add',
                                    n0.children[0],
                                    this.createNode('node_const', 1.0)),
                                n1
                            ];
                            this.mayNotBeSimplified = true;
                            return node;
                        }
                    }
                    // a + const*a -> (const + 1) * a
                    if (n1.type == 'node_op' && n1.value == 'op_mul') {

                        n1.children[1].hash = this.parser.compile(n1.children[1]);
                        n0.hash = this.parser.compile(n0);
                        if (n1.children[1].hash === n0.hash) {

                            node.value = 'op_mul';
                            node.children = [
                                this.createNode('node_op', 'op_add',
                                    this.createNode('node_const', 1.0),
                                    n1.children[0]),
                                n0
                            ];
                            this.mayNotBeSimplified = true;
                            return node;
                        }
                    }

                    break;

                // a - (-b) = a + b
                case 'op_sub':
                    n0 = node.children[0];
                    n1 = node.children[1];
                    if (n1.type == 'node_op' && n1.value == 'op_neg') {
                        node.value = 'op_add';
                        node.children[1] = n1.children[0];
                        this.mayNotBeSimplified = true;
                        return node;
                    }
                    break;

                case 'op_execfun':
                    return this.simplifyElementary(node);
            }

            return node;
        },

        simplifyElementary: function (node) {
            var fun = node.children[0].value,
                arg = node.children[1];

            // Catch errors of the form sin()
            if (arg.length == 0) {
                return node;
            }

            switch (fun) {
                // sin(0) -> 0
                // sin(PI) -> 0
                // sin (int * PI) -> 0
                // sin (PI * int) -> 0
                // Same for tan()
                case 'sin':
                case 'tan':
                    if (arg[0].type == 'node_const' && arg[0].value === 0) {
                        node.type = 'node_const';
                        node.value = 0.0;
                        return node;
                    }
                    if (arg[0].type == 'node_var' && arg[0].value == 'PI') {
                        node.type = 'node_const';
                        node.value = 0.0;
                        return node;
                    }
                    if (arg[0].type == 'node_op' && arg[0].value == 'op_mul' &&
                        arg[0].children[0].type == 'node_const' && arg[0].children[0].value % 1 === 0 &&
                        arg[0].children[1].type == 'node_var' && arg[0].children[1].value == 'PI') {
                        node.type = 'node_const';
                        node.value = 0.0;
                        return node;
                    }
                    break;

                // cos(0) -> 1.0
                // cos(PI) -> -1.0
                // cos(int * PI) -> +/- 1.0
                // cos(PI * int) -> +/- 1.0
                case 'cos':
                    if (arg[0].type == 'node_const' && arg[0].value === 0) {
                        node.type = 'node_const';
                        node.value = 1.0;
                        return node;
                    }
                    if (arg[0].type == 'node_var' && arg[0].value == 'PI') {
                        node.type = 'node_op';
                        node.value = 'op_neg';
                        node.children = [this.createNode('node_const', 1.0)];
                        return node;
                    }
                    /*
                    if (arg[0].type == 'node_op' && arg[0].value == 'op_mul' &&
                        ((arg[0].children[0].type == 'node_const' && arg[0].children[0].value % 1 === 0 &&
                         arg[0].children[1].type == 'node_var' && arg[0].children[1].value == 'PI') ||
                         (arg[0].children[1].type == 'node_const' && arg[0].children[1].value % 1 === 0 &&
                          arg[0].children[0].type == 'node_var' && arg[0].children[0].value == 'PI'))) {
                        node.type = 'node_const';
                        node.value = 1.0;
                        return node;
                    }
                    */
                    break;

                // exp(0) -> 1
                case 'exp':
                    if (arg[0].type == 'node_const' && arg[0].value === 0) {
                        node.type = 'node_const';
                        node.value = 1.0;
                        return node;
                    }
                    break;

                // pow(a, 0) -> 1
                case 'pow':
                    if (arg[1].type == 'node_const' && arg[1].value === 0) {
                        node.type = 'node_const';
                        node.value = 1.0;
                        return node;
                    }
                    break;

            }

            return node;
        }

    });

    return JXG.CA;
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/type
 */

/**
 * @fileoverview The JXG.Dump namespace provides methods to save a board to javascript.
 */

define('utils/dump',['jxg', 'utils/type'], function (JXG, Type) {

    "use strict";

    /**
     * The JXG.Dump namespace provides classes and methods to save a board to javascript.
     * @namespace
     */
    JXG.Dump = {

        /**
         * Adds markers to every element of the board
         * @param {JXG.Board} board
         * @param {Array|String} markers
         * @param {Array} values
         */
        addMarkers: function (board, markers, values) {
            var e, l, i;

            if (!Type.isArray(markers)) {
                markers = [markers];
            }

            if (!Type.isArray(values)) {
                values = [values];
            }

            l = Math.min(markers.length, values.length);

            markers.length = l;
            values.length = l;

            for (e in board.objects) {
                if (board.objects.hasOwnProperty(e)) {
                    for (i = 0; i < l; i++) {
                        board.objects[e][markers[i]] = values[i];
                    }
                }
            }
        },

        /**
         * Removes markers from every element on the board.
         * @param {JXG.Board} board
         * @param {Array|String} markers
         */
        deleteMarkers: function (board, markers) {
            var e, l, i;

            if (!Type.isArray(markers)) {
                markers = [markers];
            }

            l = markers.length;

            markers.length = l;

            for (e in board.objects) {
                if (board.objects.hasOwnProperty(e)) {
                    for (i = 0; i < l; i++) {
                        delete board.objects[e][markers[i]];
                    }
                }
            }
        },

        /**
         * Stringifies a string, i.e. puts some quotation marks around <tt>s</tt> if it is of type string.
         * @param {*} s
         * @returns {String} " + s + "
         */
        str: function (s) {
            if (typeof s === 'string' && s.substr(0, 7) !== 'function') {
                s = '"' + s + '"';
            }

            return s;
        },

        /**
         * Eliminate default values given by {@link JXG.Options} from the attributes object.
         * @param {Object} instance Attribute object of the element
         * @param {Object} s Arbitrary number of objects <tt>instance</tt> will be compared to. Usually these are
         * sub-objects of the {@link JXG.Board#options} structure.
         * @returns {Object} Minimal attributes object
         */
        minimizeObject: function (instance, s) {
            var p, pl, i,
                def = {},
                copy = Type.deepCopy(instance),
                defaults = [];

            for (i = 1; i < arguments.length; i++) {
                defaults.push(arguments[i]);
            }

            def = Type.deepCopy(def, JXG.Options.elements, true);
            for (i = defaults.length; i > 0; i--) {
                def = Type.deepCopy(def, defaults[i - 1], true);
            }

            for (p in def) {
                if (def.hasOwnProperty(p)) {
                    pl = p.toLowerCase();

                    if (typeof def[p] !== 'object' && def[p] === copy[pl]) {
                        // console.log("delete", p);
                        delete copy[pl];
                    }
                }
            }

            return copy;
        },

        /**
         * Prepare the attributes object for an element to be dumped as JavaScript or JessieCode code.
         * @param {JXG.Board} board
         * @param {JXG.GeometryElement} obj Geometry element which attributes object is generated
         * @returns {Object} An attributes object.
         */
        prepareAttributes: function (board, obj) {
            var a, s;

            a = this.minimizeObject(obj.getAttributes(), JXG.Options[obj.elType]);

            for (s in obj.subs) {
                if (obj.subs.hasOwnProperty(s)) {
                    a[s] = this.minimizeObject(obj.subs[s].getAttributes(),
                                                JXG.Options[obj.elType][s],
                                                JXG.Options[obj.subs[s].elType]);
                    a[s].id = obj.subs[s].id;
                    a[s].name = obj.subs[s].name;
                }
            }

            a.id = obj.id;
            a.name = obj.name;

            return a;
        },

        setBoundingBox: function(methods, board, boardVarName) {
            methods.push({
                obj: boardVarName,
                method: 'setBoundingBox',
                params: [board.getBoundingBox(), board.keepaspectratio]
            });

            return methods;
        },

        /**
         * Generate a save-able structure with all elements. This is used by {@link JXG.Dump#toJessie} and
         * {@link JXG.Dump#toJavaScript} to generate the script.
         * @param {JXG.Board} board
         * @returns {Array} An array with all metadata necessary to save the construction.
         */
        dump: function (board) {
            var e, obj, element, s,
                props = [],
                methods = [],
                elementList = [],
                len = board.objectsList.length;

            this.addMarkers(board, 'dumped', false);

            for (e = 0; e < len; e++) {
                obj = board.objectsList[e];
                element = {};

                if (!obj.dumped && obj.dump) {
                    element.type = obj.getType();
                    element.parents = obj.getParents().slice();

                    // Extract coordinates of a point
                    if (element.type === 'point' && element.parents[0] === 1) {
                        element.parents = element.parents.slice(1);
                    }

                    for (s = 0; s < element.parents.length; s++) {
                        if (Type.isString(element.parents[s]) &&
                                element.parents[s][0] !== "'" &&
                                element.parents[s][0] !== '"') {

                            element.parents[s] = '"' + element.parents[s] + '"';
                        } else if (Type.isArray( element.parents[s]) ) {
                            element.parents[s] = '[' + element.parents[s].toString() + ']';
                        }
                    }

                    element.attributes = this.prepareAttributes(board, obj);
                    if (element.type === 'glider' && obj.onPolygon) {
                        props.push({
                            obj: obj.id,
                            prop: 'onPolygon',
                            val: true
                        });
                    }

                    elementList.push(element);
                }
            }

            this.deleteMarkers(board, 'dumped');

            return {
                elements: elementList,
                props: props,
                methods: methods
            };
        },

        /**
         * Converts an array of different values into a parameter string that can be used by the code generators.
         * @param {Array} a
         * @param {function} converter A function that is used to transform the elements of <tt>a</tt>. Usually
         * {@link JXG.toJSON} or {@link JXG.Dump.toJCAN} are used.
         * @returns {String}
         */
        arrayToParamStr: function (a, converter) {
            var i,
                s = [];

            for (i = 0; i < a.length; i++) {
                s.push(converter.call(this, a[i]));
            }

            return s.join(', ');
        },

        /**
         * Converts a JavaScript object into a JCAN (JessieCode Attribute Notation) string.
         * @param {Object} obj A JavaScript object, functions will be ignored.
         * @returns {String} The given object stored in a JCAN string.
         */
        toJCAN: function (obj) {
            var i, list, prop;

            switch (typeof obj) {
            case 'object':
                if (obj) {
                    list = [];

                    if (Type.isArray(obj)) {
                        for (i = 0; i < obj.length; i++) {
                            list.push(this.toJCAN(obj[i]));
                        }

                        return '[' + list.join(',') + ']';
                    }

                    for (prop in obj) {
                        if (obj.hasOwnProperty(prop)) {
                            list.push(prop + ': ' + this.toJCAN(obj[prop]));
                        }
                    }

                    return '<<' + list.join(', ') + '>> ';
                }
                return 'null';
            case 'string':
                return '\'' + obj.replace(/\\/g,'\\\\').replace(/(["'])/g, '\\$1') + '\'';
            case 'number':
            case 'boolean':
                return obj.toString();
            case 'null':
                return 'null';
            }
        },

        /**
         * Saves the construction in <tt>board</tt> to JessieCode.
         * @param {JXG.Board} board
         * @returns {String} JessieCode
         */
        toJessie: function (board) {
            var i, elements, id,
                dump = this.dump(board),
                script = [];

            dump.methods = this.setBoundingBox(dump.methods, board, '$board');

            elements = dump.elements;

            for (i = 0; i < elements.length; i++) {
                if (elements[i].attributes.name.length > 0) {
                    script.push('// ' + elements[i].attributes.name);
                }
                script.push('s' + i + ' = ' + elements[i].type + '(' + elements[i].parents.join(', ') + ') ' + this.toJCAN(elements[i].attributes).replace(/\n/, '\\n') + ';');

                if (elements[i].type === 'axis') {
                    // Handle the case that remove[All]Ticks had been called.
                    id = elements[i].attributes.id;
                    if (board.objects[id].defaultTicks === null) {
                        script.push('s' + i + '.removeAllTicks();');
                    }
                }
                script.push('');
            }

            for (i = 0; i < dump.methods.length; i++) {
                script.push(dump.methods[i].obj + '.' + dump.methods[i].method + '(' + this.arrayToParamStr(dump.methods[i].params, this.toJCAN) + ');');
                script.push('');
            }

            for (i = 0; i < dump.props.length; i++) {
                script.push(dump.props[i].obj + '.' + dump.props[i].prop + ' = ' + this.toJCAN(dump.props[i].val) + ';');
                script.push('');
            }

            return script.join('\n');
        },

        /**
         * Saves the construction in <tt>board</tt> to JavaScript.
         * @param {JXG.Board} board
         * @returns {String} JavaScript
         */
        toJavaScript: function (board) {
            var i, elements, id,
                dump = this.dump(board),
                script = [];

            dump.methods = this.setBoundingBox(dump.methods, board, 'board');

            elements = dump.elements;

            for (i = 0; i < elements.length; i++) {
                script.push('board.create("' + elements[i].type + '", [' + elements[i].parents.join(', ') + '], ' + Type.toJSON(elements[i].attributes) + ');');

                if (elements[i].type === 'axis') {
                    // Handle the case that remove[All]Ticks had been called.
                    id = elements[i].attributes.id;
                    if (board.objects[id].defaultTicks === null) {
                        script.push('board.objects["' + id + '"].removeTicks(board.objects["' + id + '"].defaultTicks);');
                    }
                }
            }

            for (i = 0; i < dump.methods.length; i++) {
                script.push(dump.methods[i].obj + '.' + dump.methods[i].method + '(' + this.arrayToParamStr(dump.methods[i].params, Type.toJSON) + ');');
                script.push('');
            }

            for (i = 0; i < dump.props.length; i++) {
                script.push(dump.props[i].obj + '.' + dump.props[i].prop + ' = ' + Type.toJSON(dump.props[i].val) + ';');
                script.push('');
            }

            return script.join('\n');
        }
    };

    return JXG.Dump;
});

/*
    Copyright 2018-2022
        Alfred Wassermann,
        Tigran Saluev

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 see define call
 */

/**
 * @fileoverview In this file the Comb element is defined.
 */

define('element/comb',[
    'jxg', 'utils/type', 'base/point'
], function (JXG, Type, Point) {

    "use strict";

    /**
     * @class A comb to display domains of inequalities.
     * @pseudo
     * @name Comb
     * @augments JXG.Curve
     * @constructor
     * @type JXG.Curve
     * @throws {Error} If the element cannot be constructed with the given parent
     *  objects an exception is thrown.
     * Parameter options:
     * @param {JXG.Point,array,function_JXG.Point,array,function} point1,point2 Parent elements
     * can be two elements either of type {@link JXG.Point} or array of
     * numbers describing the coordinates of a point. In the latter case the point
     * will be constructed automatically as a fixed invisible point.
     * It is possible to provide a function returning an array or a point,
     * instead of providing an array or a point.
     * @example
     * // Create a simple horizontal comb with invisible endpoints
     * var c = board.create('comb', [[1, 0], [3, 0]]);
     *
     * </pre><div class="jxgbox" id="JXG951ccb6a-52bc-4dc2-80e9-43db064f0f1b" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG951ccb6a-52bc-4dc2-80e9-43db064f0f1b', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}),
     *     c = board.create('comb', [[1, 0], [3, 0]]);
     * })();
     * </script><pre>
     *
     * @example
     * var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
     * var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
     * var c1 = board.create('comb', [p1, p2], {width: 0.2, frequency: 0.1, angle: Math.PI / 4});
     *
     * </pre><div id="JXG04186fd2-6340-11e8-9fb9-901b0e1b8723" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG04186fd2-6340-11e8-9fb9-901b0e1b8723',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
     *     var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
     *     var c1 = board.create('comb', [p1, p2], {width: 0.2, frequency: 0.1, angle: Math.PI / 4});
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * var s = board.create('slider', [[1,3], [4,3], [0.1, 0.3, 0.8]]);
     * var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
     * var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
     * var c1 = board.create('comb', [p1, p2], {
     *     width: function(){ return 4*s.Value(); },
     *     reverse: function(){ return (s.Value()<0.5) ? false : true; },
     *     frequency: function(){ return s.Value(); },
     *     angle: function(){ return s.Value() * Math.PI / 2; },
     *     curve: {
     *         strokeColor: 'red'
     *     }
     * });
     *
     * </pre><div id="JXG6eb1bcd1-407e-4f13-8f0c-45ef39a0cfb3" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG6eb1bcd1-407e-4f13-8f0c-45ef39a0cfb3',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var s = board.create('slider', [[1,3], [4,3], [0.1, 0.3, 0.8]]);
     *     var p1 = board.create('glider', [-3, 0, board.defaultAxes.x]);
     *     var p2 = board.create('glider', [-1, 0, board.defaultAxes.x]);
     *     var c1 = board.create('comb', [p1, p2], {
     *         width: function(){ return 4*s.Value(); },
     *         reverse: function(){ return (s.Value()<0.5) ? false : true; },
     *         frequency: function(){ return s.Value(); },
     *         angle: function(){ return s.Value() * Math.PI / 2; },
     *         curve: {
     *             strokeColor: 'red'
     *         }
     *     });
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createComb = function(board, parents, attributes) {
        var p1, p2, c, attr, parent_types;
            //ds, angle, width, p;

        if (parents.length === 2) {
            // point 1 given by coordinates
            if (Type.isArray(parents[0]) && parents[0].length > 1) {
                attr = Type.copyAttributes(attributes, board.options, 'comb', 'point1');
                p1 = board.create('point', parents[0], attr);
            } else if (Type.isString(parents[0]) || Type.isPoint(parents[0])) {
                p1 =  board.select(parents[0]);
            } else if (Type.isFunction(parents[0]) && Type.isPoint(parents[0]())) {
                p1 = parents[0]();
            } else if (Type.isFunction(parents[0]) && parents[0]().length && parents[0]().length >= 2) {
                attr = Type.copyAttributes(attributes, board.options, 'comb', 'point1');
                p1 = Point.createPoint(board, parents[0](), attr);
            } else {
                throw new Error("JSXGraph: Can't create comb with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                    "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]");
            }

            // point 2 given by coordinates
            if (Type.isArray(parents[1]) && parents[1].length > 1) {
                attr = Type.copyAttributes(attributes, board.options, 'comb', 'point2');
                p2 = board.create('point', parents[1], attr);
            } else if (Type.isString(parents[1]) || Type.isPoint(parents[1])) {
                p2 =  board.select(parents[1]);
            } else if (Type.isFunction(parents[1]) &&  Type.isPoint(parents[1]()) ) {
                p2 = parents[1]();
            } else if (Type.isFunction(parents[1]) && parents[1]().length && parents[1]().length >= 2) {
                attr = Type.copyAttributes(attributes, board.options, 'comb', 'point2');
                p2 = Point.createPoint(board, parents[1](), attr);
            } else {
                throw new Error("JSXGraph: Can't create comb with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                    "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]");
            }
        } else {
            parent_types = parents.map(function(parent) { return "'" + (typeof parent) + "'"; });
            throw new Error("JSXGraph: Can't create comb with parent types " +
                parent_types.join(", ") + "." +
                "\nPossible parent types: [point,point], [[x1,y1],[x2,y2]]");
        }

        // attr = Type.copyAttributes(attributes, board.options, 'comb', 'curve');
        attr = Type.copyAttributes(attributes, board.options, 'comb');
        Type.merge(attr, Type.copyAttributes(attributes, board.options, 'comb', 'curve'));
        c = board.create('curve', [[0], [0]], attr);

        /**
         * @ignore
         */
         c.updateDataArray = function() {
            var s = 0,
                max_s = p1.Dist(p2),
                cs, sn, dx, dy,
                x, y, f,
                p1_inner = p1,
                p2_inner = p2,
                ds, angle, width;

            ds = Type.evaluate(c.visProp.frequency);
            angle = -Type.evaluate(c.visProp.angle);
            width = Type.evaluate(c.visProp.width);
            if (Type.evaluate(c.visProp.reverse)) {
                p1_inner = p2;
                p2_inner = p1;
                angle = -angle;
            }
            cs = Math.cos(angle);
            sn = Math.sin(angle);
            dx = (p2_inner.X() - p1_inner.X()) / max_s;
            dy = (p2_inner.Y() - p1_inner.Y()) / max_s;

            // But instead of lifting by sin(angle), we want lifting by width.
            cs *= width / Math.abs(sn);
            sn *= width / Math.abs(sn);

            this.dataX = [];
            this.dataY = [];
            // TODO Handle infinite boundaries?
            while (s < max_s) {
                x = p1_inner.X() + dx * s;
                y = p1_inner.Y() + dy * s;

                // We may need to cut the last piece of a comb.
                f = Math.min(cs, max_s - s) / Math.abs(cs);
                sn *= f;
                cs *= f;

                this.dataX.push(x);
                this.dataY.push(y);

                this.dataX.push(x + dx * cs + dy * sn);
                this.dataY.push(y - dx * sn + dy * cs);

                this.dataX.push(NaN);  // Force a jump
                this.dataY.push(NaN);
                s += ds;
            }
        };

        return c;
    };

    JXG.registerElement('comb', JXG.createComb);

    return {
        createComb: JXG.createComb
    };

});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 see define call
 */

/**
 * @fileoverview Example file for a triangle implemented as a extension to JSXGraph.
 */

define('element/slopetriangle',[
    'jxg', 'utils/type', 'base/constants', 'base/polygon'
], function (JXG, Type, Const, Polygon) {

    "use strict";

    var priv = {
            removeSlopeTriangle: function () {
                Polygon.Polygon.prototype.remove.call(this);

                this.board.removeObject(this.toppoint);
                this.board.removeObject(this.glider);

                this.board.removeObject(this.baseline);
                this.board.removeObject(this.basepoint);

                this.board.removeObject(this.label);

                if (this._isPrivateTangent) {
                    this.board.removeObject(this.tangent);
                }
            },
            Value: function () {
                return this.tangent.getSlope();
            }
        };

    /**
     * @class Slope triangle for a point on a line.
     * @pseudo
     * @name Slopetriangle
     * @augments JXG.Line
     * @constructor
     * @type JXG.Polygon
     * @throws {Error} If the element cannot be constructed with the given parent objects an exception is thrown.
     * Parameter options:
     * @param {JXG.Line} t A tangent based on a glider on some object, e.g. curve, circle, line or turtle.
     * @param {JXG.Line_JXG.Point} li, p A line and a point on that line.
     *  The user has to take care that the point is a member of the line.
     * @example
     * // Create a slopetriangle on a tangent
     * var f = board.create('plot', ['sin(x)']),
     *     g = board.create('glider', [1, 2, f]),
     *     t = board.create('tangent', [g]),
     *
     *     st = board.create('slopetriangle', [t]);
     *
     * </pre><div class="jxgbox" id="JXG951ccb6a-52bc-4dc2-80e9-43db064f0f1b" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXG951ccb6a-52bc-4dc2-80e9-43db064f0f1b', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}),
     *     f = board.create('plot', ['sin(x)']),
     *     g = board.create('glider', [1, 2, f]),
     *     t = board.create('tangent', [g]),
     *
     *     st = board.create('slopetriangle', [t]);
     * })();
     * </script><pre>
     *
     * @example
     * // Create a on a line and a point on that line
     * var p1 = board.create('point', [-2, 3]),
     *     p2 = board.create('point', [2, -3]),
     *     li = board.create('line', [p1, p2]),
     *     p = board.create('glider', [0, 0, li]),
     *
     *     st = board.create('slopetriangle', [li, p]);
     *
     * </pre><div class="jxgbox" id="JXGb52f451c-22cf-4677-852a-0bb9d764ee95" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function () {
     *   var board = JXG.JSXGraph.initBoard('JXGb52f451c-22cf-4677-852a-0bb9d764ee95', {boundingbox: [-5, 5, 5, -5], axis: true, showcopyright: false, shownavigation: false}),
     *     p1 = board.create('point', [-2, 3]),
     *     p2 = board.create('point', [2, -3]),
     *     li = board.create('line', [p1, p2]),
     *     p = board.create('glider', [0, 0, li]),
     *
     *     st = board.create('slopetriangle', [li, p]);
     * })();
     * </script><pre>
     */
    JXG.createSlopeTriangle = function (board, parents, attributes) {
        var el, tangent, tglide, glider, toppoint, baseline, basepoint, label, attr,
            isPrivateTangent = false;

        if (parents.length === 1 && parents[0].type === Const.OBJECT_TYPE_TANGENT) {
            tangent = parents[0];
            tglide = tangent.glider;
        } else if (parents.length === 1 && parents[0].type === Const.OBJECT_TYPE_GLIDER) {
            tglide = parents[0];
            attr = Type.copyAttributes(attributes, board.options,  'slopetriangle', 'tangent');
            tangent = board.create('tangent', [tglide], attr);
            isPrivateTangent = true;
        } else if (parents.length === 2 &&
                parents[0].elementClass === Const.OBJECT_CLASS_LINE && Type.isPoint(parents[1])) {
            tangent = parents[0];
            tglide = parents[1];
        } else {
            throw new Error("JSXGraph: Can't create slope triangle with parent types '" + (typeof parents[0]) + "'.");
        }

        attr = Type.copyAttributes(attributes, board.options, 'slopetriangle', 'basepoint');
        basepoint = board.create('point', [function () {
            return [tglide.X() + 1,  tglide.Y()];
        }], attr);

        attr = Type.copyAttributes(attributes, board.options, 'slopetriangle', 'baseline');
        baseline = board.create('line', [tglide, basepoint], attr);

        attr = Type.copyAttributes(attributes, board.options, 'slopetriangle', 'glider');
        glider = board.create('glider', [tglide.X() + 1, tglide.Y(), baseline], attr);

        attr = Type.copyAttributes(attributes, board.options, 'slopetriangle', 'toppoint');
        toppoint = board.create('point', [function () {
            return [glider.X(), glider.Y() + (glider.X() - tglide.X()) * tangent.getSlope()];
        }], attr);

        attr = Type.copyAttributes(attributes, board.options, 'slopetriangle');
        attr.borders = Type.copyAttributes(attr.borders, board.options, 'slopetriangle', 'borders');
        el = board.create('polygon', [tglide, glider, toppoint], attr);

        /**
         * Returns the value of the slope triangle, that is the slope of the tangent.
         * @name Value
         * @memberOf Slopetriangle.prototype
         * @function
         * @returns {Number} slope of the tangent.
         */
        el.Value = priv.Value;
        el.tangent = tangent;
        el._isPrivateTangent = isPrivateTangent;

        //el.borders[0].setArrow(false, {type: 2, size: 10});
        //el.borders[1].setArrow(false, {type: 2, size: 10});
        el.borders[2].setArrow(false, false);

        attr = Type.copyAttributes(attributes, board.options, 'slopetriangle', 'label');
        label = board.create('text', [
            function () { return glider.X() + 0.1; },
            function () { return (glider.Y() + toppoint.Y()) * 0.5; },
            function () { return ''; }
        ], attr);

        label._setText(function () {
            return Type.toFixed(el.Value(), Type.evaluate(label.visProp.digits));
        });
        label.fullUpdate();

        el.glider = glider;
        el.basepoint = basepoint;
        el.baseline = baseline;
        el.toppoint = toppoint;
        el.label = label;

        el.subs = {
            glider: glider,
            basePoint: basepoint,
            baseLine: baseline,
            topPoint: toppoint,
            label: label
        };
        el.inherits.push(glider, basepoint, baseline, toppoint, label);

        el.methodMap = JXG.deepCopy(el.methodMap, {
            tangent: 'tangent',
            glider: 'glider',
            basepoint: 'basepoint',
            baseline: 'baseline',
            toppoint: 'toppoint',
            label: 'label',
            Value: 'Value',
            V: 'Value'
        });

        el.remove = priv.removeSlopeTriangle;

        return el;
    };

    JXG.registerElement('slopetriangle', JXG.createSlopeTriangle);

    return {
        createSlopeTriangle: JXG.createSlopeTriangle
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, window: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/env
 utils/type
 */

/**
 * @fileoverview In this file the Text element is defined.
 */

define('element/checkbox',[
    'jxg', 'utils/env', 'utils/type'
], function (JXG, Env, Type) {

    "use strict";

    var priv = {
            CheckboxChangeEventHandler: function () {
                this._value = this.rendNodeCheckbox.checked;
                this.board.update();
            }
        };

    /**
     * @class This element is used to provide a constructor for special texts containing a
     * form checkbox element.
     * <p>
     * For this element, the attribute "display" has to have the value 'html' (which is the default).
     *
     * @pseudo
     * @description
     * @name Checkbox
     * @augments Text
     * @constructor
     * @type JXG.Text
     *
     * @param {number,function_number,function_String_String} x,y,label Parent elements for checkbox elements.
     *                     <p>
     *                     x and y are the coordinates of the lower left corner of the text box.
     *                      The position of the text is fixed,
     *                     x and y are numbers. The position is variable if x or y are functions.
     *                     <p>
     *                     The label of the input element may be given as string.
     *                     <p>
     *                     The value of the checkbox can be controlled with the attribute <tt>checked</tt>
     *                     <p>The HTML node can be accessed with <tt>element.rendNodeCheckbox</tt>
     *
     * @example
     *   // Create a checkbox element at position [0,3].
     *   var checkbox = board.create('checkbox', [0, 3, 'Change Y'], {});
     *   var p = board.create('point', [
     *       function(){ return 0.5;}, // X-coordinate
     *       function() {
     *           y = 0.5;
     *           if (checkbox.Value()) {
     *               y += 0.5;
     *           }
     *           return y;
     *       }]);
     * </pre><div class="jxgbox" id="JXG0e835e0b-ed0c-4b85-b682-78158c0e6f5c" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function() {
     *   var t1_board = JXG.JSXGraph.initBoard('JXG0e835e0b-ed0c-4b85-b682-78158c0e6f5c', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     *   var checkbox = t1_board.create('checkbox', [0, 3, 'Change Y'], {});
     *   var p = t1_board.create('point', [
     *       function(){ return 0.5;}, // X-coordinate
     *       function() {
     *           y = 0.5;
     *           if (checkbox.Value()) {
     *               y += 0.5;
     *           }
     *           return y;
     *       }]);
     * })();
     * </script><pre>
     *
     * The checkbox can be supplied with custom-made events by using the property rendNodeCheckbox.
     * @example
     * var checkbox = board.create('checkbox', [0, 4, 'Click me']),
     *     p = board.create('point', [1, 1]);
     *
     * JXG.addEvent(checkbox.rendNodeCheckbox, 'change', function() {
     *     if (this.Value()) {
     *         p.moveTo([4, 1]);
     *     } else {
     *         p.moveTo([1, 1]);
     *     }
     * }, checkbox);
     * </pre><div class="jxgbox" id="JXGb2f2345a-057d-44ce-bd7a-6aaff70bc810" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     * (function() {
     * var board = JXG.JSXGraph.initBoard('JXGb2f2345a-057d-44ce-bd7a-6aaff70bc810', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     * var checkbox = board.create('checkbox', [0, 4, 'Click me']),
     *     p = board.create('point', [1, 1]);
     *
     * JXG.addEvent(checkbox.rendNodeCheckbox, 'change', function() {
     *     if (this.Value()) {
     *         p.moveTo([4, 1]);
     *     } else {
     *         p.moveTo([1, 1]);
     *     }
     * }, checkbox);
     * })();
     * </script><pre>
     *
     * @example
     *         var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2});
     *         var c1 = board.create('checkbox', [-3, 2, 'label 1'], {});
     *         var b1 = board.create('button', [-3, -1, 'Change texts', function () {
     *                 i1.setText('g(x)');
     *                 i1.set('cos(x)');
     *                 c1.setText('label 2');
     *                 b1.setText('Texts are changed');
     *             }],
     *             {cssStyle: 'width:400px'});
     *
     * </pre><div id="JXG11cac8gg-2354-47e7-9da4-eb298e53de05" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG11cac8gg-2354-47e7-9da4-eb298e53de05',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *             var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2});
     *             var c1 = board.create('checkbox', [-3, 2, 'label 1'], {});
     *             var b1 = board.create('button', [-3, -1, 'Change texts', function () {
     *                     i1.setText('g(x)');
     *                     i1.set('cos(x)');
     *                     c1.setText('label 2');
     *                     b1.setText('Texts are changed');
     *                 }],
     *                 {cssStyle: 'width:400px'});
     *
     *     })();
     *
     * </script><pre>
     */
    JXG.createCheckbox = function (board, parents, attributes) {
        var t, par,
            attr = Type.copyAttributes(attributes, board.options, 'checkbox');

        //if (parents.length !== 3) {
            //throw new Error("JSXGraph: Can't create checkbox with parent types '" +
            //    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
            //    "\nPossible parents are: [[x,y], label]");
        //}

        par = [parents[0], parents[1],
            '<span style="display:inline">' +
            '<input type="checkbox" /><label for=""></label>' +
            '</span>'
            ];

        //t = JXG.createText(board, par, attr);
        t = board.create('text', par, attr);
        t.type = Type.OBJECT_TYPE_CHECKBOX;

        t.rendNodeCheckbox = t.rendNode.childNodes[0].childNodes[0];
        t.rendNodeLabel = t.rendNode.childNodes[0].childNodes[1];

        t.rendNodeTag = t.rendNodeCheckbox; // Needed for unified treatment in setAttribute
        t.rendNodeTag.disabled = !!attr.disabled;

        t.rendNodeLabel.innerHTML = parents[2];
        t.rendNodeCheckbox.id = t.rendNode.id + '_checkbox';
        t.rendNodeLabel.id = t.rendNode.id + '_label';
        t.rendNodeLabel.setAttribute('for', t.rendNodeCheckbox.id);

        // This sets the font-size of the checkbox itself
        t.visPropOld.fontsize = "0px";
        board.renderer.updateTextStyle(t, false);

        t.rendNodeCheckbox.checked = attr.checked;

        t._value = attr.checked;

        /**
         * Returns the value of the checkbox element
         * @name Value
         * @memberOf Checkbox.prototype
         * @function
         * @returns {String} value of the checkbox.
         */
        t.Value = function () {
            return this._value;
        };

        t.update = function () {
            if (this.needsUpdate) {
                JXG.Text.prototype.update.call(this);
                this._value = this.rendNodeCheckbox.checked;
            }
            return this;
        };

        Env.addEvent(t.rendNodeCheckbox, 'change', priv.CheckboxChangeEventHandler, t);

        return t;
    };

    JXG.registerElement('checkbox', JXG.createCheckbox);

    return {
        createCheckbox: JXG.createCheckbox
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, window: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/env
 utils/type
 */

/**
 * @fileoverview In this file the Text element is defined.
 */

define('element/input',[
    'jxg', 'utils/env', 'utils/type'
], function (JXG, Env, Type) {

    "use strict";

    var priv = {
            InputInputEventHandler: function (evt) {
                this._value = this.rendNodeInput.value;
                this.board.update();
            }
        };

    /**
     * @class This element is used to provide a constructor for special texts containing a
     * HTML form input element.
     * <p>
     * If the width of element is set with the attribute "cssStyle", the width of the
     * label must be added.
     * <p>
     * For this element, the attribute "display" has to have the value 'html' (which is the default).
     * @pseudo
     * @description
     * @name Input
     * @augments Text
     * @constructor
     * @type JXG.Text
     *
     * @param {number,function_number,function_String_String} x,y,value,label Parent elements for input elements.
     *                     <p>
     *                     x and y are the coordinates of the lower left corner of the text box. The position of the text is fixed,
     *                     x and y are numbers. The position is variable if x or y are functions.
     *                     <p>
     *                     The default value of the input element may be given as string.
     *                     <p>
     *                     The label of the input element may be given  as string.
     *
     * @example
     *  // Create an input element at position [1,4].
     *  var input = board.create('input', [0, 1, 'sin(x)*x', 'f(x)='], {cssStyle: 'width: 100px'});
     *  var f = board.jc.snippet(input.Value(), true, 'x', false);
     *  var graph = board.create('functiongraph',[f,
     *          function() {
     *            var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[0,0],board);
     *            return c.usrCoords[1];
     *          },
     *          function() {
     *            var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[board.canvasWidth,0],board);
     *            return c.usrCoords[1];
     *          }
     *        ]);
     *
     *  board.create('text', [1, 3, '&lt;button onclick="updateGraph()"&gt;Update graph&lt;/button&gt;']);
     *
     *  var updateGraph = function() {
     *      graph.Y = board.jc.snippet(input.Value(), true, 'x', false);
     *      graph.updateCurve();
     *      board.update();
     *  }
     * </pre><div class="jxgbox" id="JXGc70f55f1-21ba-4719-a37d-a93ae2943faa" style="width: 500px; height: 300px;"></div>
     * <script type="text/javascript">
     *   var t1_board = JXG.JSXGraph.initBoard('JXGc70f55f1-21ba-4719-a37d-a93ae2943faa', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     *   var input = t1_board.create('input', [1, 4, 'sin(x)*x', 'f(x)='], {cssStyle: 'width: 100px'});
     *   var f = t1_board.jc.snippet(input.Value(), true, 'x', false);
     *   var graph = t1_board.create('functiongraph',[f,
     *          function() {
     *            var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[0,0],t1_board);
     *            return c.usrCoords[1];
     *          },
     *          function() {
     *            var c = new JXG.Coords(JXG.COORDS_BY_SCREEN,[t1_board.canvasWidth,0],t1_board);
     *            return c.usrCoords[1];
     *          }
     *        ]);
     *
     *  t1_board.create('text', [1, 3, '<button onclick="updateGraph()">Update graph</button>']);
     *
     *  var updateGraph = function() {
     *      graph.Y = t1_board.jc.snippet(input.Value(), true, 'x', false);
     *      graph.updateCurve();
     *      t1_board.update();
     *  }
     * </script><pre>
     */
    JXG.createInput = function (board, parents, attributes) {
        var t, par,
            attr = Type.copyAttributes(attributes, board.options, 'input');

        par = [parents[0], parents[1],
            '<span style="display:inline; white-space:nowrap; padding:0px;">' +
            '<span></span><input type="text" maxlength="' +
                attr.maxlength +
                '" style="width:100%"/>' +
            '</span>'
            ];

        //t = JXG.createText(board, par, attr);
        t = board.create('text', par, attr);
        t.type = Type.OBJECT_TYPE_INPUT;

        t.rendNodeLabel = t.rendNode.childNodes[0].childNodes[0];
        t.rendNodeInput = t.rendNode.childNodes[0].childNodes[1];
        t.rendNodeLabel.innerHTML = parents[3];
        t.rendNodeInput.value = parents[2];
        t.rendNodeTag = t.rendNodeInput; // Needed for unified treatment in setAttribute
        t.rendNodeTag.disabled = !!attr.disabled;
        t.rendNodeLabel.id = t.rendNode.id + '_label';
        t.rendNodeInput.id = t.rendNode.id + '_input';

        t._value = parents[2];
        t.update = function () {
            if (this.needsUpdate) {
                JXG.Text.prototype.update.call(this);
                this._value = this.rendNodeInput.value;
            }
            return this;
        };

        /**
         * Returns the value (content) of the input element
         * @name Value
         * @memberOf Input.prototype
         * @function
         * @returns {String} content of the input field.
         */
        t.Value = function () {
            return this._value;
        };

        /**
         * Sets value of the input element.
         * @name set
         * @memberOf Input.prototype
         * @function
         *
         * @param {String} val
         * @returns {JXG.GeometryElement} Reference to the element.
         *
         * @example
         *         var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2});
         *         var c1 = board.create('checkbox', [-3, 2, 'label 1'], {});
         *         var b1 = board.create('button', [-3, -1, 'Change texts', function () {
         *                 i1.setText('g(x)');
         *                 i1.set('cos(x)');
         *                 c1.setText('label 2');
         *                 b1.setText('Texts are changed');
         *             }],
         *             {cssStyle: 'width:400px'});
         *
         * </pre><div id="JXG11cac8ff-2354-47e7-9da4-eb298e53de05" class="jxgbox" style="width: 300px; height: 300px;"></div>
         * <script type="text/javascript">
         *     (function() {
         *         var board = JXG.JSXGraph.initBoard('JXG11cac8ff-2354-47e7-9da4-eb298e53de05',
         *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
         *             var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2});
         *             var c1 = board.create('checkbox', [-3, 2, 'label 1'], {});
         *             var b1 = board.create('button', [-3, -1, 'Change texts', function () {
         *                     i1.setText('g(x)');
         *                     i1.set('cos(x)');
         *                     c1.setText('label 2');
         *                     b1.setText('Texts are changed');
         *                 }],
         *                 {cssStyle: 'width:400px'});
         *
         *     })();
         *
         * </script><pre>
         *
         */
        t.set = function (val) {
            this._value = val;
            this.rendNodeInput.value = val;
            return this;
        };

        Env.addEvent(t.rendNodeInput, 'input', priv.InputInputEventHandler, t);
        Env.addEvent(t.rendNodeInput, 'mousedown', function(evt) { if (Type.exists(evt.stopPropagation)) { evt.stopPropagation(); } }, t);
        Env.addEvent(t.rendNodeInput, 'touchstart', function(evt) { if (Type.exists(evt.stopPropagation)) { evt.stopPropagation(); } }, t);
        Env.addEvent(t.rendNodeInput, 'pointerdown', function(evt) { if (Type.exists(evt.stopPropagation)) { evt.stopPropagation(); } }, t);

        // This sets the font-size of the input HTML element
        t.visPropOld.fontsize = "0px";
        board.renderer.updateTextStyle(t, false);

        return t;
    };

    JXG.registerElement('input', JXG.createInput);

    return {
        createInput: JXG.createInput
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, window: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 utils/env
 utils/type
 */

/**
 * @fileoverview In this file the Text element is defined.
 */

define('element/button',[
    'jxg', 'utils/env', 'utils/type'
], function (JXG, Env, Type) {

    "use strict";

    var priv = {
            ButtonClickEventHandler: function () {
                if (this._handler) {
                    this._handler();
                }
                this.board.update();
            }
        };

    /**
     * @class This element is used to provide a constructor for special texts containing a
     * form button element.
     * <p>
     * For this element, the attribute "display" has to have the value 'html' (which is the default).
     *
     * @pseudo
     * @description
     * @name Button
     * @augments Text
     * @constructor
     * @type JXG.Text
     *
     * @param {number,function_number,function_String_function} x,y,label,handler Parent elements for button elements.
     *                     <p>
     *                     x and y are the coordinates of the lower left corner of the text box.
     *                      The position of the text is fixed,
     *                     x and y are numbers. The position is variable if x or y are functions.
     *                     <p>
     *                     The label of the input element may be given  as string.
     *                     <p>
     *                     The (optional) handler function which is called when the button is pressed.
     *
     * @example
     *  var p = board.create('point', [0.5, 0.5], {id: 'p1'});
     *
     *  // Create a button element at position [1,2].
     *  var button1 = board.create('button', [1, 2, 'Change Y with JavaScript', function() {
     *      p.moveTo([p.X(), p.Y() + 0.5], 100);
     *  }], {});
     *
     *  // Create a button element at position [1,4].
     *  var button2 = board.create('button', [1, 4, 'Change Y with JessieCode',
     *      "$('p1').Y = $('p1').Y() - 0.5;"
     *  ], {});
     *
     * </pre><div class="jxgbox" id="JXGf19b1bce-dd00-4e35-be97-ff1817d11514" style="width: 500px; height: 300px;"></div>
     * <script type="text/javascript">
     *  var t1_board = JXG.JSXGraph.initBoard('JXGf19b1bce-dd00-4e35-be97-ff1817d11514', {boundingbox: [-3, 6, 5, -3], axis: true, showcopyright: false, shownavigation: false});
     *  var p = t1_board.create('point', [0, -1], {id: 'p1'});
     *
     *  // Create a button element at position [1,2].
     *  var button1 = t1_board.create('button', [1, 2, 'Change Y with JavaScript', function() {
     *      p.moveTo([p.X(), p.Y() + 0.5], 100);
     *  }], {});
     *
     *  // Create a button element at position [1,4].
     *  var button2 = t1_board.create('button', [1, 4, 'Change Y with JessieCode',
     *      "$('p1').Y = $('p1').Y() - 0.5;"
     *  ], {});
     *
     * </script><pre>
     *
     * @example
     * // A toggle button
     * var butt = board.create('button', [-2, -2, 'Off', function() {
     *   var txt;
     *   butt.value = !butt.value;
     *   if (butt.value) {
     *   	txt = 'On';
     *   } else {
     *   	txt = 'Off';
     *   }
     * 	butt.rendNodeButton.innerHTML = txt;
     * }]);
     *
     * // Set initial value for the button
     * if (!JXG.exists(butt.value)) {
     * 	butt.value = false;
     * }
     *
     * var p = board.create('point', [2, -2], {
     * 	visible: () => butt.value
     * });
     *
     *
     *
     * </pre><div id="JXGa1eaab8f-c73b-4660-96ce-4ca17bcac4d6" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGa1eaab8f-c73b-4660-96ce-4ca17bcac4d6',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var butt = board.create('button', [-2, -2, 'Off', function() {
     *       var txt;
     *       butt.value = !butt.value;
     *       if (butt.value) {
     *       	txt = 'On';
     *       } else {
     *       	txt = 'Off';
     *       }
     *     	butt.rendNodeButton.innerHTML = txt;
     *     }]);
     *
     *     // Set initial value for the button
     *     if (!JXG.exists(butt.value)) {
     *     	butt.value = false;
     *     }
     *
     *     var p = board.create('point', [2, -2], {
     *     	visible: () => butt.value
     *     });
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     *         var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2});
     *         var c1 = board.create('checkbox', [-3, 2, 'label 1'], {});
     *         var b1 = board.create('button', [-3, -1, 'Change texts', function () {
     *                 i1.setText('g(x)');
     *                 i1.set('cos(x)');
     *                 c1.setText('label 2');
     *                 b1.setText('Texts are changed');
     *             }],
     *             {cssStyle: 'width:400px'});
     *
     * </pre><div id="JXG11cac8ff-2354-47e7-9da4-eb928e53de05" class="jxgbox" style="width: 300px; height: 300px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG11cac8ff-2354-47e7-9da4-eb928e53de05',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *             var i1 = board.create('input', [-3, 4, 'sin(x)', 'f(x)='], {cssStyle: 'width:4em', maxlength: 2});
     *             var c1 = board.create('checkbox', [-3, 2, 'label 1'], {});
     *             var b1 = board.create('button', [-3, -1, 'Change texts', function () {
     *                     i1.setText('g(x)');
     *                     i1.set('cos(x)');
     *                     c1.setText('label 2');
     *                     b1.setText('Texts are changed');
     *                 }],
     *                 {cssStyle: 'width:400px'});
     *
     *     })();
     *
     * </script><pre>
     *
     */
    JXG.createButton = function (board, parents, attributes) {
        var t, par,
            attr = Type.copyAttributes(attributes, board.options, 'button');

        //if (parents.length < 3) {
            //throw new Error("JSXGraph: Can't create button with parent types '" +
            //    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
            //    "\nPossible parents are: [x, y, label, handler]");
        //}

        par = [parents[0], parents[1], '<button type="button" style="width:100%;"></button>'];

        t = board.create('text', par, attr);
        t.type = Type.OBJECT_TYPE_BUTTON;

        t.rendNodeButton = t.rendNode.childNodes[0];
        t.rendNodeButton.id = t.rendNode.id + '_button';
        t.rendNodeButton.innerHTML = parents[2];

        t.rendNodeTag = t.rendNodeButton; // Needed for unified treatment in setAttribute
        t.rendNodeTag.disabled = !!attr.disabled;

        // This sets the font-size of the button text
        t.visPropOld.fontsize = "0px";
        board.renderer.updateTextStyle(t, false);

        if (parents[3]) {
            if (Type.isString(parents[3])) {
                t._jc = new JXG.JessieCode();
                t._jc.use(board);
                t._handler = function () {
                    t._jc.parse(parents[3]);
                };
            } else {
                t._handler = parents[3];
            }
        }

        Env.addEvent(t.rendNodeButton, 'click', priv.ButtonClickEventHandler, t);
        Env.addEvent(t.rendNodeButton, 'mousedown', function (evt) { if (Type.exists(evt.stopPropagation)) { evt.stopPropagation(); } }, t);
        Env.addEvent(t.rendNodeButton, 'touchstart', function (evt) { if (Type.exists(evt.stopPropagation)) { evt.stopPropagation(); } }, t);
        Env.addEvent(t.rendNodeButton, 'pointerdown', function (evt) { if (Type.exists(evt.stopPropagation)) { evt.stopPropagation(); } }, t);

        return t;
    };

    JXG.registerElement('button', JXG.createButton);

    return {
        createButton: JXG.createButton
    };
});

/*
    Copyright 2008-2022
        Matthias Ehmann,
        Michael Gerhaeuser,
        Carsten Miller,
        Bianca Valentin,
        Alfred Wassermann,
        Peter Wilfahrt

    This file is part of JSXGraph.

    JSXGraph is free software dual licensed under the GNU LGPL or MIT 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

    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 and
    the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
    and <http://opensource.org/licenses/MIT/>.
 */


/*global JXG: true, define: true, window: true*/
/*jslint nomen: true, plusplus: true*/

/* depends:
 jxg
 base/constants
 base/coords
 base/element
 math/math
 utils/type
 */

/**
 * @fileoverview In this file the ForeignObject element is defined.
 */

 define('base/foreignobject',[
    'jxg', 'base/constants', 'base/coords', 'base/element', 'math/math', 'utils/type', 'base/coordselement'
], function (JXG, Const, Coords, GeometryElement, Mat, Type, CoordsElement) {

    "use strict";

    /**
     * Construct and handle SVG foreignObjects.
     *
     * @class Creates a new foreignObject object. Do not use this constructor to create a foreignObject. Use {@link JXG.Board#create} with
     * type {@link foreignobject} instead.
     * @augments JXG.GeometryElement
     * @augments JXG.CoordsElement
     * @param {string|JXG.Board} board The board the new foreignObject is drawn on.
     * @param {Array} coordinates An array with the user coordinates of the foreignObject.
     * @param {Object} attributes An object containing visual and - optionally - a name and an id.
     * @param {string|function} url An URL string or a function returning an URL string.
     * @param  {Array} size Array containing width and height of the foreignObject in user coordinates.
     *
     */
     JXG.ForeignObject = function (board, coords, attributes, content, size) {
        this.constructor(board, attributes, Const.OBJECT_TYPE_FOREIGNOBJECT, Const.OBJECT_CLASS_OTHER);
        this.element = this.board.select(attributes.anchor);
        this.coordsConstructor(coords);

        this._useUserSize = false;

        /**
         * Array of length two containing [width, height] of the foreignObject in pixel.
         * @type Array
         */
        this.size = [1, 1];
        if (Type.exists(size) && size.length > 0) {
            this._useUserSize = true;

            this.W = Type.createFunction(size[0], this.board, '');
            this.H = Type.createFunction(size[1], this.board, '');
            this.usrSize = [this.W(), this.H()];
        }

        // this.size = [Math.abs(this.usrSize[0] * board.unitX), Math.abs(this.usrSize[1] * board.unitY)];

        /**
         * 'href' of the foreignObject.
         * @type {string}
         */
        this.content = content;

        this.elType = 'foreignobject';

        // span contains the anchor point and the two vectors
        // spanning the foreignObject rectangle.
        // this.span = [
        //     this.coords.usrCoords.slice(0),
        //     [this.coords.usrCoords[0], this.W(), 0],
        //     [this.coords.usrCoords[0], 0, this.H()]
        // ];
        //this.parent = board.select(attributes.anchor);

        this.id = this.board.setId(this, 'Im');

        this.board.renderer.drawForeignObject(this);
        this.board.finalizeAdding(this);

        this.methodMap = JXG.deepCopy(this.methodMap, {
            addTransformation: 'addTransform',
            trans: 'addTransform'
        });
    };

    JXG.ForeignObject.prototype = new GeometryElement();
    Type.copyPrototypeMethods(JXG.ForeignObject, CoordsElement, 'coordsConstructor');

    JXG.extend(JXG.ForeignObject.prototype, /** @lends JXG.ForeignObject.prototype */ {

        /**
         * Checks whether (x,y) is over or near the image;
         * @param {Number} x Coordinate in x direction, screen coordinates.
         * @param {Number} y Coordinate in y direction, screen coordinates.
         * @returns {Boolean} True if (x,y) is over the image, False otherwise.
         */
        hasPoint: function (x, y) {
            var dx, dy, r, type, prec,
                c, v, p, dot,
                len = this.transformations.length;

                if (Type.isObject(Type.evaluate(this.visProp.precision))) {
                    type = this.board._inputDevice;
                    prec = Type.evaluate(this.visProp.precision[type]);
                } else {
                    // 'inherit'
                    prec = this.board.options.precision.hasPoint;
                }

            // Easy case: no transformation
            if (len === 0) {
                dx = x - this.coords.scrCoords[1];
                dy = this.coords.scrCoords[2] - y;
                r = prec;

                return dx >= -r && dx - this.size[0] <= r &&
                       dy >= -r && dy - this.size[1] <= r;
            }

            // foreignObject is transformed
            c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);
            // v is the vector from anchor point to the drag point
            c = c.usrCoords;
            v = [c[0] - this.span[0][0],
                c[1] - this.span[0][1],
                c[2] - this.span[0][2]];
            dot = Mat.innerProduct;   // shortcut

            // Project the drag point to the sides.
            p = dot(v, this.span[1]);
            if (0 <= p && p <= dot(this.span[1], this.span[1])) {
                p = dot(v, this.span[2]);

                if (0 <= p && p <= dot(this.span[2], this.span[2])) {
                    return true;
                }
            }
            return false;
        },

        /**
         * Recalculate the coordinates of lower left corner and the width and height.
         *
         * @returns {JXG.ForeignObject} A reference to the element
         * @private
         */
        update: function (fromParent) {
            if (!this.needsUpdate) {
                return this;
            }
            this.updateCoords(fromParent);
            this.updateSize();
            // this.updateSpan();
            return this;
        },

        /**
         * Send an update request to the renderer.
         * @private
         */
        updateRenderer: function () {
            return this.updateRendererGeneric('updateForeignObject');
        },

        /**
         * Updates the internal arrays containing size of the foreignObject.
         * @returns {JXG.ForeignObject} A reference to the element
         * @private
         */
        updateSize: function () {
            var bb = [0, 0];

            if (this._useUserSize) {
                this.usrSize = [this.W(), this.H()];
                this.size = [Math.abs(this.usrSize[0] * this.board.unitX),
                             Math.abs(this.usrSize[1] * this.board.unitY)];
            } else {
                if (this.rendNode.hasChildNodes()) {
                    bb = this.rendNode.childNodes[0].getBoundingClientRect();
                    this.size = [bb.width, bb.height];
                }
            }

            return this;
        },

        /**
         * Update the anchor point of the foreignObject, i.e. the lower left corner
         * and the two vectors which span the rectangle.
         * @returns {JXG.ForeignObject} A reference to the element
         * @private
         *
         */
        updateSpan: function () {
            var i, j, len = this.transformations.length, v = [];

            if (len === 0) {
                this.span = [[this.Z(), this.X(), this.Y()],
                    [this.Z(), this.W(), 0],
                    [this.Z(), 0, this.H()]];
            } else {
                // v contains the three defining corners of the rectangle/image
                v[0] = [this.Z(), this.X(), this.Y()];
                v[1] = [this.Z(), this.X() + this.W(), this.Y()];
                v[2] = [this.Z(), this.X(), this.Y() + this.H()];

                // Transform the three corners
                for (i = 0; i < len; i++) {
                    for (j = 0; j < 3; j++) {
                        v[j] = Mat.matVecMult(this.transformations[i].matrix, v[j]);
                    }
                }
                // Normalize the vectors
                for (j = 0; j < 3; j++) {
                    v[j][1] /= v[j][0];
                    v[j][2] /= v[j][0];
                    v[j][0] /= v[j][0];
                }
                // Compute the two vectors spanning the rectangle
                // by subtracting the anchor point.
                for (j = 1; j < 3; j++) {
                    v[j][0] -= v[0][0];
                    v[j][1] -= v[0][1];
                    v[j][2] -= v[0][2];
                }
                this.span = v;
            }

            return this;
        },

        addTransform: function (transform) {
            var i;

            if (Type.isArray(transform)) {
                for (i = 0; i < transform.length; i++) {
                    this.transformations.push(transform[i]);
                }
            } else {
                this.transformations.push(transform);
            }

            return this;
        },

        // Documented in element.js
        getParents: function () {
            var p = [this.url, [this.Z(), this.X(), this.Y()], this.usrSize];

            if (this.parents.length !== 0) {
                p = this.parents;
            }

            return p;
        },

        /**
         * Set the width and height of the foreignObject. After setting a new size,
         * board.update() or foreignobject.fullUpdate()
         * has to be called to make the change visible.
         * @param  {number, function, string} width  Number, function or string
         *                            that determines the new width of the foreignObject
         * @param  {number, function, string} height Number, function or string
         *                            that determines the new height of the foreignObject
         * @returns {JXG.ForeignObject} A reference to the element
         *
         */
        setSize: function(width, height) {
            this.W = Type.createFunction(width, this.board, '');
            this.H = Type.createFunction(height, this.board, '');
            this._useUserSize = true;

            return this;
        },

        /**
         * Returns the width of the foreignObject in user coordinates.
         * @returns {number} width of the image in user coordinates
         */
        W: function() {},  // Needed for docs, defined in constructor

        /**
         * Returns the height of the foreignObject in user coordinates.
         * @returns {number} height of the image in user coordinates
         */
        H: function() {}  // Needed for docs, defined in constructor
    });

    /**
     * @class This element is used to provide a constructor for arbitrary content in
     * an SVG foreignObject container.
     * <p>
     * Instead of board.create('foreignobject') the shortcut board.create('fo') may be used.
     *
     * @pseudo
     * @description
     * @name ForeignObject
     * @augments JXG.ForeignObject
     * @constructor
     * @type JXG.ForeignObject
     *
     * @param {String} content HTML content of the foreignObject. May also be &lt;video&gt; or &lt;iframe&gt;
     * @param {Array} position Position of the foreignObject given by [x, y] in user coordinates. Same as for images.
     * @param {Array} [size] (Optional) argument size of the foreignObject in user coordinates. If not given, size is specified by the HTML attributes
     * or CSS properties of the content.
     *
     * @see Image
     *
     * @example
     * var p = board.create('point', [1, 7], {size: 16});
     * var fo = board.create('foreignobject', [
     *     '&lt;video width="300" height="200" src="https://eucbeniki.sio.si/vega2/278/Video_metanje_oge_.mp4" type="html5video" controls&gt;',
     *     [0, -3], [9, 6]],
     *     {layer: 8, fixed: true}
     *  );
     *
     * </pre><div id="JXG0c122f2c-3671-4a28-80a9-f4c523eeda89" class="jxgbox" style="width: 500px; height: 500px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG0c122f2c-3671-4a28-80a9-f4c523eeda89',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var p = board.create('point', [1, 7], {size: 16});
     *     var fo = board.create('foreignobject', [
     *         '<video width="300" height="200" src="https://eucbeniki.sio.si/vega2/278/Video_metanje_oge_.mp4" type="html5video" controls>',
     *         [0, -3], [9, 6]],
     *         {layer: 8, fixed: true}
     *      );
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * var p = board.create('point', [1, 7], {size: 16});
     * var fo = board.create('fo', [
     *     '&lt;div style="background-color:blue; color: yellow; padding:20px; width:200px; height:50px; "&gt;Hello&lt;/div&gt;',
     *     [-7, -6]],
     *     {layer: 1, fixed: false}
     *  );
     *
     * </pre><div id="JXG1759c868-1a4a-4767-802c-91f84902e3ec" class="jxgbox" style="width: 500px; height: 500px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXG1759c868-1a4a-4767-802c-91f84902e3ec',
     *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
     *     var p = board.create('point', [1, 7], {size: 16});
     *     var fo = board.create('foreignobject', [
     *         '<div style="background-color:blue; color: yellow; padding:20px; width:200px; height:50px; ">Hello</div>',
     *         [-7, -6]],
     *         {layer: 1, fixed: false}
     *      );
     *
     *     })();
     *
     * </script><pre>
     *
     * @example
     * board.renderer.container.style.backgroundColor = 'lightblue';
     * var points = [];
     * points.push( board.create('point', [-2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 am'}) );
     * points.push( board.create('point', [0, 3.5],  {fixed:false,color: 'yellow', size: 6,name:'12 pm'}) );
     * points.push( board.create('point', [2, 3.5],  {fixed:false,color: 'yellow', size: 6,name:'6 pm'}) );
     *
     * var fo = board.create('fo', [
     *     '&lt;video width="100%" height="100%" src="https://benedu.net/moodle/aaimg/ajx_img/astro/tr/1vd.mp4" type="html5video" controls&gt;',
     *     [-6, -4], [12, 8]],
     *     {layer: 0, fixed: true}
     *  );
     *
     * var f = JXG.Math.Numerics.lagrangePolynomial(points);
     * var graph = board.create('functiongraph', [f, -10, 10], {fixed:true,strokeWidth:3, layer: 8});
     *
     * </pre><div id="JXGc3fc5520-13aa-4f66-abaa-42e9dc3fbf3f" class="jxgbox" style="width: 500px; height: 500px;"></div>
     * <script type="text/javascript">
     *     (function() {
     *         var board = JXG.JSXGraph.initBoard('JXGc3fc5520-13aa-4f66-abaa-42e9dc3fbf3f',
     *             {boundingbox: [-6,4,6,-4], axis: true, showcopyright: false, shownavigation: false});
     *     board.renderer.container.style.backgroundColor = 'lightblue';
     *     var points = [];
     *     points.push( board.create('point', [-2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 am'}) );
     *     points.push( board.create('point', [0, 3.5],  {fixed:false,color: 'yellow', size: 6,name:'12 pm'}) );
     *     points.push( board.create('point', [2, 3.5],  {fixed:false,color: 'yellow', size: 6,name:'6 pm'}) );
     *
     *     var fo = board.create('fo', [
     *         '<video width="100%" height="100%" src="https://benedu.net/moodle/aaimg/ajx_img/astro/tr/1vd.mp4" type="html5video" controls>',
     *         [-6, -4], [12, 8]],
     *         {layer: 0, fixed: true}
     *      );
     *
     *     var f = JXG.Math.Numerics.lagrangePolynomial(points);
     *     var graph = board.create('functiongraph', [f, -10, 10], {fixed:true,strokeWidth:3, layer: 8});
     *
     *     })();
     *
     * </script><pre>
     *
     * Video "24-hour time-lapse in Cascais, Portugal. Produced by Nuno Miguel Duarte" adapted from
     * <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>,
     * ©2016 Nuno Miguel Duarte.
     *
     */
    JXG.createForeignObject = function (board, parents, attributes) {
        var attr, fo,
            content = parents[0],
            coords = parents[1],
            size = [];

        if (parents.length >= 2) {
            size = parents[2];
        }

        attr = Type.copyAttributes(attributes, board.options, 'foreignobject');
        fo = CoordsElement.create(JXG.ForeignObject, board, coords, attr, content, size);
        if (!fo) {
            throw new Error("JSXGraph: Can't create foreignObject with parent types '" +
                    (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
                    "\nPossible parent types: [string, [x, y], [w, h]], [string, [x, y]], [element,transformation]");
        }

        return fo;
    };

    JXG.registerElement('foreignobject', JXG.createForeignObject);
    JXG.registerElement('fo', JXG.createForeignObject);

    return {
        ForeignObject: JXG.ForeignObject,
        createForeignobject: JXG.createForeignObject
    };
});

/*global define: true*/
define('../build/core.deps.js',[
    'jxg',
    'utils/env',
    'base/constants',
    'utils/type',
    'utils/xml',
    'utils/event',
    'utils/expect',
    'math/math',
    'math/probfuncs',
    'math/ia',
    'math/extrapolate',
    'math/qdt',
    'math/numerics',
    'math/nlp',
    'math/plot',
    'math/metapost',
    'math/statistics',
    'math/symbolic',
    'math/geometry',
    'math/clip',
    'math/poly',
    'math/complex',
    'renderer/abstract',
    'reader/file',
    'parser/geonext',
    'base/board',
    'options',
    'jsxgraph',
    'base/element',
    'base/coords',
    'base/coordselement',
    'base/point',
    'base/line',
    'base/group',
    'base/circle',
    'element/conic',
    'base/polygon',
    'base/curve',
    'element/arc',
    'element/sector',
    'base/composition',
    'element/composition',
    'element/locus',
    'base/text',
    'base/image',
    'element/slider',
    'element/measure',
    'base/chart',
    'base/transformation',
    'base/turtle',
    'utils/color',
    'base/ticks',
    'utils/zip',
    'utils/base64',
    'utils/uuid',
    'utils/encoding',
    'server/server',
    'parser/datasource',
    'parser/jessiecode',
    'parser/ca',
    'utils/dump',
    'renderer/svg',
    'renderer/vml',
    'renderer/canvas',
    'renderer/no',
    'element/comb',
    'element/slopetriangle',
    'element/checkbox',
    'element/input',
    'element/button',
    'base/foreignobject'
], function (JXG, Env) {
    "use strict";

    // We're in the browser, export JXG to the global JXG symbol for backwards compatibility
    if (Env.isBrowser) {
        window.JXG = JXG;

    // In node there are two cases:
    // 1) jsxgraph is used without requirejs (e.g. as jsxgraphcore.js)
    // 2) jsxgraph is loaded using requirejs (e.g. the dev version)
    //
    // in case 2) module is undefined, the export is set in src/jsxgraphnode.js using
    // the return value of this factory function
    } else if (Env.isNode() && typeof module === 'object') {
        module.exports = JXG;
    } else if (Env.isWebWorker()) {
        self.JXG = JXG;
    }

    return JXG;
});

 return require('../build/core.deps.js');
}));
